From 711361e89282a4ef7ef3284371c89996d3489456 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Thu, 1 Jun 2017 16:35:46 -0700 Subject: [PATCH 1/3] Add Any Expressions report This report measures the number of expressions in a file that have type Any. --- mypy/report.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/mypy/report.py b/mypy/report.py index 157aa1caab6f..afe1a6114a2a 100644 --- a/mypy/report.py +++ b/mypy/report.py @@ -144,6 +144,58 @@ def on_finish(self) -> None: register_reporter('linecount', LineCountReporter) +class AnyExpressionsReporter(AbstractReporter): + def __init__(self, reports: Reports, output_dir: str) -> None: + super().__init__(reports, output_dir) + self.counts = {} # type: Dict[str, Tuple[int, int]] + stats.ensure_dir_exists(output_dir) + + def on_file(self, + tree: MypyFile, + type_map: Dict[Expression, Type], + options: Options) -> None: + visitor = stats.StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True) + tree.accept(visitor) + num_total = visitor.num_imprecise + visitor.num_precise + visitor.num_any + if num_total > 0: + self.counts[tree.fullname()] = (visitor.num_any, num_total) + + def on_finish(self) -> None: + total_any = sum(num_any for num_any, _ in self.counts.values()) + total_expr = sum(total for _, total in self.counts.values()) + total_coverage = (float(total_expr - total_any) / float(total_expr)) * 100 + + any_column_name = "Anys" + total_column_name = "Exprs" + file_column_name = "Name" + # find the longest filename all files + name_width = max([len(file) for file in self.counts] + [len(file_column_name)]) + # totals are the largest numbers in their column - no need to look at others + any_width = max(len(str(total_any)) + 3, len(any_column_name)) + exprs_width = max(len(str(total_expr)) + 3, len(total_column_name)) + header = '{:{name_width}} {:>{any_width}} {:>{total_width}} {:>11}'.format( + file_column_name, any_column_name, total_column_name, "Coverage", + name_width=name_width, any_width=any_width, total_width=exprs_width) + + with open(os.path.join(self.output_dir, 'any-exprs.txt'), 'w') as f: + f.write(header + '\n') + separator = '-' * len(header) + '\n' + f.write(separator) + for file in sorted(self.counts): + (num_any, num_total) = self.counts[file] + coverage = (float(num_total - num_any) / float(num_total)) * 100 + f.write('{:{name_width}} {:{any_width}} {:{total_width}} {:>10.2f}%\n'. + format(file, num_any, num_total, coverage, name_width=name_width, + any_width=any_width, total_width=exprs_width)) + f.write(separator) + f.write('{:{name_width}} {:{any_width}} {:{total_width}} {:>10.2f}%\n'.format('Total', + total_any, total_expr, total_coverage, name_width=name_width, + any_width=any_width, total_width=exprs_width)) + + +register_reporter('any-exprs', AnyExpressionsReporter) + + class LineCoverageVisitor(TraverserVisitor): def __init__(self, source: List[str]) -> None: self.source = source From feb78f8949724c1ef7301369bc3ded937fbd28e3 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Fri, 2 Jun 2017 10:30:30 -0700 Subject: [PATCH 2/3] StatisticsVisitor: Report filename and line number when there's an error If there's a cycle in function expansion, report filename and line number --- mypy/report.py | 9 ++++++--- mypy/stats.py | 11 +++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/mypy/report.py b/mypy/report.py index afe1a6114a2a..3c3a7025bef2 100644 --- a/mypy/report.py +++ b/mypy/report.py @@ -154,7 +154,8 @@ def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type], options: Options) -> None: - visitor = stats.StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True) + visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname(), + typemap=type_map, all_nodes=True) tree.accept(visitor) num_total = visitor.num_imprecise + visitor.num_precise + visitor.num_any if num_total > 0: @@ -372,7 +373,8 @@ def on_file(self, if 'stubs' in path.split('/'): return - visitor = stats.StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True) + visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname(), + typemap=type_map, all_nodes=True) tree.accept(visitor) root = etree.Element('mypy-report-file', name=path, module=tree._fullname) @@ -477,7 +479,8 @@ def on_file(self, type_map: Dict[Expression, Type], options: Options) -> None: path = os.path.relpath(tree.path) - visitor = stats.StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True) + visitor = stats.StatisticsVisitor(inferred=True, filename=tree.fullname(), + typemap=type_map, all_nodes=True) tree.accept(visitor) class_name = os.path.basename(path) diff --git a/mypy/stats.py b/mypy/stats.py index 3739763069bd..10a9e95dcf5f 100644 --- a/mypy/stats.py +++ b/mypy/stats.py @@ -30,9 +30,10 @@ class StatisticsVisitor(TraverserVisitor): - def __init__(self, inferred: bool, typemap: Dict[Expression, Type] = None, + def __init__(self, inferred: bool, filename: str, typemap: Dict[Expression, Type] = None, all_nodes: bool = False) -> None: self.inferred = inferred + self.filename = filename self.typemap = typemap self.all_nodes = all_nodes @@ -59,7 +60,8 @@ def visit_func_def(self, o: FuncDef) -> None: self.line = o.line if len(o.expanded) > 1: if o in o.expanded: - print('ERROR: cycle in function expansion; skipping') + print('{}:{}: ERROR: cycle in function expansion; skipping'.format(self.filename, + o.get_line())) return for defn in o.expanded: self.visit_func_def(cast(FuncDef, defn)) @@ -199,7 +201,7 @@ def dump_type_stats(tree: MypyFile, path: str, inferred: bool = False, if is_special_module(path): return print(path) - visitor = StatisticsVisitor(inferred, typemap) + visitor = StatisticsVisitor(inferred, filename=tree.fullname(), typemap=typemap) tree.accept(visitor) for line in visitor.output: print(line) @@ -271,7 +273,8 @@ def generate_html_report(tree: MypyFile, path: str, type_map: Dict[Expression, T path = os.path.relpath(path) if path.startswith('..'): return - visitor = StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True) + visitor = StatisticsVisitor(inferred=True, filename=tree.fullname(), typemap=type_map, + all_nodes=True) tree.accept(visitor) assert not os.path.isabs(path) and not path.startswith('..') # This line is *wrong* if the preceding assert fails. From d5d32161f0cc4499b5ae7ec6beb94e8f7f2e0a49 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 6 Jun 2017 13:35:26 -0700 Subject: [PATCH 3/3] Fix magic numbers by extracting them into variables and adding comments --- mypy/report.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/mypy/report.py b/mypy/report.py index 3c3a7025bef2..e53a68c569c1 100644 --- a/mypy/report.py +++ b/mypy/report.py @@ -169,29 +169,37 @@ def on_finish(self) -> None: any_column_name = "Anys" total_column_name = "Exprs" file_column_name = "Name" + coverage_column_name = "Coverage" # find the longest filename all files name_width = max([len(file) for file in self.counts] + [len(file_column_name)]) # totals are the largest numbers in their column - no need to look at others - any_width = max(len(str(total_any)) + 3, len(any_column_name)) - exprs_width = max(len(str(total_expr)) + 3, len(total_column_name)) - header = '{:{name_width}} {:>{any_width}} {:>{total_width}} {:>11}'.format( - file_column_name, any_column_name, total_column_name, "Coverage", - name_width=name_width, any_width=any_width, total_width=exprs_width) + min_column_distance = 3 # minimum distance between numbers in two columns + any_width = max(len(str(total_any)) + min_column_distance, len(any_column_name)) + exprs_width = max(len(str(total_expr)) + min_column_distance, len(total_column_name)) + coverage_width = len(coverage_column_name) + min_column_distance + header = '{:{name_width}} {:>{any_width}} {:>{total_width}} {:>{coverage_width}}'.format( + file_column_name, any_column_name, total_column_name, coverage_column_name, + name_width=name_width, any_width=any_width, total_width=exprs_width, + coverage_width=coverage_width) with open(os.path.join(self.output_dir, 'any-exprs.txt'), 'w') as f: f.write(header + '\n') separator = '-' * len(header) + '\n' f.write(separator) + coverage_width -= 1 # subtract one for '%' for file in sorted(self.counts): (num_any, num_total) = self.counts[file] coverage = (float(num_total - num_any) / float(num_total)) * 100 - f.write('{:{name_width}} {:{any_width}} {:{total_width}} {:>10.2f}%\n'. + f.write('{:{name_width}} {:{any_width}} {:{total_width}} ' + '{:>{coverage_width}.2f}%\n'. format(file, num_any, num_total, coverage, name_width=name_width, - any_width=any_width, total_width=exprs_width)) + any_width=any_width, total_width=exprs_width, + coverage_width=coverage_width)) f.write(separator) - f.write('{:{name_width}} {:{any_width}} {:{total_width}} {:>10.2f}%\n'.format('Total', - total_any, total_expr, total_coverage, name_width=name_width, - any_width=any_width, total_width=exprs_width)) + f.write('{:{name_width}} {:{any_width}} {:{total_width}} {:>{coverage_width}.2f}%\n' + .format('Total', total_any, total_expr, total_coverage, name_width=name_width, + any_width=any_width, total_width=exprs_width, + coverage_width=coverage_width)) register_reporter('any-exprs', AnyExpressionsReporter)