# -*- coding: utf-8 -*- # Copyright (c) 2006-2015 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2008 Fabrice Douchant # Copyright (c) 2009 Vincent # Copyright (c) 2009 Mads Kiilerich # Copyright (c) 2011-2014 Google, Inc. # Copyright (c) 2012 David Pursehouse # Copyright (c) 2012 Kevin Jing Qiu # Copyright (c) 2012 FELD Boris # Copyright (c) 2012 JT Olds # Copyright (c) 2014-2018 Claudiu Popa # Copyright (c) 2014-2015 Michal Nowikowski # Copyright (c) 2014 Brett Cannon # Copyright (c) 2014 Alexandru Coman # Copyright (c) 2014 Daniel Harding # Copyright (c) 2014 Arun Persaud # Copyright (c) 2014 Dan Goldsmith # Copyright (c) 2015-2016 Florian Bruhin # Copyright (c) 2015 Aru Sahni # Copyright (c) 2015 Steven Myint # Copyright (c) 2015 Simu Toni # Copyright (c) 2015 Mihai Balint # Copyright (c) 2015 Ionel Cristian Maries # Copyright (c) 2016-2017 Łukasz Rogalski # Copyright (c) 2016 Glenn Matthews # Copyright (c) 2016 Alan Evangelista # Copyright (c) 2017-2018 Ville Skyttä # Copyright (c) 2017-2018 hippo91 # Copyright (c) 2017 Daniel Miller # Copyright (c) 2017 Roman Ivanov # Copyright (c) 2017 Ned Batchelder # Copyright (c) 2018 Randall Leeds # Copyright (c) 2018 Mike Frysinger # Copyright (c) 2018 ssolanki # Copyright (c) 2018 Ville Skyttä # Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com> # Copyright (c) 2018 Anthony Sottile # Copyright (c) 2018 Jason Owen # Copyright (c) 2018 Gary Tyler McLeod # Copyright (c) 2018 Yuval Langer # Copyright (c) 2018 Nick Drozd # Copyright (c) 2018 kapsh # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/master/COPYING # pylint: disable=broad-except """ pylint [options] modules_or_packages Check that module(s) satisfy a coding standard (and more !). pylint --help Display this help message and exit. pylint --help-msg [,] Display help messages about given message identifiers and exit. """ import collections import contextlib import operator import os import sys import tokenize import traceback import warnings from io import TextIOWrapper import astroid from astroid import modutils from astroid.__pkginfo__ import version as astroid_version from astroid.builder import AstroidBuilder from pylint import __pkginfo__, checkers, config, exceptions, interfaces, reporters from pylint.__pkginfo__ import version from pylint.constants import MAIN_CHECKER_NAME, MSG_TYPES, OPTION_RGX from pylint.message import Message, MessageDefinitionStore, MessagesHandlerMixIn from pylint.reporters.ureports import nodes as report_nodes from pylint.utils import ASTWalker, FileState, utils try: import multiprocessing except ImportError: multiprocessing = None # type: ignore MANAGER = astroid.MANAGER def _ast_from_string(data, filepath, modname): cached = MANAGER.astroid_cache.get(modname) if cached and cached.file == filepath: return cached return AstroidBuilder(MANAGER).string_build(data, modname, filepath) 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 _get_new_args(message): location = ( message.abspath, message.path, message.module, message.obj, message.line, message.column, ) return (message.msg_id, message.symbol, location, message.msg, message.confidence) def _get_python_path(filepath): dirname = os.path.realpath(os.path.expanduser(filepath)) if not os.path.isdir(dirname): dirname = os.path.dirname(dirname) while True: if not os.path.exists(os.path.join(dirname, "__init__.py")): return dirname old_dirname = dirname dirname = os.path.dirname(dirname) if old_dirname == dirname: return os.getcwd() return None def _merge_stats(stats): merged = {} by_msg = collections.Counter() for stat in stats: message_stats = stat.pop("by_msg", {}) by_msg.update(message_stats) for key, item in stat.items(): if key not in merged: merged[key] = item else: if isinstance(item, dict): merged[key].update(item) else: merged[key] = merged[key] + item merged["by_msg"] = by_msg return merged # 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.", ), "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.", ), } def _cpu_count() -> int: """Use sched_affinity if available for virtualized or containerized environments.""" sched_getaffinity = getattr(os, "sched_getaffinity", None) # pylint: disable=not-callable,using-constant-test if sched_getaffinity: return len(sched_getaffinity(0)) if multiprocessing: return multiprocessing.cpu_count() return 1 if multiprocessing is not None: class ChildLinter(multiprocessing.Process): def run(self): # pylint: disable=no-member, unbalanced-tuple-unpacking tasks_queue, results_queue, self._config = self._args self._config["jobs"] = 1 # Child does not parallelize any further. self._python3_porting_mode = self._config.pop("python3_porting_mode", None) self._plugins = self._config.pop("plugins", None) # Run linter for received files/modules. for file_or_module in iter(tasks_queue.get, "STOP"): try: result = self._run_linter(file_or_module[0]) results_queue.put(result) except Exception as ex: print( "internal error with sending report for module %s" % file_or_module, file=sys.stderr, ) print(ex, file=sys.stderr) results_queue.put({}) def _run_linter(self, file_or_module): linter = PyLinter() # Register standard checkers. linter.load_default_plugins() # Load command line plugins. if self._plugins: linter.load_plugin_modules(self._plugins) linter.load_configuration_from_config(self._config) # Load plugin specific configuration linter.load_plugin_configuration() linter.set_reporter(reporters.CollectingReporter()) # Enable the Python 3 checker mode. This option is # passed down from the parent linter up to here, since # the Python 3 porting flag belongs to the Run class, # instead of the Linter class. if self._python3_porting_mode: linter.python3_porting_mode() # Run the checks. linter.check(file_or_module) msgs = [_get_new_args(m) for m in linter.reporter.messages] return ( file_or_module, linter.file_state.base_name, linter.current_name, msgs, linter.stats, linter.msg_status, ) # pylint: disable=too-many-instance-attributes,too-many-public-methods class PyLinter( config.OptionsManagerMixIn, MessagesHandlerMixIn, 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. """ __implements__ = (interfaces.ITokenChecker,) name = MAIN_CHECKER_NAME priority = 0 level = 0 msgs = MSGS @staticmethod def make_options(): return ( ( "ignore", { "type": "csv", "metavar": "[,...]", "dest": "black_list", "default": ("CVS",), "help": "Add files or directories to the blacklist. " "They should be base names, not paths.", }, ), ( "ignore-patterns", { "type": "regexp_csv", "metavar": "[,...]", "dest": "black_list_re", "default": (), "help": "Add files or directories matching the regex patterns to the" " blacklist. The regex matches against base names, not paths.", }, ), ( "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.", }, ), ( "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." " Leave empty to show all. Valid levels: %s." % (", ".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": "