From 8068def24eac8498cc28563b42314b50fac1c5b8 Mon Sep 17 00:00:00 2001 From: Ashley Whetter Date: Sat, 24 Feb 2018 15:19:38 -0800 Subject: Moved some reporting functions to runner --- pylint/lint.py | 245 ++++++++++++++++++++++++----------------------- pylint/reporters/text.py | 2 +- pylint/utils.py | 41 ++++++-- 3 files changed, 160 insertions(+), 128 deletions(-) diff --git a/pylint/lint.py b/pylint/lint.py index 559b078b0..d129eb9af 100644 --- a/pylint/lint.py +++ b/pylint/lint.py @@ -570,47 +570,6 @@ class PyLinter( if hasattr(module, "load_configuration"): module.load_configuration(self) - def load_reporter(self): - name = self.config.output_format.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.config.output_format - 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 register_reporter(self, reporter_class): - self._reporters[reporter_class.name] = reporter_class - if reporter_class.name == self.config.output_format.lower(): - self.load_reporter() - - 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 disable_noerror_messages(self): @@ -791,7 +750,6 @@ class PyLinter( """main checking entry: check a list of files or modules from their name. """ - assert self.reporter, "A reporter has not been loaded" # initialize msgs_state now that all messages have been registered into # the store self._init_msg_states() @@ -924,58 +882,6 @@ class PyLinter( for msg_cat in utils.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) - # XXX code below needs refactoring to be more reporter agnostic - 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) - 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, {}) - - 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) - previous_stats = config.load_results(self.file_state.base_name) - if self.stats["statement"] == 0: - return - - # 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) - # utilities ################################################################### @@ -1032,8 +938,18 @@ class PluginRegistry(object): self._checkers = collections.defaultdict(list) # TODO: Remove. This is needed for the MessagesHandlerMixIn for now. linter._checkers = self._checkers + self._reporters = {} + linter._reporters = self._reporters self._linter = linter - self.register_checker(linter) + + # TODO: Move elsewhere + for r_id, r_title, r_cb in linter.reports: + self._linter.register_post_report(r_id, r_title, r_cb) + + self.register_options(linter.options) + + # TODO: Move elsewhere + linter.msgs_store.register_messages(linter) def for_all_checkers(self): """Loop through all registered checkers. @@ -1081,6 +997,16 @@ class PluginRegistry(object): if not getattr(checker, "enabled", True): self._linter.disable(checker.name) + def register_reporter(self, reporter_class): + if reporter_class.name in self._reporters: + # TODO: Raise if classes are the same + duplicate = self._reporters[reporter_class.name] + msg = "A reporter called {} has already been registered ({})." + msg = msg.format(reporter.name, duplicate.__class__) + warnings.warn(msg) + + self._reporters[reporter_class.name] = reporter_class + # For now simply defer missing attributs to the linter, # until we know what API we want. def __getattr__(self, attribute): @@ -1231,10 +1157,12 @@ group are mutually exclusive.", self._linter = PyLinter() self._plugin_registry = PluginRegistry(self._linter) self._loaded_plugins = set() + self._global_config = config.Configuration() + self._reporter = None def run(self, args): # Phase 1: Preprocessing - option_definitions = self.option_definitions + self._linter.options + option_definitions = self.option_definitions + PyLinter.options parser = config.CLIParser(self.description) parser.add_option_definitions(option_definitions) parser.add_help_section("Environment variables", config.ENV_HELP, level=1) @@ -1270,9 +1198,8 @@ group are mutually exclusive.", level=1, ) - global_config = config.Configuration() - global_config.add_options(option_definitions) - self._linter.config = global_config + self._global_config.add_options(option_definitions) + self._linter.config = self._global_config parsed = parser.preprocess( args, "init_hook", "rcfile", "load_plugins", "ignore", "ignore_patterns" @@ -1284,13 +1211,13 @@ group are mutually exclusive.", # Load rcfile, else system rcfile file_parser = config.IniFileParser() - file_parser.add_option_definitions(self._linter.options) + file_parser.add_option_definitions(PyLinter.options) rcfile = parsed.rcfile or config.PYLINTRC if rcfile: - file_parser.parse(rcfile, global_config) + file_parser.parse(rcfile, self._global_config) def register_options(options): - global_config.add_options(options) + self._global_config.add_options(options) parser.add_option_definitions(options) file_parser.add_option_definitions(options) @@ -1312,37 +1239,37 @@ group are mutually exclusive.", # Phase 3: Full load # Fully load config files if rcfile: - file_parser.parse(rcfile, global_config) + file_parser.parse(rcfile, self._global_config) # Fully load CLI into global config - parser.parse(args, global_config) + parser.parse(args, self._global_config) - if global_config.generate_rcfile: + if self._global_config.generate_rcfile: file_parser.write() sys.exit(0) # TODO: if global_config.generate_man - if global_config.errors_only: + if self._global_config.errors_only: self._linter.errors_mode() - if global_config.py3k: + if self._global_config.py3k: self._linter.python3_porting_mode() - if global_config.full_documentation: + if self._global_config.full_documentation: self._linter.print_full_documentation() sys.exit(0) - if global_config.list_conf_levels: + if self._global_config.list_conf_levels: for level in interfaces.CONFIDENCE_LEVELS: print("%-18s: %s" % level) sys.exit(0) - if global_config.list_msgs: + if self._global_config.list_msgs: self._linter.msgs_store.list_messages() sys.exit(0) - if global_config.help_msg: - msg = utils._splitstrip(global_config.help_msg) + if self._global_config.help_msg: + msg = utils._splitstrip(self._global_config.help_msg) self._linter.msgs_store.help_message(msg) sys.exit(0) @@ -1352,13 +1279,13 @@ group are mutually exclusive.", self._linter.enable("c-extension-no-member") for checker in self._plugin_registry.for_all_checkers(): - checker.config = global_config + checker.config = self._global_config - with fix_import_path(global_config.module_or_package): - assert self._linter.config.jobs == 1 - self._linter.check(global_config.module_or_package) + with fix_import_path(self._global_config.module_or_package): + assert self._global_config.jobs == 1 + self._linter.check(self._global_config.module_or_package) - self._linter.generate_reports() + self.generate_reports() if linter.config.exit_zero: sys.exit(0) @@ -1384,11 +1311,89 @@ group are mutually exclusive.", def load_default_plugins(self): """Load all of the default plugins.""" - reporters.initialize(self._linter) + reporters.initialize(self._plugin_registry) # Make sure to load the default reporter, because # the option has been set before the plugins had been loaded. - if not self._linter.reporter: - self._linter.load_reporter() + if not self._reporter: + self.load_reporter() + + def load_reporter(self): + name = self._global_config.output_format.lower() + if name in self._plugin_registry._reporters: + self._reporter = self._plugin_registry._reporters[name]() + self._linter.reporter = self._reporter + # TODO: Remove the need to do this + self._reporter.linter = self._linter + else: + try: + reporter_class = self._load_reporter_class() + except (ImportError, AttributeError): + raise exceptions.InvalidReporterError(name) + else: + self._reporter = reporter_class() + self._linter.reporter = self._reporter + self._reporter.linter = self._linter + + def _load_reporter_class(self): + qname = self._global_config.output_format + 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 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._linter.file_state.base_name is not None: + # load previous results if any + previous_stats = config.load_results(self._linter.file_state.base_name) + # XXX code below needs refactoring to be more reporter agnostic + self._reporter.on_close(self._linter.stats, previous_stats) + if self._global_config.reports: + sect = self._linter.make_reports(self._linter.stats, previous_stats) + else: + sect = report_nodes.Section() + + if self._global_config.reports: + self._reporter.display_reports(sect) + self._report_evaluation() + # save results if persistent run + if self._global_config.persistent: + config.save_results( + self._linter.stats, self._linter.file_state.base_name + ) + else: + self._reporter.on_close(self._linter.stats, {}) + + 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) + previous_stats = config.load_results(self._linter.file_state.base_name) + if self._linter.stats["statement"] == 0: + return + + # get a global note for the code + evaluation = self._global_config.evaluation + try: + note = eval(evaluation, {}, self._linter.stats) # pylint: disable=eval-used + except Exception as ex: + msg = "An exception occurred while rating: %s" % ex + else: + self._linter.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._global_config.score: + sect = report_nodes.EvaluationSection(msg) + self._reporter.display_reports(sect) if __name__ == "__main__": diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py index 4c682ae05..07a6a2c53 100644 --- a/pylint/reporters/text.py +++ b/pylint/reporters/text.py @@ -132,7 +132,7 @@ class TextReporter(BaseReporter): def __init__(self, output=None): BaseReporter.__init__(self, output) self._modules = set() - self._template = None + self._template = self.line_format def on_set_current_module(self, module, filepath): self._template = str(self.linter.config.msg_template or self.line_format) diff --git a/pylint/utils.py b/pylint/utils.py index aa6535425..f8baa96a7 100644 --- a/pylint/utils.py +++ b/pylint/utils.py @@ -1061,28 +1061,55 @@ class ReportsHandlerMixIn: related methods for the main lint class """ + _POST_CHECKER = object() + def __init__(self): self._reports = collections.defaultdict(list) self._reports_state = {} super().__init__() def report_order(self): - """ Return a list of reports, sorted in the order - in which they must be called. + """A list of reports, sorted in the order in which they must be called. + + :returns: The list of reports. + :rtype: list(BaseChecker or object) """ - return list(self._reports) + reports = sorted(self._reports, key=lambda x: getattr(x, "name", "")) + try: + reports.remove(self._POST_CHECKER) + except ValueError: + pass + else: + reports.append(self._POST_CHECKER) + return reports def register_report(self, reportid, r_title, r_cb, checker): """register a report - reportid is the unique identifier for the report - r_title the report's title - r_cb the method to call to make the report - checker is the checker defining the report + :param reportid: The unique identifier for the report. + :type reportid: str + :param r_title: The report's title. + :type r_title: str + :param r_cb: The method to call to make the report. + :type r_cb: callable + :param checker: The checker defining the report. + :type checker: BaseChecker """ reportid = reportid.upper() self._reports[checker].append((reportid, r_title, r_cb)) + def register_post_report(self, reportid, r_title, r_cb): + """Register a report to run last. + + :param reportid: The unique identifier for the report. + :type reportid: str + :param r_title: The report's title. + :type r_title: str + :param r_cb: The method to call to make the report. + :type r_cb: callable + """ + self.register_report(reportid, r_title, r_cb, self._POST_CHECKER) + def enable_report(self, reportid): """disable the report of the given id""" reportid = reportid.upper() -- cgit v1.2.1