config_initialization.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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. from __future__ import annotations
  5. import sys
  6. import warnings
  7. from glob import glob
  8. from itertools import chain
  9. from pathlib import Path
  10. from typing import TYPE_CHECKING
  11. from pylint import reporters
  12. from pylint.config.config_file_parser import _ConfigurationFileParser
  13. from pylint.config.exceptions import (
  14. ArgumentPreprocessingError,
  15. _UnrecognizedOptionError,
  16. )
  17. from pylint.utils import utils
  18. if TYPE_CHECKING:
  19. from pylint.lint import PyLinter
  20. def _config_initialization( # pylint: disable=too-many-statements
  21. linter: PyLinter,
  22. args_list: list[str],
  23. reporter: reporters.BaseReporter | reporters.MultiReporter | None = None,
  24. config_file: None | str | Path = None,
  25. verbose_mode: bool = False,
  26. ) -> list[str]:
  27. """Parse all available options, read config files and command line arguments and
  28. set options accordingly.
  29. """
  30. linter.verbose = verbose_mode
  31. config_file = Path(config_file) if config_file else None
  32. # Set the current module to the configuration file
  33. # to allow raising messages on the configuration file.
  34. linter.set_current_module(str(config_file) if config_file else "")
  35. # Read the configuration file
  36. config_file_parser = _ConfigurationFileParser(verbose_mode, linter)
  37. try:
  38. config_data, config_args = config_file_parser.parse_config_file(
  39. file_path=config_file
  40. )
  41. except OSError as ex:
  42. print(ex, file=sys.stderr)
  43. sys.exit(32)
  44. # Order --enable=all or --disable=all to come first.
  45. config_args = _order_all_first(config_args, joined=False)
  46. # Run init hook, if present, before loading plugins
  47. if "init-hook" in config_data:
  48. exec(utils._unquote(config_data["init-hook"])) # pylint: disable=exec-used
  49. # Load plugins if specified in the config file
  50. if "load-plugins" in config_data:
  51. linter.load_plugin_modules(utils._splitstrip(config_data["load-plugins"]))
  52. unrecognized_options_message = None
  53. # First we parse any options from a configuration file
  54. try:
  55. linter._parse_configuration_file(config_args)
  56. except _UnrecognizedOptionError as exc:
  57. unrecognized_options_message = ", ".join(exc.options)
  58. # Then, if a custom reporter is provided as argument, it may be overridden
  59. # by file parameters, so we re-set it here. We do this before command line
  60. # parsing, so it's still overridable by command line options
  61. if reporter:
  62. linter.set_reporter(reporter)
  63. # Set the current module to the command line
  64. # to allow raising messages on it
  65. linter.set_current_module("Command line")
  66. # Now we parse any options from the command line, so they can override
  67. # the configuration file
  68. args_list = _order_all_first(args_list, joined=True)
  69. parsed_args_list = linter._parse_command_line_configuration(args_list)
  70. # Remove the positional arguments separator from the list of arguments if it exists
  71. try:
  72. parsed_args_list.remove("--")
  73. except ValueError:
  74. pass
  75. # Check if there are any options that we do not recognize
  76. unrecognized_options: list[str] = []
  77. for opt in parsed_args_list:
  78. if opt.startswith("--"):
  79. unrecognized_options.append(opt[2:])
  80. elif opt.startswith("-"):
  81. unrecognized_options.append(opt[1:])
  82. if unrecognized_options:
  83. msg = ", ".join(unrecognized_options)
  84. try:
  85. linter._arg_parser.error(f"Unrecognized option found: {msg}")
  86. except SystemExit:
  87. sys.exit(32)
  88. # Now that config file and command line options have been loaded
  89. # with all disables, it is safe to emit messages
  90. if unrecognized_options_message is not None:
  91. linter.set_current_module(str(config_file) if config_file else "")
  92. linter.add_message(
  93. "unrecognized-option", args=unrecognized_options_message, line=0
  94. )
  95. # TODO: Change this to be checked only when upgrading the configuration
  96. for exc_name in linter.config.overgeneral_exceptions:
  97. if "." not in exc_name:
  98. warnings.warn_explicit(
  99. f"'{exc_name}' is not a proper value for the 'overgeneral-exceptions' option. "
  100. f"Use fully qualified name (maybe 'builtins.{exc_name}' ?) instead. "
  101. "This will cease to be checked at runtime when the configuration "
  102. "upgrader is released.",
  103. category=UserWarning,
  104. filename="pylint: Command line or configuration file",
  105. lineno=1,
  106. module="pylint",
  107. )
  108. linter._emit_stashed_messages()
  109. # Set the current module to configuration as we don't know where
  110. # the --load-plugins key is coming from
  111. linter.set_current_module("Command line or configuration file")
  112. # We have loaded configuration from config file and command line. Now, we can
  113. # load plugin specific configuration.
  114. linter.load_plugin_configuration()
  115. # Now that plugins are loaded, get list of all fail_on messages, and
  116. # enable them
  117. linter.enable_fail_on_messages()
  118. # Now that fail_on messages are enabled, pass them to colorized reporter
  119. linter.pass_fail_on_config_to_color_reporter()
  120. linter._parse_error_mode()
  121. # Link the base Namespace object on the current directory
  122. linter._directory_namespaces[Path().resolve()] = (linter.config, {})
  123. # parsed_args_list should now only be a list of inputs to lint.
  124. # All other options have been removed from the list.
  125. return list(
  126. chain.from_iterable(
  127. # NOTE: 'or [arg]' is needed in the case the input file or directory does
  128. # not exist and 'glob(arg)' cannot find anything. Without this we would
  129. # not be able to output the fatal import error for this module later on,
  130. # as it would get silently ignored.
  131. glob(arg, recursive=True) or [arg]
  132. for arg in parsed_args_list
  133. )
  134. )
  135. def _order_all_first(config_args: list[str], *, joined: bool) -> list[str]:
  136. """Reorder config_args such that --enable=all or --disable=all comes first.
  137. Raise if both are given.
  138. If joined is True, expect args in the form '--enable=all,for-any-all'.
  139. If joined is False, expect args in the form '--enable', 'all,for-any-all'.
  140. """
  141. indexes_to_prepend = []
  142. all_action = ""
  143. for i, arg in enumerate(config_args):
  144. if joined and arg.startswith(("--enable=", "--disable=")):
  145. value = arg.split("=")[1]
  146. elif arg in {"--enable", "--disable"}:
  147. value = config_args[i + 1]
  148. else:
  149. continue
  150. if "all" not in (msg.strip() for msg in value.split(",")):
  151. continue
  152. arg = arg.split("=")[0]
  153. if all_action and (arg != all_action):
  154. raise ArgumentPreprocessingError(
  155. "--enable=all and --disable=all are incompatible."
  156. )
  157. all_action = arg
  158. indexes_to_prepend.append(i)
  159. if not joined:
  160. indexes_to_prepend.append(i + 1)
  161. returned_args = []
  162. for i in indexes_to_prepend:
  163. returned_args.append(config_args[i])
  164. for i, arg in enumerate(config_args):
  165. if i in indexes_to_prepend:
  166. continue
  167. returned_args.append(arg)
  168. return returned_args