arguments_manager.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  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. """Arguments manager class used to handle command-line arguments and options."""
  5. from __future__ import annotations
  6. import argparse
  7. import configparser
  8. import copy
  9. import optparse # pylint: disable=deprecated-module
  10. import os
  11. import re
  12. import sys
  13. import textwrap
  14. import warnings
  15. from collections import OrderedDict
  16. from collections.abc import Sequence
  17. from pathlib import Path
  18. from typing import TYPE_CHECKING, Any, TextIO, Union
  19. import tomlkit
  20. from pylint import utils
  21. from pylint.config.argument import (
  22. _Argument,
  23. _CallableArgument,
  24. _ExtendArgument,
  25. _StoreArgument,
  26. _StoreNewNamesArgument,
  27. _StoreOldNamesArgument,
  28. _StoreTrueArgument,
  29. )
  30. from pylint.config.exceptions import (
  31. UnrecognizedArgumentAction,
  32. _UnrecognizedOptionError,
  33. )
  34. from pylint.config.help_formatter import _HelpFormatter
  35. from pylint.config.option import Option
  36. from pylint.config.option_parser import OptionParser # type: ignore[attr-defined]
  37. from pylint.config.options_provider_mixin import ( # type: ignore[attr-defined]
  38. OptionsProviderMixIn,
  39. )
  40. from pylint.config.utils import _convert_option_to_argument, _parse_rich_type_value
  41. from pylint.constants import MAIN_CHECKER_NAME
  42. from pylint.typing import DirectoryNamespaceDict, OptionDict
  43. if sys.version_info >= (3, 11):
  44. import tomllib
  45. else:
  46. import tomli as tomllib
  47. if TYPE_CHECKING:
  48. from pylint.config.arguments_provider import _ArgumentsProvider
  49. ConfigProvider = Union["_ArgumentsProvider", OptionsProviderMixIn]
  50. # pylint: disable-next=too-many-instance-attributes
  51. class _ArgumentsManager:
  52. """Arguments manager class used to handle command-line arguments and options."""
  53. def __init__(
  54. self, prog: str, usage: str | None = None, description: str | None = None
  55. ) -> None:
  56. self._config = argparse.Namespace()
  57. """Namespace for all options."""
  58. self._base_config = self._config
  59. """Fall back Namespace object created during initialization.
  60. This is necessary for the per-directory configuration support. Whenever we
  61. fail to match a file with a directory we fall back to the Namespace object
  62. created during initialization.
  63. """
  64. self._arg_parser = argparse.ArgumentParser(
  65. prog=prog,
  66. usage=usage or "%(prog)s [options]",
  67. description=description,
  68. formatter_class=_HelpFormatter,
  69. # Needed to let 'pylint-config' overwrite the -h command
  70. conflict_handler="resolve",
  71. )
  72. """The command line argument parser."""
  73. self._argument_groups_dict: dict[str, argparse._ArgumentGroup] = {}
  74. """Dictionary of all the argument groups."""
  75. self._option_dicts: dict[str, OptionDict] = {}
  76. """All option dictionaries that have been registered."""
  77. self._directory_namespaces: DirectoryNamespaceDict = {}
  78. """Mapping of directories and their respective namespace objects."""
  79. # TODO: 3.0: Remove deprecated attributes introduced to keep API
  80. # parity with optparse. Until '_maxlevel'
  81. with warnings.catch_warnings():
  82. warnings.filterwarnings("ignore", category=DeprecationWarning)
  83. self.reset_parsers(usage or "")
  84. # list of registered options providers
  85. self._options_providers: list[ConfigProvider] = []
  86. # dictionary associating option name to checker
  87. self._all_options: OrderedDict[str, ConfigProvider] = OrderedDict()
  88. self._short_options: dict[str, str] = {}
  89. self._nocallback_options: dict[ConfigProvider, str] = {}
  90. self._mygroups: dict[str, optparse.OptionGroup] = {}
  91. # verbosity
  92. self._maxlevel: int = 0
  93. @property
  94. def config(self) -> argparse.Namespace:
  95. """Namespace for all options."""
  96. return self._config
  97. @config.setter
  98. def config(self, value: argparse.Namespace) -> None:
  99. self._config = value
  100. @property
  101. def options_providers(self) -> list[ConfigProvider]:
  102. # TODO: 3.0: Remove deprecated attribute.
  103. warnings.warn(
  104. "options_providers has been deprecated. It will be removed in pylint 3.0.",
  105. DeprecationWarning,
  106. stacklevel=2,
  107. )
  108. return self._options_providers
  109. @options_providers.setter
  110. def options_providers(self, value: list[ConfigProvider]) -> None:
  111. warnings.warn(
  112. "Setting options_providers has been deprecated. It will be removed in pylint 3.0.",
  113. DeprecationWarning,
  114. stacklevel=2,
  115. )
  116. self._options_providers = value
  117. def _register_options_provider(self, provider: _ArgumentsProvider) -> None:
  118. """Register an options provider and load its defaults."""
  119. for opt, optdict in provider.options:
  120. self._option_dicts[opt] = optdict
  121. argument = _convert_option_to_argument(opt, optdict)
  122. section = argument.section or provider.name.capitalize()
  123. section_desc = provider.option_groups_descs.get(section, None)
  124. # We exclude main since its docstring comes from PyLinter
  125. if provider.name != MAIN_CHECKER_NAME and provider.__doc__:
  126. section_desc = provider.__doc__.split("\n\n")[0]
  127. self._add_arguments_to_parser(section, section_desc, argument)
  128. self._load_default_argument_values()
  129. def _add_arguments_to_parser(
  130. self, section: str, section_desc: str | None, argument: _Argument
  131. ) -> None:
  132. """Add an argument to the correct argument section/group."""
  133. try:
  134. section_group = self._argument_groups_dict[section]
  135. except KeyError:
  136. if section_desc:
  137. section_group = self._arg_parser.add_argument_group(
  138. section, section_desc
  139. )
  140. else:
  141. section_group = self._arg_parser.add_argument_group(title=section)
  142. self._argument_groups_dict[section] = section_group
  143. self._add_parser_option(section_group, argument)
  144. @staticmethod
  145. def _add_parser_option(
  146. section_group: argparse._ArgumentGroup, argument: _Argument
  147. ) -> None:
  148. """Add an argument."""
  149. if isinstance(argument, _StoreArgument):
  150. section_group.add_argument(
  151. *argument.flags,
  152. action=argument.action,
  153. default=argument.default,
  154. type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed
  155. help=argument.help,
  156. metavar=argument.metavar,
  157. choices=argument.choices,
  158. )
  159. elif isinstance(argument, _StoreOldNamesArgument):
  160. section_group.add_argument(
  161. *argument.flags,
  162. **argument.kwargs,
  163. action=argument.action,
  164. default=argument.default,
  165. type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed
  166. help=argument.help,
  167. metavar=argument.metavar,
  168. choices=argument.choices,
  169. )
  170. # We add the old name as hidden option to make it's default value gets loaded when
  171. # argparse initializes all options from the checker
  172. assert argument.kwargs["old_names"]
  173. for old_name in argument.kwargs["old_names"]:
  174. section_group.add_argument(
  175. f"--{old_name}",
  176. action="store",
  177. default=argument.default,
  178. type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed
  179. help=argparse.SUPPRESS,
  180. metavar=argument.metavar,
  181. choices=argument.choices,
  182. )
  183. elif isinstance(argument, _StoreNewNamesArgument):
  184. section_group.add_argument(
  185. *argument.flags,
  186. **argument.kwargs,
  187. action=argument.action,
  188. default=argument.default,
  189. type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed
  190. help=argument.help,
  191. metavar=argument.metavar,
  192. choices=argument.choices,
  193. )
  194. elif isinstance(argument, _StoreTrueArgument):
  195. section_group.add_argument(
  196. *argument.flags,
  197. action=argument.action,
  198. default=argument.default,
  199. help=argument.help,
  200. )
  201. elif isinstance(argument, _CallableArgument):
  202. section_group.add_argument(
  203. *argument.flags,
  204. **argument.kwargs,
  205. action=argument.action,
  206. help=argument.help,
  207. metavar=argument.metavar,
  208. )
  209. elif isinstance(argument, _ExtendArgument):
  210. section_group.add_argument(
  211. *argument.flags,
  212. action=argument.action,
  213. default=argument.default,
  214. type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed
  215. help=argument.help,
  216. metavar=argument.metavar,
  217. choices=argument.choices,
  218. dest=argument.dest,
  219. )
  220. else:
  221. raise UnrecognizedArgumentAction
  222. def _load_default_argument_values(self) -> None:
  223. """Loads the default values of all registered options."""
  224. self.config = self._arg_parser.parse_args([], self.config)
  225. def _parse_configuration_file(self, arguments: list[str]) -> None:
  226. """Parse the arguments found in a configuration file into the namespace."""
  227. try:
  228. self.config, parsed_args = self._arg_parser.parse_known_args(
  229. arguments, self.config
  230. )
  231. except SystemExit:
  232. sys.exit(32)
  233. unrecognized_options: list[str] = []
  234. for opt in parsed_args:
  235. if opt.startswith("--"):
  236. unrecognized_options.append(opt[2:])
  237. if unrecognized_options:
  238. raise _UnrecognizedOptionError(options=unrecognized_options)
  239. def _parse_command_line_configuration(
  240. self, arguments: Sequence[str] | None = None
  241. ) -> list[str]:
  242. """Parse the arguments found on the command line into the namespace."""
  243. arguments = sys.argv[1:] if arguments is None else arguments
  244. self.config, parsed_args = self._arg_parser.parse_known_args(
  245. arguments, self.config
  246. )
  247. return parsed_args
  248. def reset_parsers(self, usage: str = "") -> None: # pragma: no cover
  249. """DEPRECATED."""
  250. warnings.warn(
  251. "reset_parsers has been deprecated. Parsers should be instantiated "
  252. "once during initialization and do not need to be reset.",
  253. DeprecationWarning,
  254. stacklevel=2,
  255. )
  256. # configuration file parser
  257. self.cfgfile_parser = configparser.ConfigParser(
  258. inline_comment_prefixes=("#", ";")
  259. )
  260. # command line parser
  261. self.cmdline_parser = OptionParser(Option, usage=usage)
  262. self.cmdline_parser.options_manager = self
  263. self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS)
  264. def register_options_provider(
  265. self, provider: ConfigProvider, own_group: bool = True
  266. ) -> None: # pragma: no cover
  267. """DEPRECATED: Register an options provider."""
  268. warnings.warn(
  269. "register_options_provider has been deprecated. Options providers and "
  270. "arguments providers should be registered by initializing ArgumentsProvider. "
  271. "This automatically registers the provider on the ArgumentsManager.",
  272. DeprecationWarning,
  273. stacklevel=2,
  274. )
  275. self.options_providers.append(provider)
  276. non_group_spec_options = [
  277. option for option in provider.options if "group" not in option[1]
  278. ]
  279. groups = getattr(provider, "option_groups", ())
  280. if own_group and non_group_spec_options:
  281. with warnings.catch_warnings():
  282. warnings.filterwarnings("ignore", category=DeprecationWarning)
  283. self.add_option_group(
  284. provider.name.upper(),
  285. provider.__doc__,
  286. non_group_spec_options,
  287. provider,
  288. )
  289. else:
  290. for opt, optdict in non_group_spec_options:
  291. with warnings.catch_warnings():
  292. warnings.filterwarnings("ignore", category=DeprecationWarning)
  293. self.add_optik_option(provider, self.cmdline_parser, opt, optdict)
  294. for gname, gdoc in groups:
  295. gname = gname.upper()
  296. goptions = [
  297. option
  298. for option in provider.options
  299. if option[1].get("group", "").upper() == gname # type: ignore[union-attr]
  300. ]
  301. with warnings.catch_warnings():
  302. warnings.filterwarnings("ignore", category=DeprecationWarning)
  303. self.add_option_group(gname, gdoc, goptions, provider)
  304. def add_option_group(
  305. self,
  306. group_name: str,
  307. _: str | None,
  308. options: list[tuple[str, OptionDict]],
  309. provider: ConfigProvider,
  310. ) -> None: # pragma: no cover
  311. """DEPRECATED."""
  312. warnings.warn(
  313. "add_option_group has been deprecated. Option groups should be "
  314. "registered by initializing ArgumentsProvider. "
  315. "This automatically registers the group on the ArgumentsManager.",
  316. DeprecationWarning,
  317. stacklevel=2,
  318. )
  319. # add option group to the command line parser
  320. if group_name in self._mygroups:
  321. group = self._mygroups[group_name]
  322. else:
  323. group = optparse.OptionGroup(
  324. self.cmdline_parser, title=group_name.capitalize()
  325. )
  326. self.cmdline_parser.add_option_group(group)
  327. self._mygroups[group_name] = group
  328. # add section to the config file
  329. if (
  330. group_name != "DEFAULT"
  331. and group_name not in self.cfgfile_parser._sections # type: ignore[attr-defined]
  332. ):
  333. self.cfgfile_parser.add_section(group_name)
  334. # add provider's specific options
  335. for opt, optdict in options:
  336. if not isinstance(optdict.get("action", "store"), str):
  337. optdict["action"] = "callback"
  338. with warnings.catch_warnings():
  339. warnings.filterwarnings("ignore", category=DeprecationWarning)
  340. self.add_optik_option(provider, group, opt, optdict)
  341. def add_optik_option(
  342. self,
  343. provider: ConfigProvider,
  344. optikcontainer: optparse.OptionParser | optparse.OptionGroup,
  345. opt: str,
  346. optdict: OptionDict,
  347. ) -> None: # pragma: no cover
  348. """DEPRECATED."""
  349. warnings.warn(
  350. "add_optik_option has been deprecated. Options should be automatically "
  351. "added by initializing an ArgumentsProvider.",
  352. DeprecationWarning,
  353. stacklevel=2,
  354. )
  355. with warnings.catch_warnings():
  356. warnings.filterwarnings("ignore", category=DeprecationWarning)
  357. args, optdict = self.optik_option(provider, opt, optdict)
  358. option = optikcontainer.add_option(*args, **optdict)
  359. self._all_options[opt] = provider
  360. self._maxlevel = max(self._maxlevel, option.level or 0)
  361. def optik_option(
  362. self, provider: ConfigProvider, opt: str, optdict: OptionDict
  363. ) -> tuple[list[str], OptionDict]: # pragma: no cover
  364. """DEPRECATED: Get our personal option definition and return a suitable form for
  365. use with optik/optparse.
  366. """
  367. warnings.warn(
  368. "optik_option has been deprecated. Parsing of option dictionaries should be done "
  369. "automatically by initializing an ArgumentsProvider.",
  370. DeprecationWarning,
  371. stacklevel=2,
  372. )
  373. optdict = copy.copy(optdict)
  374. if "action" in optdict:
  375. self._nocallback_options[provider] = opt
  376. else:
  377. optdict["action"] = "callback"
  378. optdict["callback"] = self.cb_set_provider_option
  379. # default is handled here and *must not* be given to optik if you
  380. # want the whole machinery to work
  381. if "default" in optdict:
  382. if (
  383. "help" in optdict
  384. and optdict.get("default") is not None
  385. and optdict["action"] not in ("store_true", "store_false")
  386. ):
  387. optdict["help"] += " [current: %default]" # type: ignore[operator]
  388. del optdict["default"]
  389. args = ["--" + str(opt)]
  390. if "short" in optdict:
  391. self._short_options[optdict["short"]] = opt # type: ignore[index]
  392. args.append("-" + optdict["short"]) # type: ignore[operator]
  393. del optdict["short"]
  394. # cleanup option definition dict before giving it to optik
  395. for key in list(optdict.keys()):
  396. if key not in self._optik_option_attrs:
  397. optdict.pop(key)
  398. return args, optdict
  399. def generate_config(
  400. self, stream: TextIO | None = None, skipsections: tuple[str, ...] = ()
  401. ) -> None: # pragma: no cover
  402. """DEPRECATED: Write a configuration file according to the current configuration
  403. into the given stream or stdout.
  404. """
  405. warnings.warn(
  406. "generate_config has been deprecated. It will be removed in pylint 3.0.",
  407. DeprecationWarning,
  408. stacklevel=2,
  409. )
  410. options_by_section = {}
  411. sections = []
  412. for group in sorted(
  413. self._arg_parser._action_groups,
  414. key=lambda x: (x.title != "Main", x.title),
  415. ):
  416. group_name = group.title
  417. assert group_name
  418. if group_name in skipsections:
  419. continue
  420. options = []
  421. option_actions = [
  422. i
  423. for i in group._group_actions
  424. if not isinstance(i, argparse._SubParsersAction)
  425. ]
  426. for opt in sorted(option_actions, key=lambda x: x.option_strings[0][2:]):
  427. if "--help" in opt.option_strings:
  428. continue
  429. optname = opt.option_strings[0][2:]
  430. try:
  431. optdict = self._option_dicts[optname]
  432. except KeyError:
  433. continue
  434. options.append(
  435. (
  436. optname,
  437. optdict,
  438. getattr(self.config, optname.replace("-", "_")),
  439. )
  440. )
  441. options = [
  442. (n, d, v) for (n, d, v) in options if not d.get("deprecated")
  443. ]
  444. if options:
  445. sections.append(group_name)
  446. options_by_section[group_name] = options
  447. stream = stream or sys.stdout
  448. printed = False
  449. for section in sections:
  450. if printed:
  451. print("\n", file=stream)
  452. with warnings.catch_warnings():
  453. warnings.filterwarnings("ignore", category=DeprecationWarning)
  454. utils.format_section(
  455. stream, section.upper(), sorted(options_by_section[section])
  456. )
  457. printed = True
  458. def load_provider_defaults(self) -> None: # pragma: no cover
  459. """DEPRECATED: Initialize configuration using default values."""
  460. warnings.warn(
  461. "load_provider_defaults has been deprecated. Parsing of option defaults should be done "
  462. "automatically by initializing an ArgumentsProvider.",
  463. DeprecationWarning,
  464. stacklevel=2,
  465. )
  466. for provider in self.options_providers:
  467. with warnings.catch_warnings():
  468. warnings.filterwarnings("ignore", category=DeprecationWarning)
  469. provider.load_defaults()
  470. def read_config_file(
  471. self, config_file: Path | None = None, verbose: bool = False
  472. ) -> None: # pragma: no cover
  473. """DEPRECATED: Read the configuration file but do not load it (i.e. dispatching
  474. values to each option's provider).
  475. :raises OSError: When the specified config file doesn't exist
  476. """
  477. warnings.warn(
  478. "read_config_file has been deprecated. It will be removed in pylint 3.0.",
  479. DeprecationWarning,
  480. stacklevel=2,
  481. )
  482. if not config_file:
  483. if verbose:
  484. print(
  485. "No config file found, using default configuration", file=sys.stderr
  486. )
  487. return
  488. config_file = Path(os.path.expandvars(config_file)).expanduser()
  489. if not config_file.exists():
  490. raise OSError(f"The config file {str(config_file)} doesn't exist!")
  491. parser = self.cfgfile_parser
  492. if config_file.suffix == ".toml":
  493. try:
  494. self._parse_toml(config_file, parser)
  495. except tomllib.TOMLDecodeError:
  496. pass
  497. else:
  498. # Use this encoding in order to strip the BOM marker, if any.
  499. with open(config_file, encoding="utf_8_sig") as fp:
  500. parser.read_file(fp)
  501. # normalize each section's title
  502. for sect, values in list(parser._sections.items()): # type: ignore[attr-defined]
  503. if sect.startswith("pylint."):
  504. sect = sect[len("pylint.") :]
  505. if not sect.isupper() and values:
  506. parser._sections[sect.upper()] = values # type: ignore[attr-defined]
  507. if verbose:
  508. print(f"Using config file '{config_file}'", file=sys.stderr)
  509. @staticmethod
  510. def _parse_toml(
  511. config_file: Path, parser: configparser.ConfigParser
  512. ) -> None: # pragma: no cover
  513. """DEPRECATED: Parse and handle errors of a toml configuration file.
  514. TODO: 3.0: Remove deprecated method.
  515. """
  516. with open(config_file, mode="rb") as fp:
  517. content = tomllib.load(fp)
  518. try:
  519. sections_values = content["tool"]["pylint"]
  520. except KeyError:
  521. return
  522. for section, values in sections_values.items():
  523. section_name = section.upper()
  524. # TOML has rich types, convert values to
  525. # strings as ConfigParser expects.
  526. if not isinstance(values, dict):
  527. continue
  528. for option, value in values.items():
  529. if isinstance(value, bool):
  530. values[option] = "yes" if value else "no"
  531. elif isinstance(value, list):
  532. values[option] = ",".join(value)
  533. else:
  534. values[option] = str(value)
  535. for option, value in values.items():
  536. try:
  537. parser.set(section_name, option, value=value)
  538. except configparser.NoSectionError:
  539. parser.add_section(section_name)
  540. parser.set(section_name, option, value=value)
  541. def load_config_file(self) -> None: # pragma: no cover
  542. """DEPRECATED: Dispatch values previously read from a configuration file to each
  543. option's provider.
  544. """
  545. warnings.warn(
  546. "load_config_file has been deprecated. It will be removed in pylint 3.0.",
  547. DeprecationWarning,
  548. stacklevel=2,
  549. )
  550. parser = self.cfgfile_parser
  551. for section in parser.sections():
  552. for option, value in parser.items(section):
  553. try:
  554. self.global_set_option(option, value)
  555. except (KeyError, optparse.OptionError):
  556. continue
  557. def load_configuration(self, **kwargs: Any) -> None: # pragma: no cover
  558. """DEPRECATED: Override configuration according to given parameters."""
  559. warnings.warn(
  560. "load_configuration has been deprecated. It will be removed in pylint 3.0.",
  561. DeprecationWarning,
  562. stacklevel=2,
  563. )
  564. with warnings.catch_warnings():
  565. warnings.filterwarnings("ignore", category=DeprecationWarning)
  566. return self.load_configuration_from_config(kwargs)
  567. def load_configuration_from_config(
  568. self, config: dict[str, Any]
  569. ) -> None: # pragma: no cover
  570. warnings.warn(
  571. "DEPRECATED: load_configuration_from_config has been deprecated. It will be removed in pylint 3.0.",
  572. DeprecationWarning,
  573. stacklevel=2,
  574. )
  575. for opt, opt_value in config.items():
  576. opt = opt.replace("_", "-")
  577. provider = self._all_options[opt]
  578. provider.set_option(opt, opt_value)
  579. def load_command_line_configuration(
  580. self, args: list[str] | None = None
  581. ) -> list[str]: # pragma: no cover
  582. """DEPRECATED: Override configuration according to command line parameters.
  583. return additional arguments
  584. """
  585. warnings.warn(
  586. "load_command_line_configuration has been deprecated. It will be removed in pylint 3.0.",
  587. DeprecationWarning,
  588. stacklevel=2,
  589. )
  590. args = sys.argv[1:] if args is None else list(args)
  591. (options, args) = self.cmdline_parser.parse_args(args=args)
  592. for provider in self._nocallback_options:
  593. config = provider.config
  594. for attr in config.__dict__.keys():
  595. value = getattr(options, attr, None)
  596. if value is None:
  597. continue
  598. setattr(config, attr, value)
  599. return args # type: ignore[return-value]
  600. def help(self, level: int | None = None) -> str:
  601. """Return the usage string based on the available options."""
  602. if level is not None:
  603. warnings.warn(
  604. "Supplying a 'level' argument to help() has been deprecated."
  605. "You can call help() without any arguments.",
  606. DeprecationWarning,
  607. stacklevel=2,
  608. )
  609. return self._arg_parser.format_help()
  610. def cb_set_provider_option( # pragma: no cover
  611. self, option: Any, opt: Any, value: Any, parser: Any
  612. ) -> None:
  613. """DEPRECATED: Optik callback for option setting."""
  614. # TODO: 3.0: Remove deprecated method.
  615. warnings.warn(
  616. "cb_set_provider_option has been deprecated. It will be removed in pylint 3.0.",
  617. DeprecationWarning,
  618. stacklevel=2,
  619. )
  620. if opt.startswith("--"):
  621. # remove -- on long option
  622. opt = opt[2:]
  623. else:
  624. # short option, get its long equivalent
  625. opt = self._short_options[opt[1:]]
  626. # trick since we can't set action='store_true' on options
  627. if value is None:
  628. value = 1
  629. self.set_option(opt, value)
  630. def global_set_option(self, opt: str, value: Any) -> None: # pragma: no cover
  631. """DEPRECATED: Set option on the correct option provider."""
  632. # TODO: 3.0: Remove deprecated method.
  633. warnings.warn(
  634. "global_set_option has been deprecated. You can use _arguments_manager.set_option "
  635. "or linter.set_option to set options on the global configuration object.",
  636. DeprecationWarning,
  637. stacklevel=2,
  638. )
  639. self.set_option(opt, value)
  640. def _generate_config_file(self, *, minimal: bool = False) -> str:
  641. """Write a configuration file according to the current configuration into
  642. stdout.
  643. """
  644. toml_doc = tomlkit.document()
  645. tool_table = tomlkit.table(is_super_table=True)
  646. toml_doc.add(tomlkit.key("tool"), tool_table)
  647. pylint_tool_table = tomlkit.table(is_super_table=True)
  648. tool_table.add(tomlkit.key("pylint"), pylint_tool_table)
  649. for group in sorted(
  650. self._arg_parser._action_groups,
  651. key=lambda x: (x.title != "Main", x.title),
  652. ):
  653. # Skip the options section with the --help option
  654. if group.title in {"options", "optional arguments", "Commands"}:
  655. continue
  656. # Skip sections without options such as "positional arguments"
  657. if not group._group_actions:
  658. continue
  659. group_table = tomlkit.table()
  660. option_actions = [
  661. i
  662. for i in group._group_actions
  663. if not isinstance(i, argparse._SubParsersAction)
  664. ]
  665. for action in sorted(option_actions, key=lambda x: x.option_strings[0][2:]):
  666. optname = action.option_strings[0][2:]
  667. # We skip old name options that don't have their own optdict
  668. try:
  669. optdict = self._option_dicts[optname]
  670. except KeyError:
  671. continue
  672. if optdict.get("hide_from_config_file"):
  673. continue
  674. # Add help comment
  675. if not minimal:
  676. help_msg = optdict.get("help", "")
  677. assert isinstance(help_msg, str)
  678. help_text = textwrap.wrap(help_msg, width=79)
  679. for line in help_text:
  680. group_table.add(tomlkit.comment(line))
  681. # Get current value of option
  682. value = getattr(self.config, optname.replace("-", "_"))
  683. # Create a comment if the option has no value
  684. if not value:
  685. if not minimal:
  686. group_table.add(tomlkit.comment(f"{optname} ="))
  687. group_table.add(tomlkit.nl())
  688. continue
  689. # Skip deprecated options
  690. if "kwargs" in optdict:
  691. assert isinstance(optdict["kwargs"], dict)
  692. if "new_names" in optdict["kwargs"]:
  693. continue
  694. # Tomlkit doesn't support regular expressions
  695. if isinstance(value, re.Pattern):
  696. value = value.pattern
  697. elif isinstance(value, (list, tuple)) and isinstance(
  698. value[0], re.Pattern
  699. ):
  700. value = [i.pattern for i in value]
  701. # Handle tuples that should be strings
  702. if optdict.get("type") == "py_version":
  703. value = ".".join(str(i) for i in value)
  704. # Check if it is default value if we are in minimal mode
  705. if minimal and value == optdict.get("default"):
  706. continue
  707. # Add to table
  708. group_table.add(optname, value)
  709. group_table.add(tomlkit.nl())
  710. assert group.title
  711. if group_table:
  712. pylint_tool_table.add(group.title.lower(), group_table)
  713. toml_string = tomlkit.dumps(toml_doc)
  714. # Make sure the string we produce is valid toml and can be parsed
  715. tomllib.loads(toml_string)
  716. return str(toml_string)
  717. def set_option(
  718. self,
  719. optname: str,
  720. value: Any,
  721. action: str | None = "default_value",
  722. optdict: None | str | OptionDict = "default_value",
  723. ) -> None:
  724. """Set an option on the namespace object."""
  725. # TODO: 3.0: Remove deprecated arguments.
  726. if action != "default_value":
  727. warnings.warn(
  728. "The 'action' argument has been deprecated. You can use set_option "
  729. "without the 'action' or 'optdict' arguments.",
  730. DeprecationWarning,
  731. stacklevel=2,
  732. )
  733. if optdict != "default_value":
  734. warnings.warn(
  735. "The 'optdict' argument has been deprecated. You can use set_option "
  736. "without the 'action' or 'optdict' arguments.",
  737. DeprecationWarning,
  738. stacklevel=2,
  739. )
  740. self.config = self._arg_parser.parse_known_args(
  741. [f"--{optname.replace('_', '-')}", _parse_rich_type_value(value)],
  742. self.config,
  743. )[0]