Index: third_party/pylint/lint.py |
diff --git a/third_party/pylint/lint.py b/third_party/pylint/lint.py |
index 5be37f3499a4835f648c27358d723c6165705fa6..6f762ccf98b4eb362b9db92651d4c762df31b039 100644 |
--- a/third_party/pylint/lint.py |
+++ b/third_party/pylint/lint.py |
@@ -25,6 +25,7 @@ |
Display help messages about given message identifiers and exit. |
""" |
+from __future__ import print_function |
# import this first to avoid builtin namespace pollution |
from pylint.checkers import utils #pylint: disable=unused-import |
@@ -32,16 +33,24 @@ from pylint.checkers import utils #pylint: disable=unused-import |
import sys |
import os |
import tokenize |
+from collections import defaultdict |
+from contextlib import contextmanager |
from operator import attrgetter |
from warnings import warn |
- |
-from logilab.common.configuration import UnsupportedAction, OptionsManagerMixIn |
+from itertools import chain |
+try: |
+ import multiprocessing |
+except ImportError: |
+ multiprocessing = None |
+ |
+import six |
+from logilab.common.configuration import ( |
+ UnsupportedAction, OptionsManagerMixIn) |
from logilab.common.optik_ext import check_csv |
from logilab.common.interface import implements |
from logilab.common.textutils import splitstrip, unquote |
from logilab.common.ureports import Table, Text, Section |
from logilab.common.__pkginfo__ import version as common_version |
- |
from astroid import MANAGER, AstroidBuildingException |
from astroid.__pkginfo__ import version as astroid_version |
from astroid.modutils import load_module_from_name, get_module_part |
@@ -50,21 +59,37 @@ from pylint.utils import ( |
MSG_TYPES, OPTION_RGX, |
PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, ReportsHandlerMixIn, |
MessagesStore, FileState, EmptyReport, |
- expand_modules, tokenize_module) |
-from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker |
+ expand_modules, tokenize_module, Message) |
+from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker, CONFIDENCE_LEVELS |
from pylint.checkers import (BaseTokenChecker, |
table_lines_from_stats, |
initialize as checkers_initialize) |
-from pylint.reporters import initialize as reporters_initialize |
+from pylint.reporters import initialize as reporters_initialize, CollectingReporter |
from pylint import config |
- |
from pylint.__pkginfo__ import version |
+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.dirname(os.path.realpath( |
- os.path.expanduser(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 |
@@ -74,6 +99,20 @@ def _get_python_path(filepath): |
return os.getcwd() |
+def _merge_stats(stats): |
+ merged = {} |
+ for stat in 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 |
+ return merged |
+ |
+ |
# Python Linter class ######################################################### |
MSGS = { |
@@ -147,12 +186,63 @@ MSGS = { |
def _deprecated_option(shortname, opt_type): |
- def _warn_deprecated(option, optname, *args): |
+ def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argument |
sys.stderr.write('Warning: option %s is deprecated and ignored.\n' % (optname,)) |
return {'short': shortname, 'help': 'DEPRECATED', 'hide': True, |
'type': opt_type, 'action': 'callback', 'callback': _warn_deprecated} |
+if multiprocessing is not None: |
+ class ChildLinter(multiprocessing.Process): # pylint: disable=no-member |
+ def run(self): |
+ tasks_queue, results_queue, config = self._args # pylint: disable=no-member |
+ |
+ for file_or_module in iter(tasks_queue.get, 'STOP'): |
+ result = self._run_linter(config, file_or_module[0]) |
+ try: |
+ 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, config, file_or_module): |
+ linter = PyLinter() |
+ |
+ # Register standard checkers. |
+ linter.load_default_plugins() |
+ # Load command line plugins. |
+ # TODO linter.load_plugin_modules(self._plugins) |
+ |
+ linter.disable('pointless-except') |
+ linter.disable('suppressed-message') |
+ linter.disable('useless-suppression') |
+ |
+ # TODO(cpopa): the sub-linters will not know all the options |
+ # because they are not available here, as they are patches to |
+ # PyLinter options. The following is just a hack to handle |
+ # just a part of the options available in the Run class. |
+ |
+ if 'disable_msg' in config: |
+ # Disable everything again. We don't have access |
+ # to the original linter though. |
+ for msgid in config['disable_msg']: |
+ linter.disable(msgid) |
+ for key in set(config) - set(dict(linter.options)): |
+ del config[key] |
+ |
+ config['jobs'] = 1 # Child does not parallelize any further. |
+ linter.load_configuration(**config) |
+ linter.set_reporter(CollectingReporter()) |
+ |
+ # 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(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
BaseTokenChecker): |
"""lint Python modules using external checkers. |
@@ -174,7 +264,6 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
priority = 0 |
level = 0 |
msgs = MSGS |
- may_be_disabled = False |
@staticmethod |
def make_options(): |
@@ -238,6 +327,15 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
'help' : 'Add a comment according to your evaluation note. ' |
'This is used by the global evaluation report (RP0004).'}), |
+ ('confidence', |
+ {'type' : 'multiple_choice', 'metavar': '<levels>', |
+ 'default': '', |
+ 'choices': [c.name for c in 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 CONFIDENCE_LEVELS),)}), |
+ |
('enable', |
{'type' : 'csv', 'metavar': '<msg ids>', |
'short': 'e', |
@@ -275,6 +373,27 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
('include-ids', _deprecated_option('i', 'yn')), |
('symbols', _deprecated_option('s', 'yn')), |
+ |
+ ('jobs', |
+ {'type' : 'int', 'metavar': '<n-processes>', |
+ 'short': 'j', |
+ 'default': 1, |
+ 'help' : '''Use multiple processes to speed up Pylint.''', |
+ }), |
+ |
+ ('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.')}), |
+ |
+ ('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')} |
+ ), |
) |
option_groups = ( |
@@ -291,7 +410,8 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
self.reporter = None |
self._reporter_name = None |
self._reporters = {} |
- self._checkers = {} |
+ self._checkers = defaultdict(list) |
+ self._pragma_lineno = {} |
self._ignore_file = False |
# visit variables |
self.file_state = FileState() |
@@ -338,17 +458,6 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
if not self.reporter: |
self._load_reporter() |
- def prepare_import_path(self, args): |
- """Prepare sys.path for running the linter checks.""" |
- if len(args) == 1: |
- sys.path.insert(0, _get_python_path(args[0])) |
- else: |
- sys.path.insert(0, os.getcwd()) |
- |
- def cleanup_import_path(self): |
- """Revert any changes made to sys.path in prepare_import_path.""" |
- sys.path.pop(0) |
- |
def load_plugin_modules(self, modnames): |
"""take a list of module names which are pylint plugins and load |
and register them |
@@ -404,12 +513,24 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
try: |
BaseTokenChecker.set_option(self, optname, value, action, optdict) |
except UnsupportedAction: |
- print >> sys.stderr, 'option %s can\'t be read from config file' % \ |
- optname |
+ 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): |
@@ -418,7 +539,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
checker is an object implementing IRawChecker or / and IAstroidChecker |
""" |
assert checker.priority <= 0, 'checker priority can\'t be >= 0' |
- self._checkers.setdefault(checker.name, []).append(checker) |
+ 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) |
@@ -426,8 +547,13 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
self.msgs_store.register_messages(checker) |
checker.load_defaults() |
+ # Register the checker, but disable all of its messages. |
+ # TODO(cpopa): we should have a better API for this. |
+ 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.iteritems(): |
+ for msgcat, msgids in six.iteritems(self.msgs_store._msgs_by_category): |
if msgcat == 'E': |
for msgid in msgids: |
self.enable(msgid) |
@@ -437,7 +563,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
def disable_reporters(self): |
"""disable all reporters""" |
- for reporters in self._reports.itervalues(): |
+ for reporters in six.itervalues(self._reports): |
for report_id, _title, _cb in reporters: |
self.disable_report(report_id) |
@@ -456,6 +582,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
"""process tokens from the current module to search for module/block |
level options |
""" |
+ control_pragmas = {'disable', 'enable'} |
for (tok_type, content, start, _, _) in tokens: |
if tok_type != tokenize.COMMENT: |
continue |
@@ -485,6 +612,10 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
# found a "(dis|en)able-msg" pragma deprecated suppresssion |
self.add_message('deprecated-pragma', line=start[0], args=(opt, opt.replace('-msg', ''))) |
for msgid in splitstrip(value): |
+ # Add the line where a control pragma was encountered. |
+ if opt in control_pragmas: |
+ self._pragma_lineno[msgid] = start[0] |
+ |
try: |
if (opt, msgid) == ('disable', 'all'): |
self.add_message('deprecated-pragma', line=start[0], args=('disable=all', 'skip-file')) |
@@ -502,7 +633,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
def get_checkers(self): |
"""return all available checkers as a list""" |
- return [self] + [c for checkers in self._checkers.itervalues() |
+ return [self] + [c for checkers in six.itervalues(self._checkers) |
for c in checkers if c is not self] |
def prepare_checkers(self): |
@@ -523,7 +654,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
reverse=True) |
return neededcheckers |
- def should_analyze_file(self, modname, path): # pylint: disable=unused-argument |
+ def should_analyze_file(self, modname, path): # pylint: disable=unused-argument, no-self-use |
"""Returns whether or not a module should be checked. |
This implementation returns True for all python source file, indicating |
@@ -551,6 +682,99 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
if not isinstance(files_or_modules, (list, tuple)): |
files_or_modules = (files_or_modules,) |
+ |
+ if self.config.jobs == 1: |
+ self._do_check(files_or_modules) |
+ else: |
+ # Hack that permits running pylint, on Windows, with -m switch |
+ # and with --jobs, as in 'py -2 -m pylint .. --jobs'. |
+ # For more details why this is needed, |
+ # see Python issue http://bugs.python.org/issue10845. |
+ |
+ mock_main = six.PY2 and __name__ != '__main__' # -m switch |
+ if mock_main: |
+ sys.modules['__main__'] = sys.modules[__name__] |
+ try: |
+ self._parallel_check(files_or_modules) |
+ finally: |
+ if mock_main: |
+ sys.modules.pop('__main__') |
+ |
+ def _parallel_task(self, files_or_modules): |
+ # Prepare configuration for child linters. |
+ config = vars(self.config) |
+ childs = [] |
+ manager = multiprocessing.Manager() # pylint: disable=no-member |
+ tasks_queue = manager.Queue() # pylint: disable=no-member |
+ results_queue = manager.Queue() # pylint: disable=no-member |
+ |
+ for _ in range(self.config.jobs): |
+ cl = ChildLinter(args=(tasks_queue, results_queue, config)) |
+ cl.start() # pylint: disable=no-member |
+ childs.append(cl) |
+ |
+ # send files to child linters |
+ for files_or_module in files_or_modules: |
+ tasks_queue.put([files_or_module]) |
+ |
+ # collect results from child linters |
+ failed = False |
+ for _ in files_or_modules: |
+ try: |
+ result = results_queue.get() |
+ except Exception as ex: |
+ print("internal error while receiving results from child linter", |
+ file=sys.stderr) |
+ print(ex, file=sys.stderr) |
+ failed = True |
+ break |
+ yield result |
+ |
+ # Stop child linters and wait for their completion. |
+ for _ in range(self.config.jobs): |
+ tasks_queue.put('STOP') |
+ for cl in childs: |
+ cl.join() |
+ |
+ if failed: |
+ print("Error occured, stopping the linter.", file=sys.stderr) |
+ sys.exit(32) |
+ |
+ def _parallel_check(self, files_or_modules): |
+ # Reset stats. |
+ self.open() |
+ |
+ all_stats = [] |
+ for result in self._parallel_task(files_or_modules): |
+ ( |
+ file_or_module, |
+ self.file_state.base_name, |
+ module, |
+ messages, |
+ stats, |
+ msg_status |
+ ) = result |
+ |
+ if file_or_module == files_or_modules[-1]: |
+ last_module = module |
+ |
+ for msg in messages: |
+ msg = Message(*msg) |
+ self.set_current_module(module) |
+ self.reporter.handle_message(msg) |
+ |
+ all_stats.append(stats) |
+ self.msg_status |= msg_status |
+ |
+ self.stats = _merge_stats(chain(all_stats, [self.stats])) |
+ self.current_name = last_module |
+ |
+ # Insert stats data to local checkers. |
+ for checker in self.get_checkers(): |
+ if checker is not self: |
+ checker.stats = self.stats |
+ |
+ def _do_check(self, files_or_modules): |
walker = PyLintASTWalker(self) |
checkers = self.prepare_checkers() |
tokencheckers = [c for c in checkers if implements(c, ITokenChecker) |
@@ -587,7 +811,6 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
for msgid, line, args in self.file_state.iter_spurious_suppression_messages(self.msgs_store): |
self.add_message(msgid, line, None, args) |
# notify global end |
- self.set_current_module('') |
self.stats['statement'] = walker.nbstatements |
checkers.reverse() |
for checker in checkers: |
@@ -617,28 +840,42 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
self.current_file = filepath or modname |
self.stats['by_module'][modname] = {} |
self.stats['by_module'][modname]['statement'] = 0 |
- for msg_cat in MSG_TYPES.itervalues(): |
+ for msg_cat in six.itervalues(MSG_TYPES): |
self.stats['by_module'][modname][msg_cat] = 0 |
def get_ast(self, filepath, modname): |
"""return a ast(roid) representation for a module""" |
try: |
return MANAGER.ast_from_file(filepath, modname, source=True) |
- except SyntaxError, ex: |
+ except SyntaxError as ex: |
self.add_message('syntax-error', line=ex.lineno, args=ex.msg) |
- except AstroidBuildingException, ex: |
+ except AstroidBuildingException as ex: |
self.add_message('parse-error', args=ex) |
- except Exception, ex: |
+ except Exception as ex: # pylint: disable=broad-except |
import traceback |
traceback.print_exc() |
self.add_message('astroid-error', args=(ex.__class__, ex)) |
def check_astroid_module(self, astroid, walker, rawcheckers, tokencheckers): |
"""check a module from its astroid representation, real work""" |
+ try: |
+ return self._check_astroid_module(astroid, walker, |
+ rawcheckers, tokencheckers) |
+ finally: |
+ # Close file_stream, if opened, to avoid to open many files. |
+ if astroid.file_stream: |
+ astroid.file_stream.close() |
+ # TODO(cpopa): This is an implementation detail, but it will |
+ # be moved in astroid at some point. |
+ # We invalidate the cached property, to let the others |
+ # modules which relies on this one to get a new file stream. |
+ del astroid.file_stream |
+ |
+ def _check_astroid_module(self, astroid, walker, rawcheckers, tokencheckers): |
# call raw checkers if possible |
try: |
tokens = tokenize_module(astroid) |
- except tokenize.TokenError, ex: |
+ except tokenize.TokenError as ex: |
self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0]) |
return |
@@ -669,10 +906,12 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
self.stats = {'by_module' : {}, |
'by_msg' : {}, |
} |
- for msg_cat in MSG_TYPES.itervalues(): |
+ MANAGER.always_load_extensions = self.config.unsafe_load_any_extension |
+ MANAGER.extension_package_whitelist.update(self.config.extension_pkg_whitelist) |
+ for msg_cat in six.itervalues(MSG_TYPES): |
self.stats[msg_cat] = 0 |
- def close(self): |
+ def generate_reports(self): |
"""close the whole package /module, it's time to make reports ! |
if persistent run, pickle results for later comparison |
@@ -695,6 +934,11 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
if self.config.persistent: |
config.save_results(self.stats, self.file_state.base_name) |
else: |
+ if self.config.output_format == 'html': |
+ # No output will be emitted for the html |
+ # reporter if the file doesn't exist, so emit |
+ # the results here. |
+ self.reporter.display_results(Section()) |
self.reporter.on_close(self.stats, {}) |
# specific reports ######################################################## |
@@ -708,8 +952,8 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, |
# get a global note for the code |
evaluation = self.config.evaluation |
try: |
- note = eval(evaluation, {}, self.stats) |
- except Exception, ex: |
+ note = eval(evaluation, {}, self.stats) # pylint: disable=eval-used |
+ except Exception as ex: # pylint: disable=broad-except |
msg = 'An exception occurred while rating: %s' % ex |
else: |
stats['global_note'] = note |
@@ -737,7 +981,7 @@ def report_messages_stats(sect, stats, _): |
# don't print this report when we didn't detected any errors |
raise EmptyReport() |
in_order = sorted([(value, msg_id) |
- for msg_id, value in stats['by_msg'].iteritems() |
+ for msg_id, value in six.iteritems(stats['by_msg']) |
if not msg_id.startswith('I')]) |
in_order.reverse() |
lines = ('message id', 'occurrences') |
@@ -750,18 +994,18 @@ def report_messages_by_module_stats(sect, stats, _): |
if len(stats['by_module']) == 1: |
# don't print this report when we are analysing a single module |
raise EmptyReport() |
- by_mod = {} |
+ by_mod = defaultdict(dict) |
for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'): |
total = stats[m_type] |
- for module in stats['by_module'].iterkeys(): |
+ for module in six.iterkeys(stats['by_module']): |
mod_total = stats['by_module'][module][m_type] |
if total == 0: |
percent = 0 |
else: |
percent = float((mod_total)*100) / total |
- by_mod.setdefault(module, {})[m_type] = percent |
+ by_mod[module][m_type] = percent |
sorted_result = [] |
- for module, mod_info in by_mod.iteritems(): |
+ for module, mod_info in six.iteritems(by_mod): |
sorted_result.append((mod_info['error'], |
mod_info['warning'], |
mod_info['refactor'], |
@@ -771,8 +1015,9 @@ def report_messages_by_module_stats(sect, stats, _): |
sorted_result.reverse() |
lines = ['module', 'error', 'warning', 'refactor', 'convention'] |
for line in sorted_result: |
- if line[0] == 0 and line[1] == 0: |
- break |
+ # 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) |
@@ -783,12 +1028,6 @@ def report_messages_by_module_stats(sect, stats, _): |
# utilities ################################################################### |
-# this may help to import modules using gettext |
-# XXX syt, actually needed since we don't import code? |
- |
-from logilab.common.compat import builtins |
-builtins._ = str |
- |
class ArgumentPreprocessingError(Exception): |
"""Raised if an error occurs during argument preprocessing.""" |
@@ -828,6 +1067,31 @@ def preprocess_options(args, search_for): |
else: |
i += 1 |
+ |
+@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 exitign this context. |
+ """ |
+ orig = list(sys.path) |
+ changes = [] |
+ for arg in args: |
+ path = _get_python_path(arg) |
+ if path in changes: |
+ continue |
+ else: |
+ changes.append(path) |
+ sys.path[:] = changes + sys.path |
+ try: |
+ yield |
+ finally: |
+ sys.path[:] = orig |
+ |
+ |
class Run(object): |
"""helper class to use as main for pylint : |
@@ -849,8 +1113,8 @@ group are mutually exclusive.'), |
'rcfile': (self.cb_set_rcfile, True), |
'load-plugins': (self.cb_add_plugins, True), |
}) |
- except ArgumentPreprocessingError, ex: |
- print >> sys.stderr, ex |
+ except ArgumentPreprocessingError as ex: |
+ print(ex, file=sys.stderr) |
sys.exit(32) |
self.linter = linter = self.LinterClass(( |
@@ -879,6 +1143,12 @@ group are mutually exclusive.'), |
'group': 'Commands', 'level': 1, |
'help' : "Generate pylint's messages."}), |
+ ('list-conf-levels', |
+ {'action' : 'callback', |
+ 'callback' : cb_list_confidence_levels, |
+ 'group': 'Commands', 'level': 1, |
+ 'help' : "Generate pylint's messages."}), |
+ |
('full-documentation', |
{'action' : 'callback', 'metavar': '<msg-id>', |
'callback' : self.cb_full_documentation, |
@@ -905,6 +1175,12 @@ group are mutually exclusive.'), |
'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'}), |
+ |
('profile', |
{'type' : 'yn', 'metavar' : '<y_or_n>', |
'default': False, 'hide': True, |
@@ -968,28 +1244,42 @@ group are mutually exclusive.'), |
linter.set_reporter(reporter) |
try: |
args = linter.load_command_line_configuration(args) |
- except SystemExit, exc: |
+ except SystemExit as exc: |
if exc.code == 2: # bad options |
exc.code = 32 |
raise |
if not args: |
- print linter.help() |
+ print(linter.help()) |
+ sys.exit(32) |
+ |
+ if linter.config.jobs < 0: |
+ print("Jobs number (%d) should be greater than 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) |
+ else: |
+ if linter.config.jobs == 0: |
+ linter.config.jobs = multiprocessing.cpu_count() |
+ |
# insert current working directory to the python path to have a correct |
# behaviour |
- linter.prepare_import_path(args) |
- if self.linter.config.profile: |
- print >> sys.stderr, '** profiled run' |
- import cProfile, pstats |
- cProfile.runctx('linter.check(%r)' % args, globals(), locals(), |
- 'stones.prof') |
- data = pstats.Stats('stones.prof') |
- data.strip_dirs() |
- data.sort_stats('time', 'calls') |
- data.print_stats(30) |
- else: |
- linter.check(args) |
- linter.cleanup_import_path() |
+ with fix_import_path(args): |
+ if self.linter.config.profile: |
+ print('** profiled run', file=sys.stderr) |
+ import cProfile, pstats |
+ cProfile.runctx('linter.check(%r)' % args, globals(), locals(), |
+ 'stones.prof') |
+ data = pstats.Stats('stones.prof') |
+ data.strip_dirs() |
+ data.sort_stats('time', 'calls') |
+ data.print_stats(30) |
+ else: |
+ linter.check(args) |
+ linter.generate_reports() |
if exit: |
sys.exit(self.linter.msg_status) |
@@ -1037,9 +1327,20 @@ group are mutually exclusive.'), |
self.linter.msgs_store.list_messages() |
sys.exit(0) |
+ def cb_python3_porting_mode(self, *args, **kwargs): |
+ """Activate only the python3 porting checker.""" |
+ self.linter.disable('all') |
+ self.linter.enable('python3') |
+ |
+ |
+def cb_list_confidence_levels(option, optname, value, parser): |
+ for level in 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 |
+ exec(value) # pylint: disable=exec-used |
if __name__ == '__main__': |