From 9f3cbcd5d61dd1f8ec518e272f9f7537bde23baa Mon Sep 17 00:00:00 2001 From: Onno Kortmann Date: Fri, 11 Nov 2016 23:16:32 +0100 Subject: [PATCH 1/2] DOCTYPE declaration in HTML output Firefox complains about a missing doc type when looking at the source view. This adds a DOCTYPE declaration to the generated HTML. --- xml/mypy-html.xslt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xml/mypy-html.xslt b/xml/mypy-html.xslt index 2e7ed513c131..a81a7687ee0b 100644 --- a/xml/mypy-html.xslt +++ b/xml/mypy-html.xslt @@ -6,6 +6,7 @@ + <!DOCTYPE html> @@ -48,6 +49,7 @@ + <!DOCTYPE html> From 0f18d8f61ab0746b6a0f3b4adcbdc64f65bac09c Mon Sep 17 00:00:00 2001 From: Onno Kortmann Date: Tue, 6 Dec 2016 09:30:30 +0100 Subject: [PATCH 2/2] Add HTML report type with tooltips This adds a HTML reporter that will produce mouse-over type tooltips in the output. The command line argument is --xslt-html-tooltips-report. This also adds by-default-disabled functionality to stats.StatisticsVisitor to build a detailed map of the form (Line, Column) -> TypeString. It also extends the report.MemoryXmlReporter to optionally add XML elements from the extended StatisticsVisitor. --- mypy/report.py | 47 ++++++++++++++++-- mypy/stats.py | 21 +++++++- xml/mypy-html-tooltips.xslt | 98 +++++++++++++++++++++++++++++++++++++ xml/mypy-html.css | 23 +++++++++ xml/mypy.xsd | 9 ++++ 5 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 xml/mypy-html-tooltips.xslt diff --git a/mypy/report.py b/mypy/report.py index 2bb55aa3f971..2ca474435d67 100644 --- a/mypy/report.py +++ b/mypy/report.py @@ -285,12 +285,18 @@ def __init__(self, reports: Reports, output_dir: str) -> None: super().__init__(reports, output_dir) self.xslt_html_path = os.path.join(reports.data_dir, 'xml', 'mypy-html.xslt') + self.xslt_html_tooltips_path = os.path.join( + reports.data_dir, 'xml', 'mypy-html-tooltips.xslt') self.xslt_txt_path = os.path.join(reports.data_dir, 'xml', 'mypy-txt.xslt') self.css_html_path = os.path.join(reports.data_dir, 'xml', 'mypy-html.css') xsd_path = os.path.join(reports.data_dir, 'xml', 'mypy.xsd') self.schema = etree.XMLSchema(etree.parse(xsd_path)) self.last_xml = None # type: etree._ElementTree self.files = [] # type: List[FileInfo] + self.type_columns = False + + def set_type_columns(self, type_columns: bool) -> None: + self.type_columns = type_columns def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type]) -> None: self.last_xml = None @@ -302,7 +308,8 @@ def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type]) -> None: if 'stubs' in path.split('/'): return - visitor = stats.StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True) + visitor = stats.StatisticsVisitor(inferred=True, typemap=type_map, all_nodes=True, + linecol_typestr=self.type_columns) tree.accept(visitor) root = etree.Element('mypy-report-file', name=path, module=tree._fullname) @@ -313,10 +320,28 @@ def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type]) -> None: for lineno, line_text in enumerate(input_file, 1): status = visitor.line_map.get(lineno, stats.TYPE_EMPTY) file_info.counts[status] += 1 - etree.SubElement(root, 'line', + line = etree.SubElement(root, 'line', number=str(lineno), precision=stats.precision_names[status], content=line_text[:-1]) + + if self.type_columns: + for column, ch in enumerate(line_text[:-1]): + if lineno in visitor.linecol_typename_map: + row = visitor.linecol_typename_map[lineno] + if column in row: + typestr = row[column] + else: + typestr = "" + else: + typestr = "" + + etree.SubElement(line, + "char", + column=str(column), + typestr=typestr, + content=ch) + # Assumes a layout similar to what XmlReporter uses. xslt_path = os.path.relpath('mypy-html.xslt', path) transform_pi = etree.ProcessingInstruction('xml-stylesheet', @@ -530,9 +555,12 @@ class XsltHtmlReporter(AbstractXmlReporter): def __init__(self, reports: Reports, output_dir: str) -> None: super().__init__(reports, output_dir) - self.xslt_html = etree.XSLT(etree.parse(self.memory_xml.xslt_html_path)) + self.xslt_html = etree.XSLT(etree.parse(self.xslt_file_to_use())) self.param_html = etree.XSLT.strparam('html') + def xslt_file_to_use(self) -> str: + return self.memory_xml.xslt_html_path + def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type]) -> None: last_xml = self.memory_xml.last_xml if last_xml is None: @@ -560,6 +588,19 @@ def on_finish(self) -> None: register_reporter('xslt-html', XsltHtmlReporter, needs_lxml=True) +class XsltHtmlTooltipsReporter(XsltHtmlReporter): + def __init__(self, reports: Reports, output_dir: str) -> None: + super().__init__(reports, output_dir) + self.memory_xml.set_type_columns(True) + + def xslt_file_to_use(self) -> str: + return self.memory_xml.xslt_html_tooltips_path + + +register_reporter('xslt-html-tooltips', + XsltHtmlTooltipsReporter, needs_lxml=True) + + class XsltTxtReporter(AbstractXmlReporter): """Public reporter that exports TXT via XSLT. diff --git a/mypy/stats.py b/mypy/stats.py index 5d6df35fa05a..e79700e28312 100644 --- a/mypy/stats.py +++ b/mypy/stats.py @@ -32,10 +32,12 @@ class StatisticsVisitor(TraverserVisitor): def __init__(self, inferred: bool, typemap: Dict[Expression, Type] = None, - all_nodes: bool = False) -> None: + all_nodes: bool = False, + linecol_typestr: bool = False) -> None: self.inferred = inferred self.typemap = typemap self.all_nodes = all_nodes + self.linecol_typestr = linecol_typestr self.num_precise = 0 self.num_imprecise = 0 @@ -52,6 +54,8 @@ def __init__(self, inferred: bool, typemap: Dict[Expression, Type] = None, self.line_map = {} # type: Dict[int, int] + self.linecol_typename_map = {} # type: Dict[int, Dict[int, str]] + self.output = [] # type: List[str] TraverserVisitor.__init__(self) @@ -152,6 +156,11 @@ def process_node(self, node: Expression) -> None: if self.all_nodes: typ = self.typemap.get(node) if typ: + if self.linecol_typestr: + self.record_node_typestr( + node.line, + node.column, + str(typ)) self.line = node.line self.type(typ) @@ -196,6 +205,16 @@ def record_line(self, line: int, precision: int) -> None: self.line_map[line] = max(precision, self.line_map.get(line, TYPE_PRECISE)) + def record_node_typestr(self, + line: int, + column: int, + typestr: str) -> None: + m = self.linecol_typename_map + + m.setdefault(line, m.get(line, {})) + + m[line][column] = typestr + def dump_type_stats(tree: MypyFile, path: str, inferred: bool = False, typemap: Dict[Expression, Type] = None) -> None: diff --git a/xml/mypy-html-tooltips.xslt b/xml/mypy-html-tooltips.xslt new file mode 100644 index 000000000000..13f712356869 --- /dev/null +++ b/xml/mypy-html-tooltips.xslt @@ -0,0 +1,98 @@ + + + + + + + + + <!DOCTYPE html> + + + + + +

Mypy Type Check Coverage Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Summary from
FileImprecisionLines
Total imprecise LOC
imprecise LOC
+ + +
+ + <!DOCTYPE html> + + + + + +

+ + + + + + + + +
+
+                  
+                    

+                  
+                
+
+
+                  
+                    
+		      
+			
+			  
+			    
+			    
+			      
+			    
+			  
+			
+			
+			  
+			
+		    
+		    
+		    

+                  
+                
+
+ + +
+
diff --git a/xml/mypy-html.css b/xml/mypy-html.css index 1a3302db2a92..d84f92ffa762 100644 --- a/xml/mypy-html.css +++ b/xml/mypy-html.css @@ -102,3 +102,26 @@ a:hover.lineno, a:active.lineno { .line-any { background-color: #faa; } + +.type-char { + position : relative; + border-bottom : 1px dotted black; +} + +.type-char:hover .type-tooltip { + visibility: visible; +} + +.type-char .type-tooltip { + visibility: hidden; + background-color : #f80; + position : absolute; + z-index : 1; + padding : 5px 0; + text-align : center; + border-radius : 6px; +} + +.type-char .type-tooltip { + top : 200%; +} diff --git a/xml/mypy.xsd b/xml/mypy.xsd index d5f16eb8e7a1..36c0f712df71 100644 --- a/xml/mypy.xsd +++ b/xml/mypy.xsd @@ -34,6 +34,15 @@ + + + + + + + + +