diff options
Diffstat (limited to 'pylint/lint/__init__.py')
-rw-r--r-- | pylint/lint/__init__.py | 1884 |
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:]) |