Skip to content

Type tooltips in HTML output #2444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions mypy/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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',
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.

Expand Down
21 changes: 20 additions & 1 deletion mypy/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand Down
98 changes: 98 additions & 0 deletions xml/mypy-html-tooltips.xslt
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim: set sts=2 sw=2: -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="ext" select="'xml'"/>
<xsl:output method="html"/>
<xsl:variable name="xml_stylesheet_pi" select="string(//processing-instruction('xml-stylesheet'))"/>
<xsl:variable name="stylesheet_name" select="substring($xml_stylesheet_pi, 23, string-length($xml_stylesheet_pi) - 28)"/>
<xsl:template match="/mypy-report-index">
<xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html&gt;&#10;</xsl:text>
<html>
<head>
<link rel="stylesheet" type="text/css" href="{$stylesheet_name}.css"/>
</head>
<body>
<h1>Mypy Type Check Coverage Summary</h1>
<table class="summary">
<caption>Summary from <xsl:value-of select="@name"/></caption>
<thead>
<tr class="summary">
<th class="summary">File</th>
<th class="summary">Imprecision</th>
<th class="summary">Lines</th>
</tr>
</thead>
<tfoot>
<xsl:variable name="bad_lines" select="sum(file/@imprecise|file/@any)"/>
<xsl:variable name="total_lines" select="sum(file/@total)"/>
<xsl:variable name="global_score" select="$bad_lines div ($total_lines + not(number($total_lines)))"/>
<xsl:variable name="global_quality" select="string(number(number($global_score) &gt; 0.00) + number(number($global_score) &gt;= 0.20))"/>
<tr class="summary summary-quality-{$global_quality}">
<th class="summary summary-filename">Total</th>
<th class="summary summary-precision"><xsl:value-of select="format-number($global_score, '0.00%')"/> imprecise</th>
<th class="summary summary-lines"><xsl:value-of select="$total_lines"/> LOC</th>
</tr>
</tfoot>
<tbody>
<xsl:for-each select="file">
<xsl:variable name="local_score" select="(@imprecise + @any) div (@total + not(number(@total)))"/>
<xsl:variable name="local_quality" select="string(number(number($local_score) &gt; 0.00) + number(number($local_score) &gt;= 0.20))"/>
<tr class="summary summary-quality-{$local_quality}">
<td class="summary summary-filename"><a href="{$ext}/{@name}.{$ext}"><xsl:value-of select="@module"/></a></td>
<td class="summary summary-precision"><xsl:value-of select="format-number($local_score, '0.00%')"/> imprecise</td>
<td class="summary summary-lines"><xsl:value-of select="@total"/> LOC</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="/mypy-report-file">
<xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html&gt;&#10;</xsl:text>
<html>
<head>
<link rel="stylesheet" type="text/css" href="{$stylesheet_name}.css"/>
</head>
<body>
<h2><xsl:value-of select="@module"/></h2>
<table>
<caption><xsl:value-of select="@name"/></caption>
<tbody>
<tr>
<td class="table-lines">
<pre>
<xsl:for-each select="line">
<span id="L{@number}" class="lineno"><a class="lineno" href="#L{@number}"><xsl:value-of select="@number"/></a></span><xsl:text>&#10;</xsl:text>
</xsl:for-each>
</pre>
</td>
<td class="table-code">
<pre>
<xsl:for-each select="line">
<span class="line-{@precision}">
<xsl:for-each select="char">
<xsl:if test="@typestr!=''">
<span class="type-char">
<xsl:value-of select="@content"/>
<span class="type-tooltip">
<xsl:value-of select="@typestr"/>
</span>
</span>
</xsl:if>
<xsl:if test="@typestr=''">
<xsl:value-of select="@content"/>
</xsl:if>
</xsl:for-each>
</span>
<xsl:text>&#10;</xsl:text>
</xsl:for-each>
</pre>
</td>
</tr>
</tbody>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
23 changes: 23 additions & 0 deletions xml/mypy-html.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
}
2 changes: 2 additions & 0 deletions xml/mypy-html.xslt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<xsl:variable name="xml_stylesheet_pi" select="string(//processing-instruction('xml-stylesheet'))"/>
<xsl:variable name="stylesheet_name" select="substring($xml_stylesheet_pi, 23, string-length($xml_stylesheet_pi) - 28)"/>
<xsl:template match="/mypy-report-index">
<xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html&gt;&#10;</xsl:text>
<html>
<head>
<link rel="stylesheet" type="text/css" href="{$stylesheet_name}.css"/>
Expand Down Expand Up @@ -48,6 +49,7 @@
</html>
</xsl:template>
<xsl:template match="/mypy-report-file">
<xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html&gt;&#10;</xsl:text>
<html>
<head>
<link rel="stylesheet" type="text/css" href="{$stylesheet_name}.css"/>
Expand Down
9 changes: 9 additions & 0 deletions xml/mypy.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@
<xs:sequence>
<xs:element name="line" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="char" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="column" type="xs:integer" use="required"/>
<xs:attribute name="typestr" type="xs:string" use="required"/>
<xs:attribute name="content" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="number" type="xs:integer" use="required"/>
<xs:attribute name="precision" type="precision" use="required"/>
<xs:attribute name="content" type="xs:string" use="required"/>
Expand Down