# 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 """Callback actions for various options.""" import abc import argparse import sys import warnings from pathlib import Path from typing import TYPE_CHECKING, Any, Optional, Sequence, Union 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: Union[str, Sequence[Any], None], option_string: Optional[str] = None, ) -> None: raise NotImplementedError 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: Union[str, Sequence[Any], None], option_string: Optional[str] = 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: Union[str, Sequence[Any], None], option_string: Optional[str] = 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: Union[str, Sequence[Any], None], option_string: Optional[str] = None, ) -> None: raise NotImplementedError 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: Union[str, Sequence[str], None], option_string: Optional[str] = "--help-msg", ) -> None: assert isinstance(values, (list, tuple)) self.run.linter.msgs_store.help_message(values) sys.exit(0) class _ListMessagesAction(_AccessRunObjectAction): """Display all available messages.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: Union[str, Sequence[Any], None], option_string: Optional[str] = "--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: Union[str, Sequence[Any], None], option_string: Optional[str] = "--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: Union[str, Sequence[Any], None], option_string: Optional[str] = "--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: Union[str, Sequence[Any], None], option_string: Optional[str] = "--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: Union[str, Sequence[Any], None], option_string: Optional[str] = "--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: Union[str, Sequence[Any], None], option_string: Optional[str] = "--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: Union[str, Sequence[Any], None], option_string: Optional[str] = "--generate-rcfile", ) -> None: # pylint: disable-next=fixme # TODO: Optparse: Deprecate this after discussion about this removal has been completed. 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: Union[str, Sequence[Any], None], option_string: Optional[str] = "--generate-toml-config", ) -> None: 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: Union[str, Sequence[Any], None], option_string: Optional[str] = "--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: Union[str, Sequence[Any], None], option_string: Optional[str] = "--long-help", ) -> None: formatter: "_HelpFormatter" = self.run.linter._arg_parser._get_formatter() # 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: Union[str, Sequence[Any], None], option_string: Optional[str] = None, ) -> None: raise NotImplementedError class _DisableAction(_AccessLinterObjectAction): """Callback action for disabling a message.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: Union[str, Sequence[Any], None], option_string: Optional[str] = "--disable", ) -> None: assert isinstance(values, (tuple, list)) msgids = utils._check_csv(values[0]) try: for msgid in msgids: self.linter.disable(msgid) except exceptions.UnknownMessageError: # pylint: disable-next=fixme # TODO: Optparse: Raise an informational warning here pass class _EnableAction(_AccessLinterObjectAction): """Callback action for enabling a message.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: Union[str, Sequence[Any], None], option_string: Optional[str] = "--enable", ) -> None: assert isinstance(values, (tuple, list)) msgids = utils._check_csv(values[0]) try: for msgid in msgids: self.linter.enable(msgid) except exceptions.UnknownMessageError: # pylint: disable-next=fixme # TODO: Optparse: Raise an informational warning here pass class _OutputFormatAction(_AccessLinterObjectAction): """Callback action for setting the output format.""" def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: Union[str, Sequence[Any], None], option_string: Optional[str] = "--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])