# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE # Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt # pylint: disable=too-many-arguments, redefined-builtin, duplicate-code """Callback actions for various options.""" from __future__ import annotations import abc import argparse import sys import warnings from collections.abc import Callable, Sequence from pathlib import Path from typing import TYPE_CHECKING, Any from pylint import exceptions, extensions, interfaces, utils if TYPE_CHECKING: from pylint.config.help_formatter import _HelpFormatter from pylint.lint import PyLinter from pylint.lint.run import Run class _CallbackAction(argparse.Action): """Custom callback action.""" @abc.abstractmethod def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = None, ) -> None: raise NotImplementedError # pragma: no cover class _DoNothingAction(_CallbackAction): """Action that just passes. This action is used to allow pre-processing of certain options without erroring when they are then processed again by argparse. """ def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = None, ) -> None: return None class _ExtendAction(argparse._AppendAction): """Action that adds the value to a pre-existing list. It is directly copied from the stdlib implementation which is only available on 3.8+. """ def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = None, ) -> None: assert isinstance(values, (tuple, list)) current = getattr(namespace, self.dest, []) assert isinstance(current, list) current.extend(values) setattr(namespace, self.dest, current) class _AccessRunObjectAction(_CallbackAction): """Action that has access to the Run object.""" def __init__( self, option_strings: Sequence[str], dest: str, nargs: None = None, const: None = None, default: None = None, type: None = None, choices: None = None, required: bool = False, help: str = "", metavar: str = "", **kwargs: Run, ) -> None: self.run = kwargs["Run"] super().__init__( option_strings, dest, 0, const, default, type, choices, required, help, metavar, ) @abc.abstractmethod def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = None, ) -> None: raise NotImplementedError # pragma: no cover class _MessageHelpAction(_CallbackAction): """Display the help message of a message.""" def __init__( self, option_strings: Sequence[str], dest: str, nargs: None = None, const: None = None, default: None = None, type: None = None, choices: None = None, required: bool = False, help: str = "", metavar: str = "", **kwargs: Run, ) -> None: self.run = kwargs["Run"] super().__init__( option_strings, dest, "+", const, default, type, choices, required, help, metavar, ) def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[str] | None, option_string: str | None = "--help-msg", ) -> None: assert isinstance(values, (list, tuple)) values_to_print: list[str] = [] for msg in values: assert isinstance(msg, str) values_to_print += utils._check_csv(msg) self.run.linter.msgs_store.help_message(values_to_print) sys.exit(0) class _ListMessagesAction(_AccessRunObjectAction): """Display all available messages.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--list-enabled", ) -> None: self.run.linter.msgs_store.list_messages() sys.exit(0) class _ListMessagesEnabledAction(_AccessRunObjectAction): """Display all enabled messages.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--list-msgs-enabled", ) -> None: self.run.linter.list_messages_enabled() sys.exit(0) class _ListCheckGroupsAction(_AccessRunObjectAction): """Display all the check groups that pylint knows about.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--list-groups", ) -> None: for check in self.run.linter.get_checker_names(): print(check) sys.exit(0) class _ListConfidenceLevelsAction(_AccessRunObjectAction): """Display all the confidence levels that pylint knows about.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--list-conf-levels", ) -> None: for level in interfaces.CONFIDENCE_LEVELS: print(f"%-18s: {level}") sys.exit(0) class _ListExtensionsAction(_AccessRunObjectAction): """Display all extensions under pylint.extensions.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--list-extensions", ) -> None: for filename in Path(extensions.__file__).parent.iterdir(): if filename.suffix == ".py" and not filename.stem.startswith("_"): extension_name, _, _ = filename.stem.partition(".") print(f"pylint.extensions.{extension_name}") sys.exit(0) class _FullDocumentationAction(_AccessRunObjectAction): """Display the full documentation.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--full-documentation", ) -> None: utils.print_full_documentation(self.run.linter) sys.exit(0) class _GenerateRCFileAction(_AccessRunObjectAction): """Generate a pylintrc file.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--generate-rcfile", ) -> None: # TODO: 2.x: Deprecate this after the auto-upgrade functionality of # pylint-config is sufficient. with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=DeprecationWarning) self.run.linter.generate_config(skipsections=("Commands",)) sys.exit(0) class _GenerateConfigFileAction(_AccessRunObjectAction): """Generate a .toml format configuration file.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--generate-toml-config", ) -> None: print(self.run.linter._generate_config_file()) sys.exit(0) class _ErrorsOnlyModeAction(_AccessRunObjectAction): """Turn on errors-only mode. Error mode: * disable all but error messages * disable the 'miscellaneous' checker which can be safely deactivated in debug * disable reports * do not save execution information """ def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--errors-only", ) -> None: self.run.linter._error_mode = True class _LongHelpAction(_AccessRunObjectAction): """Display the long help message.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--long-help", ) -> None: formatter: _HelpFormatter = self.run.linter._arg_parser._get_formatter() # type: ignore[assignment] # Add extra info as epilog to the help message self.run.linter._arg_parser.epilog = formatter.get_long_description() print(self.run.linter.help()) sys.exit(0) class _AccessLinterObjectAction(_CallbackAction): """Action that has access to the Linter object.""" def __init__( self, option_strings: Sequence[str], dest: str, nargs: None = None, const: None = None, default: None = None, type: None = None, choices: None = None, required: bool = False, help: str = "", metavar: str = "", **kwargs: PyLinter, ) -> None: self.linter = kwargs["linter"] super().__init__( option_strings, dest, 1, const, default, type, choices, required, help, metavar, ) @abc.abstractmethod def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = None, ) -> None: raise NotImplementedError # pragma: no cover class _XableAction(_AccessLinterObjectAction): """Callback action for enabling or disabling a message.""" def _call( self, xabling_function: Callable[[str], None], values: str | Sequence[Any] | None, option_string: str | None, ) -> None: assert isinstance(values, (tuple, list)) for msgid in utils._check_csv(values[0]): try: xabling_function(msgid) except ( exceptions.DeletedMessageError, exceptions.MessageBecameExtensionError, ) as e: self.linter._stashed_messages[ (self.linter.current_name, "useless-option-value") ].append((option_string, str(e))) except exceptions.UnknownMessageError: self.linter._stashed_messages[ (self.linter.current_name, "unknown-option-value") ].append((option_string, msgid)) @abc.abstractmethod def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--disable", ) -> None: raise NotImplementedError # pragma: no cover class _DisableAction(_XableAction): """Callback action for disabling a message.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--disable", ) -> None: self._call(self.linter.disable, values, option_string) class _EnableAction(_XableAction): """Callback action for enabling a message.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--enable", ) -> None: self._call(self.linter.enable, values, option_string) class _OutputFormatAction(_AccessLinterObjectAction): """Callback action for setting the output format.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = "--enable", ) -> None: assert isinstance(values, (tuple, list)) assert isinstance( values[0], str ), "'output-format' should be a comma separated string of reporters" self.linter._load_reporters(values[0]) class _AccessParserAction(_CallbackAction): """Action that has access to the ArgumentParser object.""" def __init__( self, option_strings: Sequence[str], dest: str, nargs: None = None, const: None = None, default: None = None, type: None = None, choices: None = None, required: bool = False, help: str = "", metavar: str = "", **kwargs: argparse.ArgumentParser, ) -> None: self.parser = kwargs["parser"] super().__init__( option_strings, dest, 0, const, default, type, choices, required, help, metavar, ) @abc.abstractmethod def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = None, ) -> None: raise NotImplementedError # pragma: no cover