From cd41c2b82500220e77a70f7c3143350ada6c9f25 Mon Sep 17 00:00:00 2001 From: Ashley Whetter Date: Thu, 17 May 2018 09:03:04 -0700 Subject: Linters are created per file --- pylint/lint.py | 123 ++++++++++++++++++++++++++++++++++++-------------------- pylint/utils.py | 11 ++--- 2 files changed, 86 insertions(+), 48 deletions(-) diff --git a/pylint/lint.py b/pylint/lint.py index 390efa2d2..131a07e0c 100644 --- a/pylint/lint.py +++ b/pylint/lint.py @@ -107,6 +107,7 @@ def _get_python_path(filepath): def _merge_stats(stats): merged = {} by_msg = collections.Counter() + nested_keys = set() for stat in stats: message_stats = stat.pop("by_msg", {}) by_msg.update(message_stats) @@ -116,11 +117,17 @@ def _merge_stats(stats): merged[key] = item else: if isinstance(item, dict): + nested_keys.add(key) + elif isinstance(item, set): merged[key].update(item) else: merged[key] = merged[key] + item - merged["by_msg"] = by_msg + for key in nested_keys: + merged[key] = _merge_stats(stat.get(key, {}) for stat in stats) + + if by_msg: + merged["by_msg"] = by_msg return merged @@ -486,6 +493,19 @@ class PyLinter(utils.MessagesHandlerMixIn, checkers.BaseTokenChecker): ), }, ), + ( + "limit-inference-results", + { + "type": "int", + "metavar": "", + "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", { @@ -516,7 +536,8 @@ class PyLinter(utils.MessagesHandlerMixIn, checkers.BaseTokenChecker): ( "exit-zero", { - "action": "store_true", + "type": "yn", + "default": False, "help": ( "Always return a 0 (non-error) status code, even if " "lint errors are found. This is primarily useful in " @@ -775,18 +796,19 @@ class PyLinter(utils.MessagesHandlerMixIn, checkers.BaseTokenChecker): walker.walk(ast_node) return True - # IAstroidChecker interface ################################################# - def open(self): - """initialize counters""" - self.stats.clear() - self.stats["by_module"] = {} - self.stats["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 utils.MSG_TYPES.values(): - self.stats[msg_cat] = 0 + self._init_msg_states() + + def add_stats(self, **kwargs): + """add some stats entries to the statistic dictionary + raise an AssertionError if there is a key conflict + """ + for key, value in kwargs.items(): + if key[-1] == "_": + key = key[:-1] + assert key not in self.stats + self.stats[key] = value + return self.stats # utilities ################################################################### @@ -929,18 +951,21 @@ class PluginRegistry(utils.MessagesHandlerMixIn, ReportRegistry): :raises InvalidCheckerError: If the priority of the checker is invalid. """ + # Allow instances to be passed for backwards compatibility + if isinstance(checker, checkers.BaseChecker): + checker = checker.__class__ + existing_checker_types = set( - type(existing_checker) + existing_checker for name_checkers in self._checkers.values() for existing_checker in name_checkers ) - checker_type = type(checker) - if checker_type in existing_checker_types: + if checker in existing_checker_types: msg_fmt = ( "Not registering checker {}. A checker of type {} has " "already been registered." ) - msg = msg_fmt.format(checker.name, checker_type.__name__) + msg = msg_fmt.format(checker.name, checker.__name__) warnings.warn(msg) return @@ -1367,7 +1392,7 @@ group are mutually exclusive.", self.generate_reports(base_name) - if self._global_config.config.exit_zero: + if self._global_config.exit_zero: sys.exit(0) else: sys.exit(status_code) @@ -1569,35 +1594,26 @@ group are mutually exclusive.", # pylint: enable=unused-argument + def close_registration(self): + """Stop registering plugins and prepare everything for checking.""" + MANAGER.always_load_extensions = self._global_config.unsafe_load_any_extension + MANAGER.max_inferable_values = self._global_config.limit_inference_results + MANAGER.extension_package_whitelist.update( + self._global_config.extension_pkg_whitelist + ) + def check(self, files_or_modules, checkers_): """main checking entry: check a list of files or modules from their name. """ - # initialize msgs_state now that all messages have been registered into - # the store - self._plugin_registry.init_msg_states() + # initialize msgs_state now that all messages have been registered into the store + self.close_registration() if not isinstance(files_or_modules, (list, tuple)): files_or_modules = (files_or_modules,) - walker = utils.PyLintASTWalker(self._plugin_registry) - tokencheckers = [ - c for c in checkers_ if interfaces.implements(c, interfaces.ITokenChecker) - ] - rawcheckers = [ - c for c in checkers_ if interfaces.implements(c, interfaces.IRawChecker) - ] # notify global begin - linter = PyLinter(self._global_config) - linter.config = self._global_config - linter.msgs_store = self._plugin_registry.msgs_store - linter.stats = self._plugin_registry.stats - linter.open() - for checker in checkers_: - checker.linter = linter - checker.open() - if interfaces.implements(checker, interfaces.IAstroidChecker): - walker.add_checker(checker) + all_stats = [self._plugin_registry.stats] # build ast and check modules or packages expanded_files = utils.expand_files( files_or_modules, @@ -1606,6 +1622,26 @@ group are mutually exclusive.", self._global_config.black_list_re, ) for module_desc in expanded_files: + linter = PyLinter(self._global_config) + linter.msgs_store = self._plugin_registry.msgs_store + linter.open() + + walker = utils.PyLintASTWalker(self._plugin_registry) + allcheckers = [] + tokencheckers = [linter] + rawcheckers = [] + for checker_cls in checkers_: + checker = checker_cls(linter) + checker.linter = linter + checker.open() + allcheckers.append(checker) + if interfaces.implements(checker, interfaces.ITokenChecker): + tokencheckers.append(checker) + if interfaces.implements(checker, interfaces.IRawChecker): + rawcheckers.append(checker) + if interfaces.implements(checker, interfaces.IAstroidChecker): + walker.add_checker(checker) + modname = module_desc.name filepath = module_desc.path if not module_desc.isarg and not self.should_analyze_file( @@ -1615,12 +1651,13 @@ group are mutually exclusive.", linter.reporter = self._reporter linter.check(module_desc, walker, rawcheckers, tokencheckers) + self._plugin_registry.stats["statement"] += walker.nbstatements + all_stats.append(linter.stats) + + for checker in reversed(allcheckers): + checker.close() - # notify global end - self._plugin_registry.stats["statement"] = walker.nbstatements - for checker in reversed(checkers_): - checker.close() - linter.close() + self._plugin_registry.stats = _merge_stats(all_stats) return module_desc.basename, linter.msg_status diff --git a/pylint/utils.py b/pylint/utils.py index 4a5966656..88c1100c5 100644 --- a/pylint/utils.py +++ b/pylint/utils.py @@ -203,13 +203,12 @@ def build_message_def(checker, msgid, msg_tuple): ) symbol = None options.setdefault("scope", default_scope) - return MessageDefinition(checker, msgid, msg, descr, symbol, **options) + return MessageDefinition(msgid, msg, descr, symbol, **options) class MessageDefinition: def __init__( self, - checker, msgid, msg, descr, @@ -219,7 +218,6 @@ class MessageDefinition: maxversion=None, old_names=None, ): - self.checker = checker if len(msgid) != 5: raise InvalidMessageError("Invalid message id %r" % msgid) if not msgid[0] in MSG_TYPES: @@ -287,7 +285,7 @@ class MessagesHandlerMixIn: self.msg_status = 0 self.msgs_store = MessagesStore() self.reporter = None - self.stats = {"by_module": {}, "by_msg": {}} + self.stats = {"by_module": {}, "by_msg": {}, "statement": 0} super().__init__() def _checker_messages(self, checker): @@ -418,11 +416,14 @@ class MessagesHandlerMixIn: for msgid in msgids: self.disable(msgid) - def init_msg_states(self): + def _init_msg_states(self): for msg in self.msgs_store.messages: if not msg.may_be_emitted(): self._msgs_state[msg.msgid] = False + for msg_cat in MSG_TYPES.values(): + self.stats[msg_cat] = 0 + def get_message_state_scope(self, msgid, line=None, confidence=UNDEFINED): """Returns the scope at which a message was enabled/disabled.""" if self.config.confidence and confidence.name not in self.config.confidence: -- cgit v1.2.1