Skip to content

Commit 12fdd83

Browse files
committed
Implement infrastructure for reports
1 parent f04f262 commit 12fdd83

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
@@ -46,6 +47,8 @@
4647
MODULE = 'module' # Build/run module as a script
4748
PROGRAM_TEXT = 'program-text' # Build/run command-line argument as a script
4849
TEST_BUILTINS = 'test-builtins' # Use stub builtins to speed up tests
50+
DUMP_TYPE_STATS = 'dump-type-stats'
51+
DUMP_INFER_STATS = 'dump-infer-stats'
4952

5053
# State ids. These describe the states a source file / module can be in a
5154
# build.
@@ -96,7 +99,7 @@ def build(program_path: str,
9699
output_dir: str = None,
97100
pyversion: int = 3,
98101
custom_typing_module: str = None,
99-
html_report_dir: str = None,
102+
report_dirs: Dict[str, str] = {},
100103
flags: List[str] = None,
101104
python_path: bool = False) -> BuildResult:
102105
"""Build a mypy program.
@@ -148,6 +151,8 @@ def build(program_path: str,
148151
if alt_lib_path:
149152
lib_path.insert(0, alt_lib_path)
150153

154+
reports = Reports(data_dir, report_dirs)
155+
151156
# Construct a build manager object that performs all the stages of the
152157
# build in the correct order.
153158
#
@@ -156,7 +161,7 @@ def build(program_path: str,
156161
pyversion=pyversion, flags=flags,
157162
ignore_prefix=os.getcwd(),
158163
custom_typing_module=custom_typing_module,
159-
html_report_dir=html_report_dir)
164+
reports=reports)
160165

161166
if program_text is None:
162167
program_path = program_path or lookup_program(module, lib_path)
@@ -171,8 +176,7 @@ def build(program_path: str,
171176
# initial state of all files) to the manager. The manager will process the
172177
# file and all dependant modules recursively.
173178
result = manager.process(UnprocessedFile(info, program_text))
174-
if 'html-report' in flags:
175-
stats.generate_html_index(html_report_dir)
179+
reports.finish()
176180
return result
177181

178182

@@ -301,7 +305,7 @@ def __init__(self, data_dir: str,
301305
flags: List[str],
302306
ignore_prefix: str,
303307
custom_typing_module: str,
304-
html_report_dir: str) -> None:
308+
reports: Reports) -> None:
305309
self.data_dir = data_dir
306310
self.errors = Errors()
307311
self.errors.set_ignore_prefix(ignore_prefix)
@@ -311,7 +315,7 @@ def __init__(self, data_dir: str,
311315
self.pyversion = pyversion
312316
self.flags = flags
313317
self.custom_typing_module = custom_typing_module
314-
self.html_report_dir = html_report_dir
318+
self.reports = reports
315319
self.semantic_analyzer = SemanticAnalyzer(lib_path, self.errors,
316320
pyversion=pyversion)
317321
self.semantic_analyzer_pass3 = ThirdPass(self.errors)
@@ -813,7 +817,7 @@ class PartiallySemanticallyAnalyzedFile(ParsedFile):
813817
def process(self) -> None:
814818
"""Perform final pass of semantic analysis and advance state."""
815819
self.semantic_analyzer_pass3().visit_file(self.tree, self.tree.path)
816-
if 'dump-type-stats' in self.manager.flags:
820+
if DUMP_TYPE_STATS in self.manager.flags:
817821
stats.dump_type_stats(self.tree, self.tree.path)
818822
self.switch_state(SemanticallyAnalyzedFile(self.info(), self.tree))
819823

@@ -826,14 +830,10 @@ def process(self) -> None:
826830
"""Type check file and advance to the next state."""
827831
if self.manager.target >= TYPE_CHECK:
828832
self.type_checker().visit_file(self.tree, self.tree.path)
829-
if 'dump-infer-stats' in self.manager.flags:
833+
if DUMP_INFER_STATS in self.manager.flags:
830834
stats.dump_type_stats(self.tree, self.tree.path, inferred=True,
831835
typemap=self.manager.type_checker.type_map)
832-
elif 'html-report' in self.manager.flags:
833-
stats.generate_html_report(
834-
self.tree, self.tree.path,
835-
type_map=self.manager.type_checker.type_map,
836-
output_dir=self.manager.html_report_dir)
836+
self.manager.reports.file(self.tree, type_map=self.manager.type_checker.type_map)
837837

838838
# FIX remove from active state list to speed up processing
839839

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

@@ -75,7 +75,7 @@ def type_check_only(path: str, module: str, program_text: str, bin_dir: str, opt
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

@@ -108,17 +108,18 @@ def process_options(args: List[str]) -> Tuple[str, str, str, Options]:
108108
help = True
109109
args = args[1:]
110110
elif args[0] == '--stats':
111-
options.build_flags.append('dump-type-stats')
111+
options.build_flags.append(build.DUMP_TYPE_STATS)
112112
args = args[1:]
113113
elif args[0] == '--inferstats':
114-
options.build_flags.append('dump-infer-stats')
114+
options.build_flags.append(build.DUMP_INFER_STATS)
115115
args = args[1:]
116116
elif args[0] == '--custom-typing' and args[1:]:
117117
options.custom_typing_module = args[1]
118118
args = args[2:]
119-
elif args[0] == '--html-report' and args[1:]:
120-
options.html_report_dir = args[1]
121-
options.build_flags.append('html-report')
119+
elif args[0] in ('--html-report', '--old-html-report') and args[1:]:
120+
report_type = args[0][2:-7]
121+
report_dir = args[1]
122+
options.report_dirs[report_type] = report_dir
122123
args = args[2:]
123124
elif args[0] == '--use-python-path':
124125
options.python_path = True
@@ -161,7 +162,8 @@ def usage(msg: str = None) -> None:
161162
162163
Optional arguments:
163164
-h, --help print this help message and exit
164-
--html-report dir generate a HTML report of type precision under dir/
165+
--<fmt>-report dir generate a <fmt> report of type precision under dir/
166+
<fmt> may be one of: html, old-html.
165167
-m mod type check module
166168
-c string type check string
167169
--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)