Skip to content

Commit 8bb09b3

Browse files
committed
Implement infrastructure for reports
1 parent b5fd9d0 commit 8bb09b3

File tree

3 files changed

+87
-22
lines changed

3 files changed

+87
-22
lines changed

mypy/build.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from mypy.errors import Errors, CompileError
2727
from mypy import parse
2828
from mypy import stats
29+
from mypy.report import Reports
2930

3031

3132
debug = False
@@ -41,6 +42,8 @@
4142
VERBOSE = 'verbose' # More verbose messages (for troubleshooting)
4243
MODULE = 'module' # Build/run module as a script
4344
TEST_BUILTINS = 'test-builtins' # Use stub builtins to speed up tests
45+
DUMP_TYPE_STATS = 'dump-type-stats'
46+
DUMP_INFER_STATS = 'dump-infer-stats'
4447

4548
# State ids. These describe the states a source file / module can be in a
4649
# build.
@@ -90,7 +93,7 @@ def build(program_path: str,
9093
output_dir: str = None,
9194
pyversion: int = 3,
9295
custom_typing_module: str = None,
93-
html_report_dir: str = None,
96+
report_dirs: Dict[str, str] = {},
9497
flags: List[str] = None,
9598
python_path: bool = False) -> BuildResult:
9699
"""Build a mypy program.
@@ -142,6 +145,8 @@ def build(program_path: str,
142145
if alt_lib_path:
143146
lib_path.insert(0, alt_lib_path)
144147

148+
reports = Reports(data_dir, report_dirs)
149+
145150
# Construct a build manager object that performs all the stages of the
146151
# build in the correct order.
147152
#
@@ -150,7 +155,7 @@ def build(program_path: str,
150155
pyversion=pyversion, flags=flags,
151156
ignore_prefix=os.getcwd(),
152157
custom_typing_module=custom_typing_module,
153-
html_report_dir=html_report_dir)
158+
reports=reports)
154159

155160
program_path = program_path or lookup_program(module, lib_path)
156161
if program_text is None:
@@ -163,8 +168,7 @@ def build(program_path: str,
163168
# initial state of all files) to the manager. The manager will process the
164169
# file and all dependant modules recursively.
165170
result = manager.process(UnprocessedFile(info, program_text))
166-
if 'html-report' in flags:
167-
stats.generate_html_index(html_report_dir)
171+
reports.finish()
168172
return result
169173

170174

@@ -288,7 +292,7 @@ def __init__(self, data_dir: str,
288292
flags: List[str],
289293
ignore_prefix: str,
290294
custom_typing_module: str,
291-
html_report_dir: str) -> None:
295+
reports: Reports) -> None:
292296
self.data_dir = data_dir
293297
self.errors = Errors()
294298
self.errors.set_ignore_prefix(ignore_prefix)
@@ -298,7 +302,7 @@ def __init__(self, data_dir: str,
298302
self.pyversion = pyversion
299303
self.flags = flags
300304
self.custom_typing_module = custom_typing_module
301-
self.html_report_dir = html_report_dir
305+
self.reports = reports
302306
self.semantic_analyzer = SemanticAnalyzer(lib_path, self.errors,
303307
pyversion=pyversion)
304308
self.semantic_analyzer_pass3 = ThirdPass(self.errors)
@@ -800,7 +804,7 @@ class PartiallySemanticallyAnalyzedFile(ParsedFile):
800804
def process(self) -> None:
801805
"""Perform final pass of semantic analysis and advance state."""
802806
self.semantic_analyzer_pass3().visit_file(self.tree, self.tree.path)
803-
if 'dump-type-stats' in self.manager.flags:
807+
if DUMP_TYPE_STATS in self.manager.flags:
804808
stats.dump_type_stats(self.tree, self.tree.path)
805809
self.switch_state(SemanticallyAnalyzedFile(self.info(), self.tree))
806810

@@ -813,14 +817,10 @@ def process(self) -> None:
813817
"""Type check file and advance to the next state."""
814818
if self.manager.target >= TYPE_CHECK:
815819
self.type_checker().visit_file(self.tree, self.tree.path)
816-
if 'dump-infer-stats' in self.manager.flags:
820+
if DUMP_INFER_STATS in self.manager.flags:
817821
stats.dump_type_stats(self.tree, self.tree.path, inferred=True,
818822
typemap=self.manager.type_checker.type_map)
819-
elif 'html-report' in self.manager.flags:
820-
stats.generate_html_report(
821-
self.tree, self.tree.path,
822-
type_map=self.manager.type_checker.type_map,
823-
output_dir=self.manager.html_report_dir)
823+
self.manager.reports.file(self.tree, type_map=self.manager.type_checker.type_map)
824824

825825
# FIX remove from active state list to speed up processing
826826

mypy/report.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Classes for producing HTML reports about imprecision."""
2+
3+
from abc import ABCMeta, abstractmethod
4+
5+
from typing import Callable, Dict, List
6+
7+
from mypy.types import Type
8+
from mypy.nodes import MypyFile, Node
9+
from mypy import stats
10+
11+
12+
reporter_classes = {} # type: Dict[str, Callable[[Reports, str], AbstractReporter]]
13+
14+
15+
class Reports:
16+
def __init__(self, data_dir: str, report_dirs: Dict[str, str]) -> None:
17+
self.data_dir = data_dir
18+
self.reporters = [] # type: List[AbstractReporter]
19+
20+
for report_type, report_dir in sorted(report_dirs.items()):
21+
self.add_report(report_type, report_dir)
22+
23+
def add_report(self, report_type: str, report_dir: str) -> 'AbstractReporter':
24+
reporter_cls = reporter_classes[report_type]
25+
reporter = reporter_cls(self, report_dir)
26+
self.reporters.append(reporter)
27+
28+
def file(self, tree: MypyFile, type_map: Dict[Node, Type]) -> None:
29+
for reporter in self.reporters:
30+
reporter.on_file(tree, type_map)
31+
32+
def finish(self) -> None:
33+
for reporter in self.reporters:
34+
reporter.on_finish()
35+
36+
37+
class AbstractReporter(metaclass=ABCMeta):
38+
def __init__(self, reports: Reports, output_dir: str) -> None:
39+
self.output_dir = output_dir
40+
41+
@abstractmethod
42+
def on_file(self, tree: MypyFile, type_map: Dict[Node, Type]) -> None:
43+
pass
44+
45+
@abstractmethod
46+
def on_finish(self) -> None:
47+
pass
48+
49+
class OldHtmlReporter(AbstractReporter):
50+
"""Old HTML reporter.
51+
52+
This just calls the old functions in `stats`, which use global
53+
variables to preserve state for the index.
54+
"""
55+
56+
def on_file(self, tree: MypyFile, type_map: Dict[Node, Type]) -> None:
57+
stats.generate_html_report(tree, tree.path, type_map, self.output_dir)
58+
59+
def on_finish(self) -> None:
60+
stats.generate_html_index(self.output_dir)
61+
reporter_classes['old-html'] = OldHtmlReporter
62+
63+
reporter_classes['html'] = reporter_classes['old-html']

scripts/mypy

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import sys
99
import tempfile
1010

1111
import typing
12-
from typing import List, Tuple
12+
from typing import Dict, List, Tuple
1313

1414
from mypy import build
1515
from mypy.errors import CompileError
@@ -24,7 +24,7 @@ class Options:
2424
self.build_flags = [] # type: List[str]
2525
self.pyversion = 3
2626
self.custom_typing_module = None # type: str
27-
self.html_report_dir = None # type: str
27+
self.report_dirs = {} # type: Dict[str, str]
2828
self.python_path = False
2929

3030

@@ -75,7 +75,7 @@ def type_check_only(path: str, module: str, bin_dir: str, options: Options) -> N
7575
target=build.TYPE_CHECK,
7676
pyversion=options.pyversion,
7777
custom_typing_module=options.custom_typing_module,
78-
html_report_dir=options.html_report_dir,
78+
report_dirs=options.report_dirs,
7979
flags=options.build_flags,
8080
python_path=options.python_path)
8181

@@ -105,17 +105,18 @@ def process_options(args: List[str]) -> Tuple[str, str, Options]:
105105
help = True
106106
args = args[1:]
107107
elif args[0] == '--stats':
108-
options.build_flags.append('dump-type-stats')
108+
options.build_flags.append(build.DUMP_TYPE_STATS)
109109
args = args[1:]
110110
elif args[0] == '--inferstats':
111-
options.build_flags.append('dump-infer-stats')
111+
options.build_flags.append(build.DUMP_INFER_STATS)
112112
args = args[1:]
113113
elif args[0] == '--custom-typing' and args[1:]:
114114
options.custom_typing_module = args[1]
115115
args = args[2:]
116-
elif args[0] == '--html-report' and args[1:]:
117-
options.html_report_dir = args[1]
118-
options.build_flags.append('html-report')
116+
elif args[0] in ('--html-report', '--old-html-report') and args[1:]:
117+
report_type = args[0][2:-7]
118+
report_dir = args[1]
119+
options.report_dirs[report_type] = report_dir
119120
args = args[2:]
120121
elif args[0] == '--use-python-path':
121122
options.python_path = True
@@ -158,7 +159,8 @@ Try 'mypy -h' for more information.
158159
159160
Optional arguments:
160161
-h, --help print this help message and exit
161-
--html-report dir generate a HTML report of type precision under dir/
162+
--<fmt>-report dir generate a <fmt> report of type precision under dir/
163+
<fmt> may be one of: html, old-html.
162164
-m mod type check module
163165
--verbose more verbose messages
164166
--use-python-path search for modules in sys.path of running Python

0 commit comments

Comments
 (0)