summaryrefslogtreecommitdiff
path: root/pylint/lint/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'pylint/lint/__init__.py')
-rw-r--r--pylint/lint/__init__.py1884
1 files changed, 1884 insertions, 0 deletions
diff --git a/pylint/lint/__init__.py b/pylint/lint/__init__.py
new file mode 100644
index 000000000..b86f2ffed
--- /dev/null
+++ b/pylint/lint/__init__.py
@@ -0,0 +1,1884 @@
+# Copyright (c) 2006-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
+# Copyright (c) 2008 Fabrice Douchant <Fabrice.Douchant@logilab.fr>
+# Copyright (c) 2009 Vincent
+# Copyright (c) 2009 Mads Kiilerich <mads@kiilerich.com>
+# Copyright (c) 2011-2014 Google, Inc.
+# Copyright (c) 2012 David Pursehouse <david.pursehouse@sonymobile.com>
+# Copyright (c) 2012 Kevin Jing Qiu <kevin.jing.qiu@gmail.com>
+# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
+# Copyright (c) 2012 JT Olds <jtolds@xnet5.com>
+# Copyright (c) 2014-2018 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2014-2015 Michal Nowikowski <godfryd@gmail.com>
+# Copyright (c) 2014 Brett Cannon <brett@python.org>
+# Copyright (c) 2014 Alexandru Coman <fcoman@bitdefender.com>
+# Copyright (c) 2014 Daniel Harding <dharding@living180.net>
+# Copyright (c) 2014 Arun Persaud <arun@nubati.net>
+# Copyright (c) 2014 Dan Goldsmith <djgoldsmith@googlemail.com>
+# Copyright (c) 2015-2016 Florian Bruhin <me@the-compiler.org>
+# Copyright (c) 2015 Aru Sahni <arusahni@gmail.com>
+# Copyright (c) 2015 Steven Myint <hg@stevenmyint.com>
+# Copyright (c) 2015 Simu Toni <simutoni@gmail.com>
+# Copyright (c) 2015 Mihai Balint <balint.mihai@gmail.com>
+# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
+# Copyright (c) 2016-2017 Łukasz Rogalski <rogalski.91@gmail.com>
+# Copyright (c) 2016 Glenn Matthews <glenn@e-dad.net>
+# Copyright (c) 2016 Alan Evangelista <alanoe@linux.vnet.ibm.com>
+# Copyright (c) 2017-2018 Ville Skyttä <ville.skytta@iki.fi>
+# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2017 Daniel Miller <millerdev@gmail.com>
+# Copyright (c) 2017 Roman Ivanov <me@roivanov.com>
+# Copyright (c) 2017 Ned Batchelder <ned@nedbatchelder.com>
+# Copyright (c) 2018 Randall Leeds <randall@bleeds.info>
+# Copyright (c) 2018 Mike Frysinger <vapier@gmail.com>
+# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
+# Copyright (c) 2018 Ville Skyttä <ville.skytta@upcloud.com>
+# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
+# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
+# Copyright (c) 2018 Jason Owen <jason.a.owen@gmail.com>
+# Copyright (c) 2018 Gary Tyler McLeod <mail@garytyler.com>
+# Copyright (c) 2018 Yuval Langer <yuvallanger@mail.tau.ac.il>
+# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
+# Copyright (c) 2018 kapsh <kapsh@kap.sh>
+
+# 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 <msg-id>[,<msg-id>]
+
+ Display help messages about given message identifiers and exit.
+"""
+import collections
+import contextlib
+import functools
+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,
+ extensions,
+ interfaces,
+ reporters,
+)
+from pylint.__pkginfo__ import version
+from pylint.constants import MAIN_CHECKER_NAME, MSG_TYPES
+from pylint.message import Message, MessageDefinitionStore, MessagesHandlerMixIn
+from pylint.reporters.ureports import nodes as report_nodes
+from pylint.utils import ASTWalker, FileState, utils
+from pylint.utils.pragma_parser import (
+ OPTION_PO,
+ InvalidPragmaError,
+ UnRecognizedOptionError,
+ parse_pragma,
+)
+
+try:
+ import multiprocessing
+except ImportError:
+ multiprocessing = None # type: ignore
+
+
+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 _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 _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
+ elif 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
+
+
+# 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.
+
+ 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
+
+ @staticmethod
+ def make_options():
+ return (
+ (
+ "ignore",
+ {
+ "type": "csv",
+ "metavar": "<file>[,<file>...]",
+ "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": "<pattern>[,<pattern>...]",
+ "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": "<y_or_n>",
+ "level": 1,
+ "help": "Pickle collected data for later comparisons.",
+ },
+ ),
+ (
+ "load-plugins",
+ {
+ "type": "csv",
+ "metavar": "<modules>",
+ "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": "<format>",
+ "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": "<y_or_n>",
+ "short": "r",
+ "group": "Reports",
+ "help": "Tells whether to display a full report or only the "
+ "messages.",
+ },
+ ),
+ (
+ "evaluation",
+ {
+ "type": "string",
+ "metavar": "<python_expression>",
+ "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": "<y_or_n>",
+ "short": "s",
+ "group": "Reports",
+ "help": "Activate the evaluation score.",
+ },
+ ),
+ (
+ "fail-under",
+ {
+ "default": 10,
+ "type": "int",
+ "metavar": "<score>",
+ "help": "Specify a score threshold to be exceeded before program exits with error.",
+ },
+ ),
+ (
+ "confidence",
+ {
+ "type": "multiple_choice",
+ "metavar": "<levels>",
+ "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": "<msg ids>",
+ "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": "<msg ids>",
+ "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": "<template>",
+ "group": "Reports",
+ "help": (
+ "Template used to display messages. "
+ "This is a python new-style format string "
+ "used to format the message information. "
+ "See doc for all details."
+ ),
+ },
+ ),
+ (
+ "jobs",
+ {
+ "type": "int",
+ "metavar": "<n-processes>",
+ "short": "j",
+ "default": 1,
+ "help": "Use multiple processes to speed up Pylint. Specifying 0 will "
+ "auto-detect the number of processors available to use.",
+ },
+ ),
+ (
+ "unsafe-load-any-extension",
+ {
+ "type": "yn",
+ "metavar": "<yn>",
+ "default": False,
+ "hide": True,
+ "help": (
+ "Allow loading of arbitrary C extensions. Extensions"
+ " are imported into the active Python interpreter and"
+ " may run arbitrary code."
+ ),
+ },
+ ),
+ (
+ "limit-inference-results",
+ {
+ "type": "int",
+ "metavar": "<number-of-results>",
+ "default": 100,
+ "help": (
+ "Control the amount of potential inferred values when inferring "
+ "a single object. This can help the performance when dealing with "
+ "large functions or complex, nested conditions. "
+ ),
+ },
+ ),
+ (
+ "extension-pkg-whitelist",
+ {
+ "type": "csv",
+ "metavar": "<pkg[,pkg]>",
+ "default": [],
+ "help": (
+ "A comma-separated list of package or module names"
+ " from where C extensions may be loaded. Extensions are"
+ " loading into the active Python interpreter and may run"
+ " arbitrary code."
+ ),
+ },
+ ),
+ (
+ "suggestion-mode",
+ {
+ "type": "yn",
+ "metavar": "<yn>",
+ "default": True,
+ "help": (
+ "When enabled, pylint would attempt to guess common "
+ "misconfiguration and emit user-friendly hints instead "
+ "of false-positive error messages."
+ ),
+ },
+ ),
+ (
+ "exit-zero",
+ {
+ "action": "store_true",
+ "help": (
+ "Always return a 0 (non-error) status code, even if "
+ "lint errors are found. This is primarily useful in "
+ "continuous integration scripts."
+ ),
+ },
+ ),
+ (
+ "from-stdin",
+ {
+ "action": "store_true",
+ "help": (
+ "Interpret the stdin as a python script, whose filename "
+ "needs to be passed as the module_or_package argument."
+ ),
+ },
+ ),
+ )
+
+ option_groups = (
+ ("Messages control", "Options controlling analysis messages"),
+ ("Reports", "Options related to output formatting and reporting"),
+ )
+
+ def __init__(self, options=(), reporter=None, option_groups=(), pylintrc=None):
+ # some stuff has to be done before ancestors initialization...
+ #
+ # messages store / checkers / reporter / astroid manager
+ self.msgs_store = MessageDefinitionStore()
+ self.reporter = None
+ self._reporter_name = None
+ self._reporters = {}
+ self._checkers = collections.defaultdict(list)
+ self._pragma_lineno = {}
+ self._ignore_file = False
+ # visit variables
+ self.file_state = FileState()
+ self.current_name = None
+ self.current_file = None
+ self.stats = None
+ # init options
+ self._external_opts = options
+ self.options = options + PyLinter.make_options()
+ self.option_groups = option_groups + PyLinter.option_groups
+ self._options_methods = {"enable": self.enable, "disable": self.disable}
+ self._bw_options_methods = {
+ "disable-msg": self.disable,
+ "enable-msg": self.enable,
+ }
+ full_version = "pylint %s\nastroid %s\nPython %s" % (
+ version,
+ astroid_version,
+ sys.version,
+ )
+ MessagesHandlerMixIn.__init__(self)
+ reporters.ReportsHandlerMixIn.__init__(self)
+ super().__init__(
+ usage=__doc__,
+ version=full_version,
+ config_file=pylintrc or next(config.find_default_config_files(), None),
+ )
+ checkers.BaseTokenChecker.__init__(self)
+ # provided reports
+ self.reports = (
+ ("RP0001", "Messages by category", report_total_messages_stats),
+ (
+ "RP0002",
+ "% errors / warnings by module",
+ report_messages_by_module_stats,
+ ),
+ ("RP0003", "Messages", report_messages_stats),
+ )
+ self.register_checker(self)
+ self._dynamic_plugins = set()
+ self._python3_porting_mode = False
+ self._error_mode = False
+ self.load_provider_defaults()
+ if reporter:
+ self.set_reporter(reporter)
+
+ def load_default_plugins(self):
+ checkers.initialize(self)
+ reporters.initialize(self)
+ # Make sure to load the default reporter, because
+ # the option has been set before the plugins had been loaded.
+ if not self.reporter:
+ self._load_reporter()
+
+ def load_plugin_modules(self, modnames):
+ """take a list of module names which are pylint plugins and load
+ and register them
+ """
+ for modname in modnames:
+ if modname in self._dynamic_plugins:
+ continue
+ self._dynamic_plugins.add(modname)
+ module = modutils.load_module_from_name(modname)
+ module.register(self)
+
+ def load_plugin_configuration(self):
+ """Call the configuration hook for plugins
+
+ This walks through the list of plugins, grabs the "load_configuration"
+ hook, if exposed, and calls it to allow plugins to configure specific
+ settings.
+ """
+ for modname in self._dynamic_plugins:
+ module = modutils.load_module_from_name(modname)
+ if hasattr(module, "load_configuration"):
+ module.load_configuration(self)
+
+ def _load_reporter(self):
+ name = self._reporter_name.lower()
+ if name in self._reporters:
+ self.set_reporter(self._reporters[name]())
+ else:
+ try:
+ reporter_class = self._load_reporter_class()
+ except (ImportError, AttributeError):
+ raise exceptions.InvalidReporterError(name)
+ else:
+ self.set_reporter(reporter_class())
+
+ def _load_reporter_class(self):
+ qname = self._reporter_name
+ module = modutils.load_module_from_name(modutils.get_module_part(qname))
+ class_name = qname.split(".")[-1]
+ reporter_class = getattr(module, class_name)
+ return reporter_class
+
+ def set_reporter(self, reporter):
+ """set the reporter used to display messages and reports"""
+ self.reporter = reporter
+ reporter.linter = self
+
+ def set_option(self, optname, value, action=None, optdict=None):
+ """overridden from config.OptionsProviderMixin to handle some
+ special options
+ """
+ if optname in self._options_methods or optname in self._bw_options_methods:
+ if value:
+ try:
+ meth = self._options_methods[optname]
+ except KeyError:
+ meth = self._bw_options_methods[optname]
+ warnings.warn(
+ "%s is deprecated, replace it by %s"
+ % (optname, optname.split("-")[0]),
+ DeprecationWarning,
+ )
+ value = utils._check_csv(value)
+ if isinstance(value, (list, tuple)):
+ for _id in value:
+ meth(_id, ignore_unknown=True)
+ else:
+ meth(value)
+ return # no need to call set_option, disable/enable methods do it
+ elif optname == "output-format":
+ self._reporter_name = value
+ # If the reporters are already available, load
+ # the reporter class.
+ if self._reporters:
+ self._load_reporter()
+
+ try:
+ checkers.BaseTokenChecker.set_option(self, optname, value, action, optdict)
+ except config.UnsupportedAction:
+ print("option %s can't be read from config file" % optname, file=sys.stderr)
+
+ def register_reporter(self, reporter_class):
+ self._reporters[reporter_class.name] = reporter_class
+
+ def report_order(self):
+ reports = sorted(self._reports, key=lambda x: getattr(x, "name", ""))
+ try:
+ # Remove the current reporter and add it
+ # at the end of the list.
+ reports.pop(reports.index(self))
+ except ValueError:
+ pass
+ else:
+ reports.append(self)
+ return reports
+
+ # checkers manipulation methods ############################################
+
+ def register_checker(self, checker):
+ """register a new checker
+
+ checker is an object implementing IRawChecker or / and IAstroidChecker
+ """
+ assert checker.priority <= 0, "checker priority can't be >= 0"
+ self._checkers[checker.name].append(checker)
+ for r_id, r_title, r_cb in checker.reports:
+ self.register_report(r_id, r_title, r_cb, checker)
+ self.register_options_provider(checker)
+ if hasattr(checker, "msgs"):
+ self.msgs_store.register_messages_from_checker(checker)
+ checker.load_defaults()
+
+ # Register the checker, but disable all of its messages.
+ if not getattr(checker, "enabled", True):
+ self.disable(checker.name)
+
+ def disable_noerror_messages(self):
+ for msgcat, msgids in self.msgs_store._msgs_by_category.items():
+ # enable only messages with 'error' severity and above ('fatal')
+ if msgcat in ["E", "F"]:
+ for msgid in msgids:
+ self.enable(msgid)
+ else:
+ for msgid in msgids:
+ self.disable(msgid)
+
+ def disable_reporters(self):
+ """disable all reporters"""
+ for _reporters in self._reports.values():
+ for report_id, _, _ in _reporters:
+ self.disable_report(report_id)
+
+ def error_mode(self):
+ """error mode: enable only errors; no reports, no persistent"""
+ self._error_mode = True
+ self.disable_noerror_messages()
+ self.disable("miscellaneous")
+ if self._python3_porting_mode:
+ self.disable("all")
+ for msg_id in self._checker_messages("python3"):
+ if msg_id.startswith("E"):
+ self.enable(msg_id)
+ config_parser = self.cfgfile_parser
+ if config_parser.has_option("MESSAGES CONTROL", "disable"):
+ value = config_parser.get("MESSAGES CONTROL", "disable")
+ self.global_set_option("disable", value)
+ else:
+ self.disable("python3")
+ self.set_option("reports", False)
+ self.set_option("persistent", False)
+ self.set_option("score", False)
+
+ def python3_porting_mode(self):
+ """Disable all other checkers and enable Python 3 warnings."""
+ self.disable("all")
+ # re-enable some errors, or 'print', 'raise', 'async', 'await' will mistakenly lint fine
+ self.enable("fatal") # F0001
+ self.enable("astroid-error") # F0002
+ self.enable("parse-error") # F0010
+ self.enable("syntax-error") # E0001
+ self.enable("python3")
+ if self._error_mode:
+ # The error mode was activated, using the -E flag.
+ # So we'll need to enable only the errors from the
+ # Python 3 porting checker.
+ for msg_id in self._checker_messages("python3"):
+ if msg_id.startswith("E"):
+ self.enable(msg_id)
+ else:
+ self.disable(msg_id)
+ config_parser = self.cfgfile_parser
+ if config_parser.has_option("MESSAGES CONTROL", "disable"):
+ value = config_parser.get("MESSAGES CONTROL", "disable")
+ self.global_set_option("disable", value)
+ self._python3_porting_mode = True
+
+ def list_messages_enabled(self):
+ enabled = [
+ " %s (%s)" % (message.symbol, message.msgid)
+ for message in self.msgs_store.messages
+ if self.is_message_enabled(message.msgid)
+ ]
+ disabled = [
+ " %s (%s)" % (message.symbol, message.msgid)
+ for message in self.msgs_store.messages
+ if not self.is_message_enabled(message.msgid)
+ ]
+ print("Enabled messages:")
+ for msg in sorted(enabled):
+ print(msg)
+ print("\nDisabled messages:")
+ for msg in sorted(disabled):
+ print(msg)
+ print("")
+
+ # block level option handling #############################################
+ #
+ # see func_block_disable_msg.py test case for expected behaviour
+
+ def process_tokens(self, tokens):
+ """process tokens from the current module to search for module/block
+ level options
+ """
+ control_pragmas = {"disable", "enable"}
+ prev_line = None
+ saw_newline = True
+ seen_newline = True
+ for (tok_type, content, start, _, _) in tokens:
+ if prev_line and prev_line != start[0]:
+ saw_newline = seen_newline
+ seen_newline = False
+
+ prev_line = start[0]
+ if tok_type in (tokenize.NL, tokenize.NEWLINE):
+ seen_newline = True
+
+ if tok_type != tokenize.COMMENT:
+ continue
+ match = OPTION_PO.search(content)
+ if match is None:
+ continue
+
+ try:
+ for pragma_repr in parse_pragma(match.group(2)):
+ if pragma_repr.action in ("disable-all", "skip-file"):
+ if pragma_repr.action == "disable-all":
+ self.add_message(
+ "deprecated-pragma",
+ line=start[0],
+ args=("disable-all", "skip-file"),
+ )
+ self.add_message("file-ignored", line=start[0])
+ self._ignore_file = True
+ return
+ try:
+ meth = self._options_methods[pragma_repr.action]
+ except KeyError:
+ meth = self._bw_options_methods[pragma_repr.action]
+ # found a "(dis|en)able-msg" pragma deprecated suppression
+ self.add_message(
+ "deprecated-pragma",
+ line=start[0],
+ args=(
+ pragma_repr.action,
+ pragma_repr.action.replace("-msg", ""),
+ ),
+ )
+ for msgid in pragma_repr.messages:
+ # Add the line where a control pragma was encountered.
+ if pragma_repr.action in control_pragmas:
+ self._pragma_lineno[msgid] = start[0]
+
+ if (pragma_repr.action, msgid) == ("disable", "all"):
+ self.add_message(
+ "deprecated-pragma",
+ line=start[0],
+ args=("disable=all", "skip-file"),
+ )
+ self.add_message("file-ignored", line=start[0])
+ self._ignore_file = True
+ return
+ # If we did not see a newline between the previous line and now,
+ # we saw a backslash so treat the two lines as one.
+ l_start = start[0]
+ if not saw_newline:
+ l_start -= 1
+ try:
+ meth(msgid, "module", l_start)
+ except exceptions.UnknownMessageError:
+ self.add_message(
+ "bad-option-value", args=msgid, line=start[0]
+ )
+ except UnRecognizedOptionError as err:
+ self.add_message(
+ "unrecognized-inline-option", args=err.token, line=start[0]
+ )
+ continue
+ except InvalidPragmaError as err:
+ self.add_message("bad-inline-option", args=err.token, line=start[0])
+ continue
+
+ # code checking methods ###################################################
+
+ def get_checkers(self):
+ """return all available checkers as a list"""
+ return [self] + [
+ c
+ for _checkers in self._checkers.values()
+ for c in _checkers
+ if c is not self
+ ]
+
+ def get_checker_names(self):
+ """Get all the checker names that this linter knows about."""
+ current_checkers = self.get_checkers()
+ return sorted(
+ {
+ checker.name
+ for checker in current_checkers
+ if checker.name != MAIN_CHECKER_NAME
+ }
+ )
+
+ def prepare_checkers(self):
+ """return checkers needed for activated messages and reports"""
+ if not self.config.reports:
+ self.disable_reporters()
+ # get needed checkers
+ needed_checkers = [self]
+ for checker in self.get_checkers()[1:]:
+ messages = {msg for msg in checker.msgs if self.is_message_enabled(msg)}
+ if messages or any(self.report_is_enabled(r[0]) for r in checker.reports):
+ needed_checkers.append(checker)
+ # Sort checkers by priority
+ needed_checkers = sorted(
+ needed_checkers, key=operator.attrgetter("priority"), reverse=True
+ )
+ return needed_checkers
+
+ # pylint: disable=unused-argument
+ @staticmethod
+ def should_analyze_file(modname, path, is_argument=False):
+ """Returns whether or not a module should be checked.
+
+ This implementation returns True for all python source file, indicating
+ that all files should be linted.
+
+ Subclasses may override this method to indicate that modules satisfying
+ certain conditions should not be linted.
+
+ :param str modname: The name of the module to be checked.
+ :param str path: The full path to the source code of the module.
+ :param bool is_argument: Whetter the file is an argument to pylint or not.
+ Files which respect this property are always
+ checked, since the user requested it explicitly.
+ :returns: True if the module should be checked.
+ :rtype: bool
+ """
+ if is_argument:
+ return True
+ return path.endswith(".py")
+
+ # pylint: enable=unused-argument
+
+ def initialize(self):
+ """Initialize linter for linting
+
+ This method is called before any linting is done.
+ """
+ # initialize msgs_state now that all messages have been registered into
+ # the store
+ for msg in self.msgs_store.messages:
+ if not msg.may_be_emitted():
+ self._msgs_state[msg.msgid] = False
+
+ def check(self, files_or_modules):
+ """main checking entry: check a list of files or modules from their name.
+
+ files_or_modules is either a string or list of strings presenting modules to check.
+ """
+
+ self.initialize()
+
+ if not isinstance(files_or_modules, (list, tuple)):
+ files_or_modules = (files_or_modules,)
+
+ if self.config.from_stdin:
+ if len(files_or_modules) != 1:
+ raise exceptions.InvalidArgsError(
+ "Missing filename required for --from-stdin"
+ )
+
+ filepath = files_or_modules[0]
+ with fix_import_path(files_or_modules):
+ self._check_files(
+ functools.partial(self.get_ast, data=_read_stdin()),
+ [self._get_file_descr_from_stdin(filepath)],
+ )
+ elif self.config.jobs == 1:
+ with fix_import_path(files_or_modules):
+ self._check_files(
+ self.get_ast, self._iterate_file_descrs(files_or_modules)
+ )
+ else:
+ check_parallel(
+ self,
+ self.config.jobs,
+ self._iterate_file_descrs(files_or_modules),
+ files_or_modules,
+ )
+
+ def check_single_file(self, name, filepath, modname):
+ """Check single file
+
+ The arguments are the same that are documented in _check_files
+
+ The initialize() method should be called before calling this method
+ """
+ with self._astroid_module_checker() as check_astroid_module:
+ self._check_file(
+ self.get_ast, check_astroid_module, name, filepath, modname
+ )
+
+ def _check_files(self, get_ast, file_descrs):
+ """Check all files from file_descrs
+
+ The file_descrs should be iterable of tuple (name, filepath, modname)
+ where
+ - name: full name of the module
+ - filepath: path of the file
+ - modname: module name
+ """
+ with self._astroid_module_checker() as check_astroid_module:
+ for name, filepath, modname in file_descrs:
+ self._check_file(get_ast, check_astroid_module, name, filepath, modname)
+
+ def _check_file(self, get_ast, check_astroid_module, name, filepath, modname):
+ """Check a file using the passed utility functions (get_ast and check_astroid_module)
+
+ :param callable get_ast: callable returning AST from defined file taking the following arguments
+ - filepath: path to the file to check
+ - name: Python module name
+ :param callable check_astroid_module: callable checking an AST taking the following arguments
+ - ast: AST of the module
+ :param str name: full name of the module
+ :param str filepath: path to checked file
+ :param str modname: name of the checked Python module
+ """
+ self.set_current_module(name, filepath)
+ # get the module representation
+ ast_node = get_ast(filepath, name)
+ if ast_node is None:
+ return
+
+ self._ignore_file = False
+
+ self.file_state = FileState(modname)
+ # fix the current file (if the source file was not available or
+ # if it's actually a c extension)
+ self.current_file = ast_node.file # pylint: disable=maybe-no-member
+ check_astroid_module(ast_node)
+ # warn about spurious inline messages handling
+ spurious_messages = self.file_state.iter_spurious_suppression_messages(
+ self.msgs_store
+ )
+ for msgid, line, args in spurious_messages:
+ self.add_message(msgid, line, None, args)
+
+ @staticmethod
+ def _get_file_descr_from_stdin(filepath):
+ """Return file description (tuple of module name, file path, base name) from given file path
+
+ This method is used for creating suitable file description for _check_files when the
+ source is standard input.
+ """
+ try:
+ # Note that this function does not really perform an
+ # __import__ but may raise an ImportError exception, which
+ # we want to catch here.
+ modname = ".".join(modutils.modpath_from_file(filepath))
+ except ImportError:
+ modname = os.path.splitext(os.path.basename(filepath))[0]
+
+ return (modname, filepath, filepath)
+
+ def _iterate_file_descrs(self, files_or_modules):
+ """Return generator yielding file descriptions (tuples of module name, file path, base name)
+
+ The returned generator yield one item for each Python module that should be linted.
+ """
+ for descr in self._expand_files(files_or_modules):
+ name, filepath, is_arg = descr["name"], descr["path"], descr["isarg"]
+ if self.should_analyze_file(name, filepath, is_argument=is_arg):
+ yield (name, filepath, descr["basename"])
+
+ def _expand_files(self, modules):
+ """get modules and errors from a list of modules and handle errors
+ """
+ result, errors = utils.expand_modules(
+ modules, self.config.black_list, self.config.black_list_re
+ )
+ for error in errors:
+ message = modname = error["mod"]
+ key = error["key"]
+ self.set_current_module(modname)
+ if key == "fatal":
+ message = str(error["ex"]).replace(os.getcwd() + os.sep, "")
+ self.add_message(key, args=message)
+ return result
+
+ def set_current_module(self, modname, filepath=None):
+ """set the name of the currently analyzed module and
+ init statistics for it
+ """
+ if not modname and filepath is None:
+ return
+ self.reporter.on_set_current_module(modname, filepath)
+ self.current_name = modname
+ self.current_file = filepath or modname
+ self.stats["by_module"][modname] = {}
+ self.stats["by_module"][modname]["statement"] = 0
+ for msg_cat in MSG_TYPES.values():
+ self.stats["by_module"][modname][msg_cat] = 0
+
+ @contextlib.contextmanager
+ def _astroid_module_checker(self):
+ """Context manager for checking ASTs
+
+ The value in the context is callable accepting AST as its only argument.
+ """
+ walker = ASTWalker(self)
+ _checkers = self.prepare_checkers()
+ tokencheckers = [
+ c
+ for c in _checkers
+ if interfaces.implements(c, interfaces.ITokenChecker) and c is not self
+ ]
+ rawcheckers = [
+ c for c in _checkers if interfaces.implements(c, interfaces.IRawChecker)
+ ]
+ # notify global begin
+ for checker in _checkers:
+ checker.open()
+ if interfaces.implements(checker, interfaces.IAstroidChecker):
+ walker.add_checker(checker)
+
+ yield functools.partial(
+ self.check_astroid_module,
+ walker=walker,
+ tokencheckers=tokencheckers,
+ rawcheckers=rawcheckers,
+ )
+
+ # notify global end
+ self.stats["statement"] = walker.nbstatements
+ for checker in reversed(_checkers):
+ checker.close()
+
+ def get_ast(self, filepath, modname, data=None):
+ """Return an ast(roid) representation of a module or a string.
+
+ :param str filepath: path to checked file.
+ :param str modname: The name of the module to be checked.
+ :param str data: optional contents of the checked file.
+ :returns: the AST
+ :rtype: astroid.nodes.Module
+ """
+ try:
+ if data is None:
+ return MANAGER.ast_from_file(filepath, modname, source=True)
+ return AstroidBuilder(MANAGER).string_build(data, modname, filepath)
+ except astroid.AstroidSyntaxError as ex:
+ # pylint: disable=no-member
+ self.add_message(
+ "syntax-error",
+ line=getattr(ex.error, "lineno", 0),
+ col_offset=getattr(ex.error, "offset", None),
+ args=str(ex.error),
+ )
+ except astroid.AstroidBuildingException as ex:
+ self.add_message("parse-error", args=ex)
+ except Exception as ex:
+ traceback.print_exc()
+ self.add_message("astroid-error", args=(ex.__class__, ex))
+
+ def check_astroid_module(self, ast_node, walker, rawcheckers, tokencheckers):
+ """Check a module from its astroid representation.
+
+ For return value see _check_astroid_module
+ """
+ before_check_statements = walker.nbstatements
+
+ retval = self._check_astroid_module(
+ ast_node, walker, rawcheckers, tokencheckers
+ )
+
+ self.stats["by_module"][self.current_name]["statement"] = (
+ walker.nbstatements - before_check_statements
+ )
+
+ return retval
+
+ def _check_astroid_module(self, ast_node, walker, rawcheckers, tokencheckers):
+ """Check given AST node with given walker and checkers
+
+ :param astroid.nodes.Module ast_node: AST node of the module to check
+ :param pylint.utils.ast_walker.ASTWalker walker: AST walker
+ :param list rawcheckers: List of token checkers to use
+ :param list tokencheckers: List of raw checkers to use
+
+ :returns: True if the module was checked, False if ignored,
+ None if the module contents could not be parsed
+ :rtype: bool
+ """
+ try:
+ tokens = utils.tokenize_module(ast_node)
+ except tokenize.TokenError as ex:
+ self.add_message("syntax-error", line=ex.args[1][0], args=ex.args[0])
+ return None
+
+ if not ast_node.pure_python:
+ self.add_message("raw-checker-failed", args=ast_node.name)
+ else:
+ # assert astroid.file.endswith('.py')
+ # invoke ITokenChecker interface on self to fetch module/block
+ # level options
+ self.process_tokens(tokens)
+ if self._ignore_file:
+ return False
+ # walk ast to collect line numbers
+ self.file_state.collect_block_lines(self.msgs_store, ast_node)
+ # run raw and tokens checkers
+ for checker in rawcheckers:
+ checker.process_module(ast_node)
+ for checker in tokencheckers:
+ checker.process_tokens(tokens)
+ # generate events to astroid checkers
+ walker.walk(ast_node)
+ return True
+
+ # IAstroidChecker interface #################################################
+
+ def open(self):
+ """initialize counters"""
+ self.stats = {"by_module": {}, "by_msg": {}}
+ MANAGER.always_load_extensions = self.config.unsafe_load_any_extension
+ MANAGER.max_inferable_values = self.config.limit_inference_results
+ MANAGER.extension_package_whitelist.update(self.config.extension_pkg_whitelist)
+ for msg_cat in MSG_TYPES.values():
+ self.stats[msg_cat] = 0
+
+ def generate_reports(self):
+ """close the whole package /module, it's time to make reports !
+
+ if persistent run, pickle results for later comparison
+ """
+ # Display whatever messages are left on the reporter.
+ self.reporter.display_messages(report_nodes.Section())
+
+ if self.file_state.base_name is not None:
+ # load previous results if any
+ previous_stats = config.load_results(self.file_state.base_name)
+ self.reporter.on_close(self.stats, previous_stats)
+ if self.config.reports:
+ sect = self.make_reports(self.stats, previous_stats)
+ else:
+ sect = report_nodes.Section()
+
+ if self.config.reports:
+ self.reporter.display_reports(sect)
+ score_value = self._report_evaluation()
+ # save results if persistent run
+ if self.config.persistent:
+ config.save_results(self.stats, self.file_state.base_name)
+ else:
+ self.reporter.on_close(self.stats, {})
+ score_value = None
+ return score_value
+
+ def _report_evaluation(self):
+ """make the global evaluation report"""
+ # check with at least check 1 statements (usually 0 when there is a
+ # syntax error preventing pylint from further processing)
+ note = None
+ previous_stats = config.load_results(self.file_state.base_name)
+ if self.stats["statement"] == 0:
+ return note
+
+ # get a global note for the code
+ evaluation = self.config.evaluation
+ try:
+ note = eval(evaluation, {}, self.stats) # pylint: disable=eval-used
+ except Exception as ex:
+ msg = "An exception occurred while rating: %s" % ex
+ else:
+ self.stats["global_note"] = note
+ msg = "Your code has been rated at %.2f/10" % note
+ pnote = previous_stats.get("global_note")
+ if pnote is not None:
+ msg += " (previous run: %.2f/10, %+.2f)" % (pnote, note - pnote)
+
+ if self.config.score:
+ sect = report_nodes.EvaluationSection(msg)
+ self.reporter.display_reports(sect)
+ return note
+
+
+def check_parallel(linter, jobs, files, arguments=None):
+ """Use the given linter to lint the files with given amount of workers (jobs)"""
+ # The reporter does not need to be passed to worker processess, i.e. the reporter does
+ # not need to be pickleable
+ original_reporter = linter.reporter
+ linter.reporter = None
+
+ # The linter is inherited by all the pool's workers, i.e. the linter
+ # is identical to the linter object here. This is requred so that
+ # a custom PyLinter object can be used.
+ initializer = functools.partial(_worker_initialize, arguments=arguments)
+ with multiprocessing.Pool(jobs, initializer=initializer, initargs=[linter]) as pool:
+ # ..and now when the workers have inherited the linter, the actual reporter
+ # can be set back here on the parent process so that results get stored into
+ # correct reporter
+ linter.set_reporter(original_reporter)
+ linter.open()
+
+ all_stats = []
+
+ for module, messages, stats, msg_status in pool.imap_unordered(
+ _worker_check_single_file, files
+ ):
+ linter.set_current_module(module)
+ for msg in messages:
+ msg = Message(*msg)
+ linter.reporter.handle_message(msg)
+
+ all_stats.append(stats)
+ linter.msg_status |= msg_status
+
+ linter.stats = _merge_stats(all_stats)
+
+ # Insert stats data to local checkers.
+ for checker in linter.get_checkers():
+ if checker is not linter:
+ checker.stats = linter.stats
+
+
+# PyLinter object used by worker processes when checking files using multiprocessing
+# should only be used by the worker processes
+_worker_linter = None
+
+
+def _worker_initialize(linter, arguments=None):
+ global _worker_linter # pylint: disable=global-statement
+ _worker_linter = linter
+
+ # On the worker process side the messages are just collected and passed back to
+ # parent process as _worker_check_file function's return value
+ _worker_linter.set_reporter(reporters.CollectingReporter())
+ _worker_linter.open()
+
+ # Patch sys.path so that each argument is importable just like in single job mode
+ _patch_sys_path(arguments or ())
+
+
+def _worker_check_single_file(file_item):
+ name, filepath, modname = file_item
+
+ _worker_linter.open()
+ _worker_linter.check_single_file(name, filepath, modname)
+
+ msgs = [_get_new_args(m) for m in _worker_linter.reporter.messages]
+ return (
+ _worker_linter.current_name,
+ msgs,
+ _worker_linter.stats,
+ _worker_linter.msg_status,
+ )
+
+
+# some reporting functions ####################################################
+
+
+def report_total_messages_stats(sect, stats, previous_stats):
+ """make total errors / warnings report"""
+ lines = ["type", "number", "previous", "difference"]
+ lines += checkers.table_lines_from_stats(
+ stats, previous_stats, ("convention", "refactor", "warning", "error")
+ )
+ sect.append(report_nodes.Table(children=lines, cols=4, rheaders=1))
+
+
+def report_messages_stats(sect, stats, _):
+ """make messages type report"""
+ if not stats["by_msg"]:
+ # don't print this report when we didn't detected any errors
+ raise exceptions.EmptyReportError()
+ in_order = sorted(
+ [
+ (value, msg_id)
+ for msg_id, value in stats["by_msg"].items()
+ if not msg_id.startswith("I")
+ ]
+ )
+ in_order.reverse()
+ lines = ("message id", "occurrences")
+ for value, msg_id in in_order:
+ lines += (msg_id, str(value))
+ sect.append(report_nodes.Table(children=lines, cols=2, rheaders=1))
+
+
+def report_messages_by_module_stats(sect, stats, _):
+ """make errors / warnings by modules report"""
+ if len(stats["by_module"]) == 1:
+ # don't print this report when we are analysing a single module
+ raise exceptions.EmptyReportError()
+ by_mod = collections.defaultdict(dict)
+ for m_type in ("fatal", "error", "warning", "refactor", "convention"):
+ total = stats[m_type]
+ for module in stats["by_module"].keys():
+ mod_total = stats["by_module"][module][m_type]
+ if total == 0:
+ percent = 0
+ else:
+ percent = float((mod_total) * 100) / total
+ by_mod[module][m_type] = percent
+ sorted_result = []
+ for module, mod_info in by_mod.items():
+ sorted_result.append(
+ (
+ mod_info["error"],
+ mod_info["warning"],
+ mod_info["refactor"],
+ mod_info["convention"],
+ module,
+ )
+ )
+ sorted_result.sort()
+ sorted_result.reverse()
+ lines = ["module", "error", "warning", "refactor", "convention"]
+ for line in sorted_result:
+ # Don't report clean modules.
+ if all(entry == 0 for entry in line[:-1]):
+ continue
+ lines.append(line[-1])
+ for val in line[:-1]:
+ lines.append("%.2f" % val)
+ if len(lines) == 5:
+ raise exceptions.EmptyReportError()
+ sect.append(report_nodes.Table(children=lines, cols=5, rheaders=1))
+
+
+# utilities ###################################################################
+
+
+class ArgumentPreprocessingError(Exception):
+ """Raised if an error occurs during argument preprocessing."""
+
+
+def preprocess_options(args, search_for):
+ """look for some options (keys of <search_for>) which have to be processed
+ before others
+
+ values of <search_for> are callback functions to call when the option is
+ found
+ """
+ i = 0
+ while i < len(args):
+ arg = args[i]
+ if arg.startswith("--"):
+ try:
+ option, val = arg[2:].split("=", 1)
+ except ValueError:
+ option, val = arg[2:], None
+ try:
+ cb, takearg = search_for[option]
+ except KeyError:
+ i += 1
+ else:
+ del args[i]
+ if takearg and val is None:
+ if i >= len(args) or args[i].startswith("-"):
+ msg = "Option %s expects a value" % option
+ raise ArgumentPreprocessingError(msg)
+ val = args[i]
+ del args[i]
+ elif not takearg and val is not None:
+ msg = "Option %s doesn't expects a value" % option
+ raise ArgumentPreprocessingError(msg)
+ cb(option, val)
+ else:
+ i += 1
+
+
+def _patch_sys_path(args):
+ original = list(sys.path)
+ changes = []
+ seen = set()
+ for arg in args:
+ path = utils.get_python_path(arg)
+ if path not in seen:
+ changes.append(path)
+ seen.add(path)
+
+ sys.path[:] = changes + sys.path
+ return original
+
+
+@contextlib.contextmanager
+def fix_import_path(args):
+ """Prepare sys.path for running the linter checks.
+
+ Within this context, each of the given arguments is importable.
+ Paths are added to sys.path in corresponding order to the arguments.
+ We avoid adding duplicate directories to sys.path.
+ `sys.path` is reset to its original value upon exiting this context.
+ """
+ original = _patch_sys_path(args)
+ try:
+ yield
+ finally:
+ sys.path[:] = original
+
+
+class Run:
+ """helper class to use as main for pylint :
+
+ run(*sys.argv[1:])
+ """
+
+ LinterClass = PyLinter
+ option_groups = (
+ (
+ "Commands",
+ "Options which are actually commands. Options in this \
+group are mutually exclusive.",
+ ),
+ )
+
+ @staticmethod
+ def _return_one(*args): # pylint: disable=unused-argument
+ return 1
+
+ def __init__(self, args, reporter=None, do_exit=True):
+ self._rcfile = None
+ self._plugins = []
+ self.verbose = None
+ try:
+ preprocess_options(
+ args,
+ {
+ # option: (callback, takearg)
+ "init-hook": (cb_init_hook, True),
+ "rcfile": (self.cb_set_rcfile, True),
+ "load-plugins": (self.cb_add_plugins, True),
+ "verbose": (self.cb_verbose_mode, False),
+ },
+ )
+ except ArgumentPreprocessingError as ex:
+ print(ex, file=sys.stderr)
+ sys.exit(32)
+
+ self.linter = linter = self.LinterClass(
+ (
+ (
+ "rcfile",
+ {
+ "action": "callback",
+ "callback": Run._return_one,
+ "group": "Commands",
+ "type": "string",
+ "metavar": "<file>",
+ "help": "Specify a configuration file to load.",
+ },
+ ),
+ (
+ "init-hook",
+ {
+ "action": "callback",
+ "callback": Run._return_one,
+ "type": "string",
+ "metavar": "<code>",
+ "level": 1,
+ "help": "Python code to execute, usually for sys.path "
+ "manipulation such as pygtk.require().",
+ },
+ ),
+ (
+ "help-msg",
+ {
+ "action": "callback",
+ "type": "string",
+ "metavar": "<msg-id>",
+ "callback": self.cb_help_message,
+ "group": "Commands",
+ "help": "Display a help message for the given message id and "
+ "exit. The value may be a comma separated list of message ids.",
+ },
+ ),
+ (
+ "list-msgs",
+ {
+ "action": "callback",
+ "metavar": "<msg-id>",
+ "callback": self.cb_list_messages,
+ "group": "Commands",
+ "level": 1,
+ "help": "Generate pylint's messages.",
+ },
+ ),
+ (
+ "list-msgs-enabled",
+ {
+ "action": "callback",
+ "metavar": "<msg-id>",
+ "callback": self.cb_list_messages_enabled,
+ "group": "Commands",
+ "level": 1,
+ "help": "Display a list of what messages are enabled "
+ "and disabled with the given configuration.",
+ },
+ ),
+ (
+ "list-groups",
+ {
+ "action": "callback",
+ "metavar": "<msg-id>",
+ "callback": self.cb_list_groups,
+ "group": "Commands",
+ "level": 1,
+ "help": "List pylint's message groups.",
+ },
+ ),
+ (
+ "list-conf-levels",
+ {
+ "action": "callback",
+ "callback": cb_list_confidence_levels,
+ "group": "Commands",
+ "level": 1,
+ "help": "Generate pylint's confidence levels.",
+ },
+ ),
+ (
+ "list-extensions",
+ {
+ "action": "callback",
+ "callback": cb_list_extensions,
+ "group": "Commands",
+ "level": 1,
+ "help": "List available extensions.",
+ },
+ ),
+ (
+ "full-documentation",
+ {
+ "action": "callback",
+ "metavar": "<msg-id>",
+ "callback": self.cb_full_documentation,
+ "group": "Commands",
+ "level": 1,
+ "help": "Generate pylint's full documentation.",
+ },
+ ),
+ (
+ "generate-rcfile",
+ {
+ "action": "callback",
+ "callback": self.cb_generate_config,
+ "group": "Commands",
+ "help": "Generate a sample configuration file according to "
+ "the current configuration. You can put other options "
+ "before this one to get them in the generated "
+ "configuration.",
+ },
+ ),
+ (
+ "generate-man",
+ {
+ "action": "callback",
+ "callback": self.cb_generate_manpage,
+ "group": "Commands",
+ "help": "Generate pylint's man page.",
+ "hide": True,
+ },
+ ),
+ (
+ "errors-only",
+ {
+ "action": "callback",
+ "callback": self.cb_error_mode,
+ "short": "E",
+ "help": "In error mode, checkers without error messages are "
+ "disabled and for others, only the ERROR messages are "
+ "displayed, and no reports are done by default.",
+ },
+ ),
+ (
+ "py3k",
+ {
+ "action": "callback",
+ "callback": self.cb_python3_porting_mode,
+ "help": "In Python 3 porting mode, all checkers will be "
+ "disabled and only messages emitted by the porting "
+ "checker will be displayed.",
+ },
+ ),
+ (
+ "verbose",
+ {
+ "action": "callback",
+ "callback": self.cb_verbose_mode,
+ "short": "v",
+ "help": "In verbose mode, extra non-checker-related info "
+ "will be displayed.",
+ },
+ ),
+ ),
+ option_groups=self.option_groups,
+ pylintrc=self._rcfile,
+ )
+ # register standard checkers
+ linter.load_default_plugins()
+ # load command line plugins
+ linter.load_plugin_modules(self._plugins)
+ # add some help section
+ linter.add_help_section("Environment variables", config.ENV_HELP, level=1)
+ # pylint: disable=bad-continuation
+ linter.add_help_section(
+ "Output",
+ "Using the default text output, the message format is : \n"
+ " \n"
+ " MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE \n"
+ " \n"
+ "There are 5 kind of message types : \n"
+ " * (C) convention, for programming standard violation \n"
+ " * (R) refactor, for bad code smell \n"
+ " * (W) warning, for python specific problems \n"
+ " * (E) error, for probable bugs in the code \n"
+ " * (F) fatal, if an error occurred which prevented pylint from doing further\n"
+ "processing.\n",
+ level=1,
+ )
+ linter.add_help_section(
+ "Output status code",
+ "Pylint should leave with following status code: \n"
+ " * 0 if everything went fine \n"
+ " * 1 if a fatal message was issued \n"
+ " * 2 if an error message was issued \n"
+ " * 4 if a warning message was issued \n"
+ " * 8 if a refactor message was issued \n"
+ " * 16 if a convention message was issued \n"
+ " * 32 on usage error \n"
+ " \n"
+ "status 1 to 16 will be bit-ORed so you can know which different categories has\n"
+ "been issued by analysing pylint output status code\n",
+ level=1,
+ )
+ # read configuration
+ linter.disable("I")
+ linter.enable("c-extension-no-member")
+ linter.read_config_file(verbose=self.verbose)
+ config_parser = linter.cfgfile_parser
+ # run init hook, if present, before loading plugins
+ if config_parser.has_option("MASTER", "init-hook"):
+ cb_init_hook(
+ "init-hook", utils._unquote(config_parser.get("MASTER", "init-hook"))
+ )
+ # is there some additional plugins in the file configuration, in
+ if config_parser.has_option("MASTER", "load-plugins"):
+ plugins = utils._splitstrip(config_parser.get("MASTER", "load-plugins"))
+ linter.load_plugin_modules(plugins)
+ # now we can load file config and command line, plugins (which can
+ # provide options) have been registered
+ linter.load_config_file()
+
+ if reporter:
+ # if a custom reporter is provided as argument, it may be overridden
+ # by file parameters, so re-set it here, but before command line
+ # parsing so it's still overrideable by command line option
+ linter.set_reporter(reporter)
+ try:
+ args = linter.load_command_line_configuration(args)
+ except SystemExit as exc:
+ if exc.code == 2: # bad options
+ exc.code = 32
+ raise
+ if not args:
+ print(linter.help())
+ sys.exit(32)
+
+ if linter.config.jobs < 0:
+ print(
+ "Jobs number (%d) should be greater than or equal to 0"
+ % linter.config.jobs,
+ file=sys.stderr,
+ )
+ sys.exit(32)
+ if linter.config.jobs > 1 or linter.config.jobs == 0:
+ if multiprocessing is None:
+ print(
+ "Multiprocessing library is missing, " "fallback to single process",
+ file=sys.stderr,
+ )
+ linter.set_option("jobs", 1)
+ elif linter.config.jobs == 0:
+ linter.config.jobs = _cpu_count()
+
+ # We have loaded configuration from config file and command line. Now, we can
+ # load plugin specific configuration.
+ linter.load_plugin_configuration()
+
+ linter.check(args)
+ score_value = linter.generate_reports()
+ if do_exit:
+ if linter.config.exit_zero:
+ sys.exit(0)
+ else:
+ if score_value and score_value > linter.config.fail_under:
+ sys.exit(0)
+ sys.exit(self.linter.msg_status)
+
+ def cb_set_rcfile(self, name, value):
+ """callback for option preprocessing (i.e. before option parsing)"""
+ self._rcfile = value
+
+ def cb_add_plugins(self, name, value):
+ """callback for option preprocessing (i.e. before option parsing)"""
+ self._plugins.extend(utils._splitstrip(value))
+
+ def cb_error_mode(self, *args, **kwargs):
+ """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
+ """
+ self.linter.error_mode()
+
+ def cb_generate_config(self, *args, **kwargs):
+ """optik callback for sample config file generation"""
+ self.linter.generate_config(skipsections=("COMMANDS",))
+ sys.exit(0)
+
+ def cb_generate_manpage(self, *args, **kwargs):
+ """optik callback for sample config file generation"""
+ self.linter.generate_manpage(__pkginfo__)
+ sys.exit(0)
+
+ def cb_help_message(self, option, optname, value, parser):
+ """optik callback for printing some help about a particular message"""
+ self.linter.msgs_store.help_message(utils._splitstrip(value))
+ sys.exit(0)
+
+ def cb_full_documentation(self, option, optname, value, parser):
+ """optik callback for printing full documentation"""
+ self.linter.print_full_documentation()
+ sys.exit(0)
+
+ def cb_list_messages(self, option, optname, value, parser):
+ """optik callback for printing available messages"""
+ self.linter.msgs_store.list_messages()
+ sys.exit(0)
+
+ def cb_list_messages_enabled(self, option, optname, value, parser):
+ """optik callback for printing available messages"""
+ self.linter.list_messages_enabled()
+ sys.exit(0)
+
+ def cb_list_groups(self, *args, **kwargs):
+ """List all the check groups that pylint knows about
+
+ These should be useful to know what check groups someone can disable
+ or enable.
+ """
+ for check in self.linter.get_checker_names():
+ print(check)
+ sys.exit(0)
+
+ def cb_python3_porting_mode(self, *args, **kwargs):
+ """Activate only the python3 porting checker."""
+ self.linter.python3_porting_mode()
+
+ def cb_verbose_mode(self, *args, **kwargs):
+ self.verbose = True
+
+
+def cb_list_extensions(option, optname, value, parser):
+ """List all the extensions under pylint.extensions"""
+
+ for filename in os.listdir(os.path.dirname(extensions.__file__)):
+ if filename.endswith(".py") and not filename.startswith("_"):
+ extension_name, _, _ = filename.partition(".")
+ print("pylint.extensions.{}".format(extension_name))
+ sys.exit(0)
+
+
+def cb_list_confidence_levels(option, optname, value, parser):
+ for level in interfaces.CONFIDENCE_LEVELS:
+ print("%-18s: %s" % level)
+ sys.exit(0)
+
+
+def cb_init_hook(optname, value):
+ """exec arbitrary code to set sys.path for instance"""
+ exec(value) # pylint: disable=exec-used
+
+
+if __name__ == "__main__":
+ Run(sys.argv[1:])