# -*- coding: utf-8 -*- # Copyright (c) 2006-2015 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2008 Fabrice Douchant # Copyright (c) 2009 Vincent # Copyright (c) 2009 Mads Kiilerich # Copyright (c) 2011-2014 Google, Inc. # Copyright (c) 2012 David Pursehouse # Copyright (c) 2012 Kevin Jing Qiu # Copyright (c) 2012 FELD Boris # Copyright (c) 2012 JT Olds # Copyright (c) 2014-2017 Claudiu Popa # Copyright (c) 2014-2015 Michal Nowikowski # Copyright (c) 2014 Brett Cannon # Copyright (c) 2014 Alexandru Coman # Copyright (c) 2014 Daniel Harding # Copyright (c) 2014 Arun Persaud # Copyright (c) 2014 Dan Goldsmith # Copyright (c) 2015-2016 Florian Bruhin # Copyright (c) 2015 Aru Sahni # Copyright (c) 2015 Steven Myint # Copyright (c) 2015 Simu Toni # Copyright (c) 2015 Mihai Balint # Copyright (c) 2015 Ionel Cristian Maries # Copyright (c) 2016-2017 Łukasz Rogalski # Copyright (c) 2016 Glenn Matthews # Copyright (c) 2016 Alan Evangelista # Copyright (c) 2017 Daniel Miller # Copyright (c) 2017 hippo91 # Copyright (c) 2017 Roman Ivanov # Copyright (c) 2017 Ned Batchelder # Copyright (c) 2017 Ville Skyttä # 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 """ %prog [options] modules_or_packages Check that module(s) satisfy a coding standard (and more !). %prog --help Display this help message and exit. %prog --help-msg [,] Display help messages about given message identifiers and exit. """ from __future__ import print_function import collections import contextlib import operator import os try: import multiprocessing except ImportError: multiprocessing = None import sys import tokenize import warnings import six import astroid from astroid.__pkginfo__ import version as astroid_version from astroid import modutils from pylint import checkers from pylint import interfaces from pylint import reporters from pylint import exceptions from pylint import utils from pylint import config from pylint.__pkginfo__ import version from pylint.reporters.ureports import nodes as report_nodes MANAGER = astroid.MANAGER def _get_new_args(message): location = ( message.abspath, message.path, message.module, message.obj, message.line, message.column, ) return ( message.msg_id, message.symbol, location, message.msg, message.confidence, ) def _get_python_path(filepath): dirname = os.path.realpath(os.path.expanduser(filepath)) if not os.path.isdir(dirname): dirname = os.path.dirname(dirname) while True: if not os.path.exists(os.path.join(dirname, "__init__.py")): return dirname old_dirname = dirname dirname = os.path.dirname(dirname) if old_dirname == dirname: return os.getcwd() return None def _merge_stats(stats): merged = {} by_msg = collections.Counter() for stat in stats: message_stats = stat.pop('by_msg', {}) by_msg.update(message_stats) for key, item in six.iteritems(stat): if key not in merged: merged[key] = item else: if isinstance(item, dict): merged[key].update(item) else: merged[key] = merged[key] + item merged['by_msg'] = by_msg return merged @contextlib.contextmanager def _patch_sysmodules(): # Context manager that permits running pylint, on Windows, with -m switch # and with --jobs, as in 'python -2 -m pylint .. --jobs'. # For more details why this is needed, # see Python issue http://bugs.python.org/issue10845. mock_main = __name__ != '__main__' # -m switch if mock_main: sys.modules['__main__'] = sys.modules[__name__] try: yield finally: if mock_main: sys.modules.pop('__main__') # 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.'), 'I0012': ('Locally enabling %s (%s)', 'locally-enabled', 'Used when an inline option enables 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.'), } if multiprocessing is not None: class ChildLinter(multiprocessing.Process): def run(self): # pylint: disable=no-member, unbalanced-tuple-unpacking tasks_queue, results_queue, self._config = self._args self._config["jobs"] = 1 # Child does not parallelize any further. self._python3_porting_mode = self._config.pop( 'python3_porting_mode', None) self._plugins = self._config.pop('plugins', None) # Run linter for received files/modules. for file_or_module in iter(tasks_queue.get, 'STOP'): try: result = self._run_linter(file_or_module[0]) results_queue.put(result) except Exception as ex: print("internal error with sending report for module %s" % file_or_module, file=sys.stderr) print(ex, file=sys.stderr) results_queue.put({}) def _run_linter(self, file_or_module): linter = PyLinter() # Register standard checkers. linter.load_default_plugins() # Load command line plugins. if self._plugins: linter.load_plugin_modules(self._plugins) linter.load_configuration_from_config(self._config) linter.set_reporter(reporters.CollectingReporter()) # Enable the Python 3 checker mode. This option is # passed down from the parent linter up to here, since # the Python 3 porting flag belongs to the Run class, # instead of the Linter class. if self._python3_porting_mode: linter.python3_porting_mode() # Run the checks. linter.check(file_or_module) msgs = [_get_new_args(m) for m in linter.reporter.messages] return (file_or_module, linter.file_state.base_name, linter.current_name, msgs, linter.stats, linter.msg_status) class PyLinter(config.OptionsManagerMixIn, utils.MessagesHandlerMixIn, utils.ReportsHandlerMixIn, checkers.BaseTokenChecker): """lint Python modules using external checkers. This is the main checker controlling the other ones and the reports generation. It is itself both a raw checker and an astroid checker in order to: * handle message activation / deactivation at the module level * handle some basic but necessary stats'data (number of classes, methods...) IDE plugin developers: you may have to call `astroid.builder.MANAGER.astroid_cache.clear()` across runs if you want to ensure the latest code version is actually checked. """ __implements__ = (interfaces.ITokenChecker, ) name = 'master' priority = 0 level = 0 msgs = MSGS @staticmethod def make_options(): return (('ignore', {'type' : 'csv', 'metavar' : '[,...]', 'dest' : 'black_list', 'default' : ('CVS',), 'help' : 'Add files or directories to the blacklist. ' 'They should be base names, not paths.'}), ('ignore-patterns', {'type' : 'regexp_csv', 'metavar' : '[,...]', 'dest' : 'black_list_re', 'default' : (), 'help' : 'Add files or directories matching the regex patterns to the' ' blacklist. The regex matches against base names, not paths.'}), ('persistent', {'default': True, 'type' : 'yn', 'metavar' : '', 'level': 1, 'help' : 'Pickle collected data for later comparisons.'}), ('load-plugins', {'type' : 'csv', 'metavar' : '', 'default' : (), 'level': 1, 'help' : 'List of plugins (as comma separated values of ' 'python modules names) to load, usually to register ' 'additional checkers.'}), ('output-format', {'default': 'text', 'type': 'string', 'metavar' : '', 'short': 'f', 'group': 'Reports', 'help' : 'Set the output format. Available formats are text,' ' parseable, colorized, json and msvs (visual studio).' 'You can also give a reporter class, eg mypackage.mymodule.' 'MyReporterClass.'}), ('reports', {'default': False, 'type' : 'yn', 'metavar' : '', 'short': 'r', 'group': 'Reports', 'help' : 'Tells whether to display a full report or only the ' 'messages'}), ('evaluation', {'type' : 'string', 'metavar' : '', 'group': 'Reports', 'level': 1, 'default': '10.0 - ((float(5 * error + warning + refactor + ' 'convention) / statement) * 10)', 'help' : 'Python expression which should return a note less ' 'than 10 (10 is the highest note). You have access ' 'to the variables errors warning, statement which ' 'respectively contain the number of errors / ' 'warnings messages and the total number of ' 'statements analyzed. This is used by the global ' 'evaluation report (RP0004).'}), ('score', {'default': True, 'type': 'yn', 'metavar': '', 'short': 's', 'group': 'Reports', 'help': 'Activate the evaluation score.'}), ('confidence', {'type' : 'multiple_choice', 'metavar': '', 'default': '', 'choices': [c.name for c in interfaces.CONFIDENCE_LEVELS], 'group': 'Messages control', 'help' : 'Only show warnings with the listed confidence levels.' ' Leave empty to show all. Valid levels: %s' % ( ', '.join(c.name for c in interfaces.CONFIDENCE_LEVELS),)}), ('enable', {'type' : 'csv', 'metavar': '', 'short': 'e', 'group': 'Messages control', 'help' : 'Enable the message, report, category or checker with the ' 'given id(s). You can either give multiple identifier ' 'separated by comma (,) or put this option multiple time ' '(only on the command line, not in the configuration file ' 'where it should appear only once). ' 'See also the "--disable" option for examples. '}), ('disable', {'type' : 'csv', 'metavar': '', 'short': 'd', 'group': 'Messages control', 'help' : 'Disable the message, report, category or checker ' 'with the given id(s). You can either give multiple identifiers' ' separated by comma (,) or put this option multiple times ' '(only on the command line, not in the configuration file ' 'where it should appear only once).' 'You can also use "--disable=all" to disable everything first ' 'and then reenable specific checks. For example, if you want ' 'to run only the similarities checker, you can use ' '"--disable=all --enable=similarities". ' 'If you want to run only the classes checker, but have no ' 'Warning level messages displayed, use' '"--disable=all --enable=classes --disable=W"'}), ('msg-template', {'type' : 'string', 'metavar': '