summaryrefslogtreecommitdiff
path: root/coverage/html.py
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2022-12-31 14:53:21 -0500
committerNed Batchelder <ned@nedbatchelder.com>2022-12-31 14:53:21 -0500
commitbf73b37080c3c6deec969a555b45b70ee6727b13 (patch)
tree98f6c9c20d89afcc4d0c2396a155229fbd802290 /coverage/html.py
parentee1e4150529e55cd860fc3628b820d3a2ed471de (diff)
downloadpython-coveragepy-git-bf73b37080c3c6deec969a555b45b70ee6727b13.tar.gz
mypy: check tests/goldtest.py, tests/test_html.py
Diffstat (limited to 'coverage/html.py')
-rw-r--r--coverage/html.py75
1 files changed, 54 insertions, 21 deletions
diff --git a/coverage/html.py b/coverage/html.py
index 21b5189e..3fcecc5d 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -3,12 +3,16 @@
"""HTML reporting for coverage.py."""
+from __future__ import annotations
+
import datetime
import json
import os
import re
import shutil
-import types
+
+from dataclasses import dataclass
+from typing import Iterable, List, Optional, TYPE_CHECKING
import coverage
from coverage.data import add_data_to_hash
@@ -17,13 +21,18 @@ from coverage.files import flat_rootname
from coverage.misc import ensure_dir, file_be_gone, Hasher, isolate_module, format_local_datetime
from coverage.misc import human_sorted, plural
from coverage.report import get_analysis_to_report
-from coverage.results import Numbers
+from coverage.results import Analysis, Numbers
from coverage.templite import Templite
+from coverage.types import TLineNo, TMorf
+
+if TYPE_CHECKING:
+ from coverage import Coverage
+ from coverage.plugins import FileReporter
os = isolate_module(os)
-def data_filename(fname):
+def data_filename(fname: str) -> str:
"""Return the path to an "htmlfiles" data file of ours.
"""
static_dir = os.path.join(os.path.dirname(__file__), "htmlfiles")
@@ -31,25 +40,47 @@ def data_filename(fname):
return static_filename
-def read_data(fname):
+def read_data(fname: str) -> str:
"""Return the contents of a data file of ours."""
with open(data_filename(fname)) as data_file:
return data_file.read()
-def write_html(fname, html):
+def write_html(fname: str, html: str) -> None:
"""Write `html` to `fname`, properly encoded."""
html = re.sub(r"(\A\s+)|(\s+$)", "", html, flags=re.MULTILINE) + "\n"
with open(fname, "wb") as fout:
fout.write(html.encode('ascii', 'xmlcharrefreplace'))
+@dataclass
+class LineData:
+ """The data for each source line of HTML output."""
+ tokens: str
+ number: TLineNo
+ category: str
+ statement: bool
+ contexts: List[str]
+ contexts_label: str
+ context_list: List[str]
+ short_annotations: List[str]
+ long_annotations: List[str]
+
+
+@dataclass
+class FileData:
+ """The data for each source file of HTML output."""
+ relative_filename: str
+ nums: Numbers
+ lines: List[LineData]
+
+
class HtmlDataGeneration:
"""Generate structured data to be turned into HTML reports."""
EMPTY = "(empty)"
- def __init__(self, cov):
+ def __init__(self, cov: Coverage) -> None:
self.coverage = cov
self.config = self.coverage.config
data = self.coverage.get_data()
@@ -59,7 +90,7 @@ class HtmlDataGeneration:
self.coverage._warn("No contexts were measured")
data.set_query_contexts(self.config.report_contexts)
- def data_for_file(self, fr, analysis):
+ def data_for_file(self, fr: FileReporter, analysis: Analysis) -> FileData:
"""Produce the data needed for one file's report."""
if self.has_arcs:
missing_branch_arcs = analysis.missing_branch_arcs()
@@ -72,7 +103,7 @@ class HtmlDataGeneration:
for lineno, tokens in enumerate(fr.source_token_lines(), start=1):
# Figure out how to mark this line.
- category = None
+ category = ""
short_annotations = []
long_annotations = []
@@ -86,13 +117,14 @@ class HtmlDataGeneration:
if b < 0:
short_annotations.append("exit")
else:
- short_annotations.append(b)
+ short_annotations.append(str(b))
long_annotations.append(fr.missing_arc_description(lineno, b, arcs_executed))
elif lineno in analysis.statements:
category = 'run'
- contexts = contexts_label = None
- context_list = None
+ contexts = []
+ contexts_label = ""
+ context_list = []
if category and self.config.show_contexts:
contexts = human_sorted(c or self.EMPTY for c in contexts_by_lineno.get(lineno, ()))
if contexts == [self.EMPTY]:
@@ -101,7 +133,7 @@ class HtmlDataGeneration:
contexts_label = f"{len(contexts)} ctx"
context_list = contexts
- lines.append(types.SimpleNamespace(
+ lines.append(LineData(
tokens=tokens,
number=lineno,
category=category,
@@ -113,7 +145,7 @@ class HtmlDataGeneration:
long_annotations=long_annotations,
))
- file_data = types.SimpleNamespace(
+ file_data = FileData(
relative_filename=fr.relative_filename(),
nums=analysis.numbers,
lines=lines,
@@ -124,7 +156,7 @@ class HtmlDataGeneration:
class FileToReport:
"""A file we're considering reporting."""
- def __init__(self, fr, analysis):
+ def __init__(self, fr: FileReporter, analysis: Analysis) -> None:
self.fr = fr
self.analysis = analysis
self.rootname = flat_rootname(fr.relative_filename())
@@ -144,7 +176,7 @@ class HtmlReporter:
"favicon_32.png",
]
- def __init__(self, cov):
+ def __init__(self, cov: Coverage) -> None:
self.coverage = cov
self.config = self.coverage.config
self.directory = self.config.html_dir
@@ -160,6 +192,7 @@ class HtmlReporter:
title = self.config.html_title
+ self.extra_css: Optional[str]
if self.config.extra_css:
self.extra_css = os.path.basename(self.config.extra_css)
else:
@@ -204,7 +237,7 @@ class HtmlReporter:
self.pyfile_html_source = read_data("pyfile.html")
self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals)
- def report(self, morfs):
+ def report(self, morfs: Iterable[TMorf]) -> float:
"""Generate an HTML report for `morfs`.
`morfs` is a list of modules or file names.
@@ -254,13 +287,13 @@ class HtmlReporter:
self.make_local_static_report_files()
return self.totals.n_statements and self.totals.pc_covered
- def make_directory(self):
+ def make_directory(self) -> None:
"""Make sure our htmlcov directory exists."""
ensure_dir(self.directory)
if not os.listdir(self.directory):
self.directory_was_empty = True
- def make_local_static_report_files(self):
+ def make_local_static_report_files(self) -> None:
"""Make local instances of static files for HTML report."""
# The files we provide must always be copied.
for static in self.STATIC_FILES:
@@ -439,12 +472,12 @@ class IncrementalChecker:
self.directory = directory
self.reset()
- def reset(self):
+ def reset(self) -> None:
"""Initialize to empty. Causes all files to be reported."""
self.globals = ''
self.files = {}
- def read(self):
+ def read(self) -> None:
"""Read the information we stored last time."""
usable = False
try:
@@ -469,7 +502,7 @@ class IncrementalChecker:
else:
self.reset()
- def write(self):
+ def write(self) -> None:
"""Write the current status."""
status_file = os.path.join(self.directory, self.STATUS_FILE)
files = {}