callback_actions.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. # pylint: disable=too-many-arguments, redefined-builtin, duplicate-code
  5. """Callback actions for various options."""
  6. from __future__ import annotations
  7. import abc
  8. import argparse
  9. import sys
  10. from collections.abc import Callable, Sequence
  11. from pathlib import Path
  12. from typing import TYPE_CHECKING, Any
  13. from pylint import exceptions, extensions, interfaces, utils
  14. if TYPE_CHECKING:
  15. from pylint.config.help_formatter import _HelpFormatter
  16. from pylint.lint import PyLinter
  17. from pylint.lint.run import Run
  18. class _CallbackAction(argparse.Action):
  19. """Custom callback action."""
  20. @abc.abstractmethod
  21. def __call__(
  22. self,
  23. parser: argparse.ArgumentParser,
  24. namespace: argparse.Namespace,
  25. values: str | Sequence[Any] | None,
  26. option_string: str | None = None,
  27. ) -> None:
  28. raise NotImplementedError # pragma: no cover
  29. class _DoNothingAction(_CallbackAction):
  30. """Action that just passes.
  31. This action is used to allow pre-processing of certain options
  32. without erroring when they are then processed again by argparse.
  33. """
  34. def __call__(
  35. self,
  36. parser: argparse.ArgumentParser,
  37. namespace: argparse.Namespace,
  38. values: str | Sequence[Any] | None,
  39. option_string: str | None = None,
  40. ) -> None:
  41. return None
  42. class _AccessRunObjectAction(_CallbackAction):
  43. """Action that has access to the Run object."""
  44. def __init__(
  45. self,
  46. option_strings: Sequence[str],
  47. dest: str,
  48. nargs: None = None,
  49. const: None = None,
  50. default: None = None,
  51. type: None = None,
  52. choices: None = None,
  53. required: bool = False,
  54. help: str = "",
  55. metavar: str = "",
  56. **kwargs: Run,
  57. ) -> None:
  58. self.run = kwargs["Run"]
  59. super().__init__(
  60. option_strings,
  61. dest,
  62. 0,
  63. const,
  64. default,
  65. type,
  66. choices,
  67. required,
  68. help,
  69. metavar,
  70. )
  71. @abc.abstractmethod
  72. def __call__(
  73. self,
  74. parser: argparse.ArgumentParser,
  75. namespace: argparse.Namespace,
  76. values: str | Sequence[Any] | None,
  77. option_string: str | None = None,
  78. ) -> None:
  79. raise NotImplementedError # pragma: no cover
  80. class _MessageHelpAction(_CallbackAction):
  81. """Display the help message of a message."""
  82. def __init__(
  83. self,
  84. option_strings: Sequence[str],
  85. dest: str,
  86. nargs: None = None,
  87. const: None = None,
  88. default: None = None,
  89. type: None = None,
  90. choices: None = None,
  91. required: bool = False,
  92. help: str = "",
  93. metavar: str = "",
  94. **kwargs: Run,
  95. ) -> None:
  96. self.run = kwargs["Run"]
  97. super().__init__(
  98. option_strings,
  99. dest,
  100. "+",
  101. const,
  102. default,
  103. type,
  104. choices,
  105. required,
  106. help,
  107. metavar,
  108. )
  109. def __call__(
  110. self,
  111. parser: argparse.ArgumentParser,
  112. namespace: argparse.Namespace,
  113. values: str | Sequence[str] | None,
  114. option_string: str | None = "--help-msg",
  115. ) -> None:
  116. assert isinstance(values, (list, tuple))
  117. values_to_print: list[str] = []
  118. for msg in values:
  119. assert isinstance(msg, str)
  120. values_to_print += utils._check_csv(msg)
  121. self.run.linter.msgs_store.help_message(values_to_print)
  122. sys.exit(0)
  123. class _ListMessagesAction(_AccessRunObjectAction):
  124. """Display all available messages."""
  125. def __call__(
  126. self,
  127. parser: argparse.ArgumentParser,
  128. namespace: argparse.Namespace,
  129. values: str | Sequence[Any] | None,
  130. option_string: str | None = "--list-enabled",
  131. ) -> None:
  132. self.run.linter.msgs_store.list_messages()
  133. sys.exit(0)
  134. class _ListMessagesEnabledAction(_AccessRunObjectAction):
  135. """Display all enabled messages."""
  136. def __call__(
  137. self,
  138. parser: argparse.ArgumentParser,
  139. namespace: argparse.Namespace,
  140. values: str | Sequence[Any] | None,
  141. option_string: str | None = "--list-msgs-enabled",
  142. ) -> None:
  143. self.run.linter.list_messages_enabled()
  144. sys.exit(0)
  145. class _ListCheckGroupsAction(_AccessRunObjectAction):
  146. """Display all the check groups that pylint knows about."""
  147. def __call__(
  148. self,
  149. parser: argparse.ArgumentParser,
  150. namespace: argparse.Namespace,
  151. values: str | Sequence[Any] | None,
  152. option_string: str | None = "--list-groups",
  153. ) -> None:
  154. for check in self.run.linter.get_checker_names():
  155. print(check)
  156. sys.exit(0)
  157. class _ListConfidenceLevelsAction(_AccessRunObjectAction):
  158. """Display all the confidence levels that pylint knows about."""
  159. def __call__(
  160. self,
  161. parser: argparse.ArgumentParser,
  162. namespace: argparse.Namespace,
  163. values: str | Sequence[Any] | None,
  164. option_string: str | None = "--list-conf-levels",
  165. ) -> None:
  166. for level in interfaces.CONFIDENCE_LEVELS:
  167. print(f"%-18s: {level}")
  168. sys.exit(0)
  169. class _ListExtensionsAction(_AccessRunObjectAction):
  170. """Display all extensions under pylint.extensions."""
  171. def __call__(
  172. self,
  173. parser: argparse.ArgumentParser,
  174. namespace: argparse.Namespace,
  175. values: str | Sequence[Any] | None,
  176. option_string: str | None = "--list-extensions",
  177. ) -> None:
  178. for filename in Path(extensions.__file__).parent.iterdir():
  179. if filename.suffix == ".py" and not filename.stem.startswith("_"):
  180. extension_name, _, _ = filename.stem.partition(".")
  181. print(f"pylint.extensions.{extension_name}")
  182. sys.exit(0)
  183. class _FullDocumentationAction(_AccessRunObjectAction):
  184. """Display the full documentation."""
  185. def __call__(
  186. self,
  187. parser: argparse.ArgumentParser,
  188. namespace: argparse.Namespace,
  189. values: str | Sequence[Any] | None,
  190. option_string: str | None = "--full-documentation",
  191. ) -> None:
  192. utils.print_full_documentation(self.run.linter)
  193. sys.exit(0)
  194. class _GenerateRCFileAction(_AccessRunObjectAction):
  195. """Generate a pylintrc file."""
  196. def __call__(
  197. self,
  198. parser: argparse.ArgumentParser,
  199. namespace: argparse.Namespace,
  200. values: str | Sequence[Any] | None,
  201. option_string: str | None = "--generate-rcfile",
  202. ) -> None:
  203. # TODO: 4.x: Deprecate this after the auto-upgrade functionality of
  204. # pylint-config is sufficient.
  205. self.run.linter._generate_config(skipsections=("Commands",))
  206. sys.exit(0)
  207. class _GenerateConfigFileAction(_AccessRunObjectAction):
  208. """Generate a .toml format configuration file."""
  209. def __call__(
  210. self,
  211. parser: argparse.ArgumentParser,
  212. namespace: argparse.Namespace,
  213. values: str | Sequence[Any] | None,
  214. option_string: str | None = "--generate-toml-config",
  215. ) -> None:
  216. print(self.run.linter._generate_config_file())
  217. sys.exit(0)
  218. class _ErrorsOnlyModeAction(_AccessRunObjectAction):
  219. """Turn on errors-only mode.
  220. Error mode:
  221. * disable all but error messages
  222. * disable the 'miscellaneous' checker which can be safely deactivated in
  223. debug
  224. * disable reports
  225. * do not save execution information
  226. """
  227. def __call__(
  228. self,
  229. parser: argparse.ArgumentParser,
  230. namespace: argparse.Namespace,
  231. values: str | Sequence[Any] | None,
  232. option_string: str | None = "--errors-only",
  233. ) -> None:
  234. self.run.linter._error_mode = True
  235. class _LongHelpAction(_AccessRunObjectAction):
  236. """Display the long help message."""
  237. def __call__(
  238. self,
  239. parser: argparse.ArgumentParser,
  240. namespace: argparse.Namespace,
  241. values: str | Sequence[Any] | None,
  242. option_string: str | None = "--long-help",
  243. ) -> None:
  244. formatter: _HelpFormatter = self.run.linter._arg_parser._get_formatter() # type: ignore[assignment]
  245. # Add extra info as epilog to the help message
  246. self.run.linter._arg_parser.epilog = formatter.get_long_description()
  247. print(self.run.linter.help())
  248. sys.exit(0)
  249. class _AccessLinterObjectAction(_CallbackAction):
  250. """Action that has access to the Linter object."""
  251. def __init__(
  252. self,
  253. option_strings: Sequence[str],
  254. dest: str,
  255. nargs: None = None,
  256. const: None = None,
  257. default: None = None,
  258. type: None = None,
  259. choices: None = None,
  260. required: bool = False,
  261. help: str = "",
  262. metavar: str = "",
  263. **kwargs: PyLinter,
  264. ) -> None:
  265. self.linter = kwargs["linter"]
  266. super().__init__(
  267. option_strings,
  268. dest,
  269. 1,
  270. const,
  271. default,
  272. type,
  273. choices,
  274. required,
  275. help,
  276. metavar,
  277. )
  278. @abc.abstractmethod
  279. def __call__(
  280. self,
  281. parser: argparse.ArgumentParser,
  282. namespace: argparse.Namespace,
  283. values: str | Sequence[Any] | None,
  284. option_string: str | None = None,
  285. ) -> None:
  286. raise NotImplementedError # pragma: no cover
  287. class _XableAction(_AccessLinterObjectAction):
  288. """Callback action for enabling or disabling a message."""
  289. def _call(
  290. self,
  291. xabling_function: Callable[[str], None],
  292. values: str | Sequence[Any] | None,
  293. option_string: str | None,
  294. ) -> None:
  295. assert isinstance(values, (tuple, list))
  296. for msgid in utils._check_csv(values[0]):
  297. try:
  298. xabling_function(msgid)
  299. except (
  300. exceptions.DeletedMessageError,
  301. exceptions.MessageBecameExtensionError,
  302. ) as e:
  303. self.linter._stashed_messages[
  304. (self.linter.current_name, "useless-option-value")
  305. ].append((option_string, str(e)))
  306. except exceptions.UnknownMessageError:
  307. self.linter._stashed_messages[
  308. (self.linter.current_name, "unknown-option-value")
  309. ].append((option_string, msgid))
  310. @abc.abstractmethod
  311. def __call__(
  312. self,
  313. parser: argparse.ArgumentParser,
  314. namespace: argparse.Namespace,
  315. values: str | Sequence[Any] | None,
  316. option_string: str | None = "--disable",
  317. ) -> None:
  318. raise NotImplementedError # pragma: no cover
  319. class _DisableAction(_XableAction):
  320. """Callback action for disabling a message."""
  321. def __call__(
  322. self,
  323. parser: argparse.ArgumentParser,
  324. namespace: argparse.Namespace,
  325. values: str | Sequence[Any] | None,
  326. option_string: str | None = "--disable",
  327. ) -> None:
  328. self._call(self.linter.disable, values, option_string)
  329. class _EnableAction(_XableAction):
  330. """Callback action for enabling a message."""
  331. def __call__(
  332. self,
  333. parser: argparse.ArgumentParser,
  334. namespace: argparse.Namespace,
  335. values: str | Sequence[Any] | None,
  336. option_string: str | None = "--enable",
  337. ) -> None:
  338. self._call(self.linter.enable, values, option_string)
  339. class _OutputFormatAction(_AccessLinterObjectAction):
  340. """Callback action for setting the output format."""
  341. def __call__(
  342. self,
  343. parser: argparse.ArgumentParser,
  344. namespace: argparse.Namespace,
  345. values: str | Sequence[Any] | None,
  346. option_string: str | None = "--enable",
  347. ) -> None:
  348. assert isinstance(values, (tuple, list))
  349. assert isinstance(
  350. values[0], str
  351. ), "'output-format' should be a comma separated string of reporters"
  352. self.linter._load_reporters(values[0])
  353. class _AccessParserAction(_CallbackAction):
  354. """Action that has access to the ArgumentParser object."""
  355. def __init__(
  356. self,
  357. option_strings: Sequence[str],
  358. dest: str,
  359. nargs: None = None,
  360. const: None = None,
  361. default: None = None,
  362. type: None = None,
  363. choices: None = None,
  364. required: bool = False,
  365. help: str = "",
  366. metavar: str = "",
  367. **kwargs: argparse.ArgumentParser,
  368. ) -> None:
  369. self.parser = kwargs["parser"]
  370. super().__init__(
  371. option_strings,
  372. dest,
  373. 0,
  374. const,
  375. default,
  376. type,
  377. choices,
  378. required,
  379. help,
  380. metavar,
  381. )
  382. @abc.abstractmethod
  383. def __call__(
  384. self,
  385. parser: argparse.ArgumentParser,
  386. namespace: argparse.Namespace,
  387. values: str | Sequence[Any] | None,
  388. option_string: str | None = None,
  389. ) -> None:
  390. raise NotImplementedError # pragma: no cover