# 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 import collections import contextlib import functools import operator import os import sys import tokenize import traceback import warnings from io import TextIOWrapper from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence, Union import astroid from astroid import AstroidError, nodes from pylint import checkers, config, exceptions, interfaces, reporters from pylint.constants import ( MAIN_CHECKER_NAME, MSG_STATE_CONFIDENCE, MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, MSG_TYPES, MSG_TYPES_LONG, MSG_TYPES_STATUS, ) from pylint.lint.expand_modules import expand_modules from pylint.lint.parallel import check_parallel from pylint.lint.report_functions import ( report_messages_by_module_stats, report_messages_stats, report_total_messages_stats, ) from pylint.lint.utils import ( fix_import_path, get_fatal_error_message, prepare_crash_report, ) from pylint.message import Message, MessageDefinition, MessageDefinitionStore from pylint.reporters.ureports import nodes as report_nodes from pylint.typing import ( FileItem, ManagedMessage, MessageLocationTuple, ModuleDescriptionDict, ) from pylint.utils import ASTWalker, FileState, LinterStats, get_global_option, utils from pylint.utils.pragma_parser import ( OPTION_PO, InvalidPragmaError, UnRecognizedOptionError, parse_pragma, ) if sys.version_info >= (3, 8): from typing import Literal else: from typing_extensions import Literal MANAGER = astroid.MANAGER def _read_stdin(): # https://mail.python.org/pipermail/python-list/2012-November/634424.html sys.stdin = TextIOWrapper(sys.stdin.detach(), encoding="utf-8") return sys.stdin.read() def _load_reporter_by_class(reporter_class: str) -> type: qname = reporter_class module_part = astroid.modutils.get_module_part(qname) module = astroid.modutils.load_module_from_name(module_part) class_name = qname.split(".")[-1] return getattr(module, class_name) # Python Linter class ######################################################### MSGS = { "F0001": ( "%s", "fatal", "Used when an error occurred preventing the analysis of a \ module (unable to find it for instance).", ), "F0002": ( "%s: %s", "astroid-error", "Used when an unexpected error occurred while building the " "Astroid representation. This is usually accompanied by a " "traceback. Please report such errors !", ), "F0010": ( "error while code parsing: %s", "parse-error", "Used when an exception occurred while building the Astroid " "representation which could be handled by astroid.", ), "F0011": ( "error while parsing the configuration: %s", "config-parse-error", "Used when an exception occurred while parsing a pylint configuration file.", ), "I0001": ( "Unable to run raw checkers on built-in module %s", "raw-checker-failed", "Used to inform that a built-in module has not been checked " "using the raw checkers.", ), "I0010": ( "Unable to consider inline option %r", "bad-inline-option", "Used when an inline option is either badly formatted or can't " "be used inside modules.", ), "I0011": ( "Locally disabling %s (%s)", "locally-disabled", "Used when an inline option disables a message or a messages category.", ), "I0013": ( "Ignoring entire file", "file-ignored", "Used to inform that the file will not be checked", ), "I0020": ( "Suppressed %s (from line %d)", "suppressed-message", "A message was triggered on a line, but suppressed explicitly " "by a disable= comment in the file. This message is not " "generated for messages that are ignored due to configuration " "settings.", ), "I0021": ( "Useless suppression of %s", "useless-suppression", "Reported when a message is explicitly disabled for a line or " "a block of code, but never triggered.", ), "I0022": ( 'Pragma "%s" is deprecated, use "%s" instead', "deprecated-pragma", "Some inline pylint options have been renamed or reworked, " "only the most recent form should be used. " "NOTE:skip-all is only available with pylint >= 0.26", {"old_names": [("I0014", "deprecated-disable-all")]}, ), "E0001": ("%s", "syntax-error", "Used when a syntax error is raised for a module."), "E0011": ( "Unrecognized file option %r", "unrecognized-inline-option", "Used when an unknown inline option is encountered.", ), "E0012": ( "Bad option value %r", "bad-option-value", "Used when a bad value for an inline option is encountered.", ), "E0013": ( "Plugin '%s' is impossible to load, is it installed ? ('%s')", "bad-plugin-value", "Used when a bad value is used in 'load-plugins'.", ), "E0014": ( "Out-of-place setting encountered in top level configuration-section '%s' : '%s'", "bad-configuration-section", "Used when we detect a setting in the top level of a toml configuration that shouldn't be there.", ), } # pylint: disable=too-many-instance-attributes,too-many-public-methods class PyLinter( config.OptionsManagerMixIn, reporters.ReportsHandlerMixIn, checkers.BaseTokenChecker, ): """lint Python modules using external checkers. This is the main checker controlling the other ones and the reports generation. It is itself both a raw checker and an astroid checker in order to: * handle message activation / deactivation at the module level * handle some basic but necessary stats'data (number of classes, methods...) IDE plugin developers: you may have to call `astroid.builder.MANAGER.astroid_cache.clear()` across runs if you want to ensure the latest code version is actually checked. This class needs to support pickling for parallel linting to work. The exception is reporter member; see check_parallel function for more details. """ __implements__ = (interfaces.ITokenChecker,) name = MAIN_CHECKER_NAME priority = 0 level = 0 msgs = MSGS # Will be used like this : datetime.now().strftime(crash_file_path) crash_file_path: str = "pylint-crash-%Y-%m-%d-%H.txt" @staticmethod def make_options(): return ( ( "ignore", { "type": "csv", "metavar": "[,...]", "dest": "black_list", "default": ("CVS",), "help": "Files or directories to be skipped. " "They should be base names, not paths.", }, ), ( "ignore-patterns", { "type": "regexp_csv", "metavar": "[,...]", "dest": "black_list_re", "default": (), "help": "Files or directories matching the regex patterns are" " skipped. The regex matches against base names, not paths.", }, ), ( "ignore-paths", { "type": "regexp_paths_csv", "metavar": "[,...]", "default": [], "help": "Add files or directories matching the regex patterns to the " "ignore-list. The regex matches against paths and can be in " "Posix or Windows format.", }, ), ( "persistent", { "default": True, "type": "yn", "metavar": "", "level": 1, "help": "Pickle collected data for later comparisons.", }, ), ( "load-plugins", { "type": "csv", "metavar": "", "default": (), "level": 1, "help": "List of plugins (as comma separated values of " "python module names) to load, usually to register " "additional checkers.", }, ), ( "output-format", { "default": "text", "type": "string", "metavar": "", "short": "f", "group": "Reports", "help": "Set the output format. Available formats are text," " parseable, colorized, json and msvs (visual studio)." " You can also give a reporter class, e.g. mypackage.mymodule." "MyReporterClass.", }, ), ( "reports", { "default": False, "type": "yn", "metavar": "", "short": "r", "group": "Reports", "help": "Tells whether to display a full report or only the " "messages.", }, ), ( "evaluation", { "type": "string", "metavar": "", "group": "Reports", "level": 1, "default": "10.0 - ((float(5 * error + warning + refactor + " "convention) / statement) * 10)", "help": "Python expression which should return a score less " "than or equal to 10. You have access to the variables " "'error', 'warning', 'refactor', and 'convention' which " "contain the number of messages in each category, as well as " "'statement' which is the total number of statements " "analyzed. This score is used by the global " "evaluation report (RP0004).", }, ), ( "score", { "default": True, "type": "yn", "metavar": "", "short": "s", "group": "Reports", "help": "Activate the evaluation score.", }, ), ( "fail-under", { "default": 10, "type": "float", "metavar": "", "help": "Specify a score threshold to be exceeded before program exits with error.", }, ), ( "fail-on", { "default": "", "type": "csv", "metavar": "", "help": "Return non-zero exit code if any of these messages/categories are detected," " even if score is above --fail-under value. Syntax same as enable." " Messages specified are enabled, while categories only check already-enabled messages.", }, ), ( "confidence", { "type": "multiple_choice", "metavar": "", "default": "", "choices": [c.name for c in interfaces.CONFIDENCE_LEVELS], "group": "Messages control", "help": "Only show warnings with the listed confidence levels." f" Leave empty to show all. Valid levels: {', '.join(c.name for c in interfaces.CONFIDENCE_LEVELS)}.", }, ), ( "enable", { "type": "csv", "metavar": "", "short": "e", "group": "Messages control", "help": "Enable the message, report, category or checker with the " "given id(s). You can either give multiple identifier " "separated by comma (,) or put this option multiple time " "(only on the command line, not in the configuration file " "where it should appear only once). " 'See also the "--disable" option for examples.', }, ), ( "disable", { "type": "csv", "metavar": "", "short": "d", "group": "Messages control", "help": "Disable the message, report, category or checker " "with the given id(s). You can either give multiple identifiers " "separated by comma (,) or put this option multiple times " "(only on the command line, not in the configuration file " "where it should appear only once). " 'You can also use "--disable=all" to disable everything first ' "and then reenable specific checks. For example, if you want " "to run only the similarities checker, you can use " '"--disable=all --enable=similarities". ' "If you want to run only the classes checker, but have no " "Warning level messages displayed, use " '"--disable=all --enable=classes --disable=W".', }, ), ( "msg-template", { "type": "string", "metavar": "