callback_actions.py 14 KB

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