Skip to content

Commit 923760a

Browse files
committed
Implement infrastructure for reports
1 parent 46cd254 commit 923760a

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
# We need to know the location of this file to load data, but
@@ -45,6 +46,8 @@
4546
MODULE = 'module' # Build module as a script
4647
PROGRAM_TEXT = 'program-text' # Build command-line argument as a script
4748
TEST_BUILTINS = 'test-builtins' # Use stub builtins to speed up tests
49+
DUMP_TYPE_STATS = 'dump-type-stats'
50+
DUMP_INFER_STATS = 'dump-infer-stats'
4851

4952
# State ids. These describe the states a source file / module can be in a
5053
# build.
@@ -94,7 +97,7 @@ def build(program_path: str,
9497
bin_dir: str = None,
9598
pyversion: int = 3,
9699
custom_typing_module: str = None,
97-
html_report_dir: str = None,
100+
report_dirs: Dict[str, str] = {},
98101
flags: List[str] = None,
99102
python_path: bool = False) -> BuildResult:
100103
"""Analyze a program.
@@ -143,6 +146,8 @@ def build(program_path: str,
143146
if alt_lib_path:
144147
lib_path.insert(0, alt_lib_path)
145148

149+
reports = Reports(data_dir, report_dirs)
150+
146151
# Construct a build manager object that performs all the stages of the
147152
# build in the correct order.
148153
#
@@ -151,7 +156,7 @@ def build(program_path: str,
151156
pyversion=pyversion, flags=flags,
152157
ignore_prefix=os.getcwd(),
153158
custom_typing_module=custom_typing_module,
154-
html_report_dir=html_report_dir)
159+
reports=reports)
155160

156161
if program_text is None:
157162
program_path = program_path or lookup_program(module, lib_path)
@@ -166,8 +171,7 @@ def build(program_path: str,
166171
# initial state of all files) to the manager. The manager will process the
167172
# file and all dependant modules recursively.
168173
result = manager.process(UnprocessedFile(info, program_text))
169-
if 'html-report' in flags:
170-
stats.generate_html_index(html_report_dir)
174+
reports.finish()
171175
return result
172176

173177

@@ -309,7 +313,7 @@ def __init__(self, data_dir: str,
309313
flags: List[str],
310314
ignore_prefix: str,
311315
custom_typing_module: str,
312-
html_report_dir: str) -> None:
316+
reports: Reports) -> None:
313317
self.data_dir = data_dir
314318
self.errors = Errors()
315319
self.errors.set_ignore_prefix(ignore_prefix)
@@ -318,7 +322,7 @@ def __init__(self, data_dir: str,
318322
self.pyversion = pyversion
319323
self.flags = flags
320324
self.custom_typing_module = custom_typing_module
321-
self.html_report_dir = html_report_dir
325+
self.reports = reports
322326
self.semantic_analyzer = SemanticAnalyzer(lib_path, self.errors,
323327
pyversion=pyversion)
324328
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/main.py

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

1010
import typing
11-
from typing import List, Tuple
11+
from typing import Dict, List, Tuple
1212

1313
from mypy import build
1414
from mypy.errors import CompileError
@@ -23,7 +23,7 @@ def __init__(self) -> None:
2323
self.build_flags = [] # type: List[str]
2424
self.pyversion = 3
2525
self.custom_typing_module = None # type: str
26-
self.html_report_dir = None # type: str
26+
self.report_dirs = {} # type: Dict[str, str]
2727
self.python_path = False
2828

2929

@@ -76,7 +76,7 @@ def type_check_only(path: str, module: str, program_text: str,
7676
target=build.TYPE_CHECK,
7777
pyversion=options.pyversion,
7878
custom_typing_module=options.custom_typing_module,
79-
html_report_dir=options.html_report_dir,
79+
report_dirs=options.report_dirs,
8080
flags=options.build_flags,
8181
python_path=options.python_path)
8282

@@ -109,17 +109,18 @@ def process_options(args: List[str]) -> Tuple[str, str, str, Options]:
109109
help = True
110110
args = args[1:]
111111
elif args[0] == '--stats':
112-
options.build_flags.append('dump-type-stats')
112+
options.build_flags.append(build.DUMP_TYPE_STATS)
113113
args = args[1:]
114114
elif args[0] == '--inferstats':
115-
options.build_flags.append('dump-infer-stats')
115+
options.build_flags.append(build.DUMP_INFER_STATS)
116116
args = args[1:]
117117
elif args[0] == '--custom-typing' and args[1:]:
118118
options.custom_typing_module = args[1]
119119
args = args[2:]
120-
elif args[0] == '--html-report' and args[1:]:
121-
options.html_report_dir = args[1]
122-
options.build_flags.append('html-report')
120+
elif args[0] in ('--html-report', '--old-html-report') and args[1:]:
121+
report_type = args[0][2:-7]
122+
report_dir = args[1]
123+
options.report_dirs[report_type] = report_dir
123124
args = args[2:]
124125
elif args[0] == '--use-python-path':
125126
options.python_path = True
@@ -162,7 +163,8 @@ def usage(msg: str = None) -> None:
162163
163164
Optional arguments:
164165
-h, --help print this help message and exit
165-
--html-report dir generate a HTML report of type precision under dir/
166+
--<fmt>-report dir generate a <fmt> report of type precision under dir/
167+
<fmt> may be one of: html, old-html.
166168
-m mod type check module
167169
-c string type check string
168170
--verbose more verbose messages

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']

0 commit comments

Comments
 (0)