[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot] 2024-04-13 00:00:18 +00:00
parent 72ad6dc953
commit f4cd1ba0d6
813 changed files with 66015 additions and 58839 deletions

View file

@ -1,8 +1,6 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
"""HTML reporting for coverage.py."""
from __future__ import annotations
import collections
@ -13,20 +11,31 @@ import os
import re
import shutil
import string
from dataclasses import dataclass
from typing import Any, Iterable, TYPE_CHECKING, cast
from typing import Any
from typing import cast
from typing import Iterable
from typing import TYPE_CHECKING
import coverage
from coverage.data import CoverageData, add_data_to_hash
from coverage.data import add_data_to_hash
from coverage.data import CoverageData
from coverage.exceptions import NoDataError
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, stdout_link
from coverage.misc import ensure_dir
from coverage.misc import file_be_gone
from coverage.misc import format_local_datetime
from coverage.misc import Hasher
from coverage.misc import human_sorted
from coverage.misc import isolate_module
from coverage.misc import plural
from coverage.misc import stdout_link
from coverage.report_core import get_analysis_to_report
from coverage.results import Analysis, Numbers
from coverage.results import Analysis
from coverage.results import Numbers
from coverage.templite import Templite
from coverage.types import TLineNo, TMorf
from coverage.types import TLineNo
from coverage.types import TMorf
from coverage.version import __url__
@ -56,7 +65,7 @@ os = isolate_module(os)
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")
static_dir = os.path.join(os.path.dirname(__file__), 'htmlfiles')
static_filename = os.path.join(static_dir, fname)
return static_filename
@ -69,9 +78,9 @@ def read_data(fname: str) -> str:
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"))
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
@ -86,11 +95,11 @@ class LineData:
context_list: list[str]
short_annotations: list[str]
long_annotations: list[str]
html: str = ""
html: str = ''
context_str: str | None = None
annotate: str | None = None
annotate_long: str | None = None
css_class: str = ""
css_class: str = ''
@dataclass
@ -104,7 +113,7 @@ class FileData:
class HtmlDataGeneration:
"""Generate structured data to be turned into HTML reports."""
EMPTY = "(empty)"
EMPTY = '(empty)'
def __init__(self, cov: Coverage) -> None:
self.coverage = cov
@ -112,8 +121,8 @@ class HtmlDataGeneration:
data = self.coverage.get_data()
self.has_arcs = data.has_arcs()
if self.config.show_contexts:
if data.measured_contexts() == {""}:
self.coverage._warn("No contexts were measured")
if data.measured_contexts() == {''}:
self.coverage._warn('No contexts were measured')
data.set_query_contexts(self.config.report_contexts)
def data_for_file(self, fr: FileReporter, analysis: Analysis) -> FileData:
@ -129,47 +138,49 @@ class HtmlDataGeneration:
for lineno, tokens in enumerate(fr.source_token_lines(), start=1):
# Figure out how to mark this line.
category = ""
category = ''
short_annotations = []
long_annotations = []
if lineno in analysis.excluded:
category = "exc"
category = 'exc'
elif lineno in analysis.missing:
category = "mis"
category = 'mis'
elif self.has_arcs and lineno in missing_branch_arcs:
category = "par"
category = 'par'
for b in missing_branch_arcs[lineno]:
if b < 0:
short_annotations.append("exit")
short_annotations.append('exit')
else:
short_annotations.append(str(b))
long_annotations.append(fr.missing_arc_description(lineno, b, arcs_executed))
elif lineno in analysis.statements:
category = "run"
category = 'run'
contexts = []
contexts_label = ""
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]:
contexts_label = self.EMPTY
else:
contexts_label = f"{len(contexts)} ctx"
contexts_label = f'{len(contexts)} ctx'
context_list = contexts
lines.append(LineData(
tokens=tokens,
number=lineno,
category=category,
statement=(lineno in analysis.statements),
contexts=contexts,
contexts_label=contexts_label,
context_list=context_list,
short_annotations=short_annotations,
long_annotations=long_annotations,
))
lines.append(
LineData(
tokens=tokens,
number=lineno,
category=category,
statement=(lineno in analysis.statements),
contexts=contexts,
contexts_label=contexts_label,
context_list=context_list,
short_annotations=short_annotations,
long_annotations=long_annotations,
),
)
file_data = FileData(
relative_filename=fr.relative_filename(),
@ -182,15 +193,17 @@ class HtmlDataGeneration:
class FileToReport:
"""A file we're considering reporting."""
def __init__(self, fr: FileReporter, analysis: Analysis) -> None:
self.fr = fr
self.analysis = analysis
self.rootname = flat_rootname(fr.relative_filename())
self.html_filename = self.rootname + ".html"
self.html_filename = self.rootname + '.html'
HTML_SAFE = string.ascii_letters + string.digits + "!#$%'()*+,-./:;=?@[]^_`{|}~"
@functools.lru_cache(maxsize=None)
def encode_int(n: int) -> str:
"""Create a short HTML-safe string from an integer, using HTML_SAFE."""
@ -201,7 +214,7 @@ def encode_int(n: int) -> str:
while n:
n, t = divmod(n, len(HTML_SAFE))
r.append(HTML_SAFE[t])
return "".join(r)
return ''.join(r)
class HtmlReporter:
@ -210,11 +223,11 @@ class HtmlReporter:
# These files will be copied from the htmlfiles directory to the output
# directory.
STATIC_FILES = [
"style.css",
"coverage_html.js",
"keybd_closed.png",
"keybd_open.png",
"favicon_32.png",
'style.css',
'coverage_html.js',
'keybd_closed.png',
'keybd_open.png',
'favicon_32.png',
]
def __init__(self, cov: Coverage) -> None:
@ -253,29 +266,29 @@ class HtmlReporter:
self.template_globals = {
# Functions available in the templates.
"escape": escape,
"pair": pair,
"len": len,
'escape': escape,
'pair': pair,
'len': len,
# Constants for this report.
"__url__": __url__,
"__version__": coverage.__version__,
"title": title,
"time_stamp": format_local_datetime(datetime.datetime.now()),
"extra_css": self.extra_css,
"has_arcs": self.has_arcs,
"show_contexts": self.config.show_contexts,
'__url__': __url__,
'__version__': coverage.__version__,
'title': title,
'time_stamp': format_local_datetime(datetime.datetime.now()),
'extra_css': self.extra_css,
'has_arcs': self.has_arcs,
'show_contexts': self.config.show_contexts,
# Constants for all reports.
# These css classes determine which lines are highlighted by default.
"category": {
"exc": "exc show_exc",
"mis": "mis show_mis",
"par": "par run show_par",
"run": "run",
'category': {
'exc': 'exc show_exc',
'mis': 'mis show_mis',
'par': 'par run show_par',
'run': 'run',
},
}
self.pyfile_html_source = read_data("pyfile.html")
self.pyfile_html_source = read_data('pyfile.html')
self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals)
def report(self, morfs: Iterable[TMorf] | None) -> float:
@ -303,17 +316,17 @@ class HtmlReporter:
for i, ftr in enumerate(files_to_report):
if i == 0:
prev_html = "index.html"
prev_html = 'index.html'
else:
prev_html = files_to_report[i - 1].html_filename
if i == len(files_to_report) - 1:
next_html = "index.html"
next_html = 'index.html'
else:
next_html = files_to_report[i + 1].html_filename
self.write_html_file(ftr, prev_html, next_html)
if not self.all_files_nums:
raise NoDataError("No data to report.")
raise NoDataError('No data to report.')
self.totals = cast(Numbers, sum(self.all_files_nums))
@ -322,7 +335,7 @@ class HtmlReporter:
first_html = files_to_report[0].html_filename
final_html = files_to_report[-1].html_filename
else:
first_html = final_html = "index.html"
first_html = final_html = 'index.html'
self.index_file(first_html, final_html)
self.make_local_static_report_files()
@ -344,8 +357,8 @@ class HtmlReporter:
# .gitignore can't be copied from the source tree because it would
# prevent the static files from being checked in.
if self.directory_was_empty:
with open(os.path.join(self.directory, ".gitignore"), "w") as fgi:
fgi.write("# Created by coverage.py\n*\n")
with open(os.path.join(self.directory, '.gitignore'), 'w') as fgi:
fgi.write('# Created by coverage.py\n*\n')
# The user may have extra CSS they want copied.
if self.extra_css:
@ -401,29 +414,29 @@ class HtmlReporter:
# Build the HTML for the line.
html_parts = []
for tok_type, tok_text in ldata.tokens:
if tok_type == "ws":
if tok_type == 'ws':
html_parts.append(escape(tok_text))
else:
tok_html = escape(tok_text) or "&nbsp;"
tok_html = escape(tok_text) or '&nbsp;'
html_parts.append(f'<span class="{tok_type}">{tok_html}</span>')
ldata.html = "".join(html_parts)
ldata.html = ''.join(html_parts)
if ldata.context_list:
encoded_contexts = [
encode_int(context_codes[c_context]) for c_context in ldata.context_list
]
code_width = max(len(ec) for ec in encoded_contexts)
ldata.context_str = (
str(code_width)
+ "".join(ec.ljust(code_width) for ec in encoded_contexts)
str(code_width) +
''.join(ec.ljust(code_width) for ec in encoded_contexts)
)
else:
ldata.context_str = ""
ldata.context_str = ''
if ldata.short_annotations:
# 202F is NARROW NO-BREAK SPACE.
# 219B is RIGHTWARDS ARROW WITH STROKE.
ldata.annotate = ",&nbsp;&nbsp; ".join(
f"{ldata.number}&#x202F;&#x219B;&#x202F;{d}"
ldata.annotate = ',&nbsp;&nbsp; '.join(
f'{ldata.number}&#x202F;&#x219B;&#x202F;{d}'
for d in ldata.short_annotations
)
else:
@ -434,10 +447,10 @@ class HtmlReporter:
if len(longs) == 1:
ldata.annotate_long = longs[0]
else:
ldata.annotate_long = "{:d} missed branches: {}".format(
ldata.annotate_long = '{:d} missed branches: {}'.format(
len(longs),
", ".join(
f"{num:d}) {ann_long}"
', '.join(
f'{num:d}) {ann_long}'
for num, ann_long in enumerate(longs, start=1)
),
)
@ -447,24 +460,24 @@ class HtmlReporter:
css_classes = []
if ldata.category:
css_classes.append(
self.template_globals["category"][ldata.category], # type: ignore[index]
self.template_globals['category'][ldata.category], # type: ignore[index]
)
ldata.css_class = " ".join(css_classes) or "pln"
ldata.css_class = ' '.join(css_classes) or 'pln'
html_path = os.path.join(self.directory, ftr.html_filename)
html = self.source_tmpl.render({
**file_data.__dict__,
"contexts_json": contexts_json,
"prev_html": prev_html,
"next_html": next_html,
'contexts_json': contexts_json,
'prev_html': prev_html,
'next_html': next_html,
})
write_html(html_path, html)
# Save this file's information for the index file.
index_info: IndexInfoDict = {
"nums": ftr.analysis.numbers,
"html_filename": ftr.html_filename,
"relative_filename": ftr.fr.relative_filename(),
'nums': ftr.analysis.numbers,
'html_filename': ftr.html_filename,
'relative_filename': ftr.fr.relative_filename(),
}
self.file_summaries.append(index_info)
self.incr.set_index_info(ftr.rootname, index_info)
@ -472,30 +485,30 @@ class HtmlReporter:
def index_file(self, first_html: str, final_html: str) -> None:
"""Write the index.html file for this report."""
self.make_directory()
index_tmpl = Templite(read_data("index.html"), self.template_globals)
index_tmpl = Templite(read_data('index.html'), self.template_globals)
skipped_covered_msg = skipped_empty_msg = ""
skipped_covered_msg = skipped_empty_msg = ''
if self.skipped_covered_count:
n = self.skipped_covered_count
skipped_covered_msg = f"{n} file{plural(n)} skipped due to complete coverage."
skipped_covered_msg = f'{n} file{plural(n)} skipped due to complete coverage.'
if self.skipped_empty_count:
n = self.skipped_empty_count
skipped_empty_msg = f"{n} empty file{plural(n)} skipped."
skipped_empty_msg = f'{n} empty file{plural(n)} skipped.'
html = index_tmpl.render({
"files": self.file_summaries,
"totals": self.totals,
"skipped_covered_msg": skipped_covered_msg,
"skipped_empty_msg": skipped_empty_msg,
"first_html": first_html,
"final_html": final_html,
'files': self.file_summaries,
'totals': self.totals,
'skipped_covered_msg': skipped_covered_msg,
'skipped_empty_msg': skipped_empty_msg,
'first_html': first_html,
'final_html': final_html,
})
index_file = os.path.join(self.directory, "index.html")
index_file = os.path.join(self.directory, 'index.html')
write_html(index_file, html)
print_href = stdout_link(index_file, f"file://{os.path.abspath(index_file)}")
self.coverage._message(f"Wrote HTML report to {print_href}")
print_href = stdout_link(index_file, f'file://{os.path.abspath(index_file)}')
self.coverage._message(f'Wrote HTML report to {print_href}')
# Write the latest hashes for next time.
self.incr.write()
@ -504,12 +517,12 @@ class HtmlReporter:
class IncrementalChecker:
"""Logic and data to support incremental reporting."""
STATUS_FILE = "status.json"
STATUS_FILE = 'status.json'
STATUS_FORMAT = 2
NOTE = (
"This file is an internal implementation detail to speed up HTML report"
+ " generation. Its format can change at any time. You might be looking"
+ " for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json"
'This file is an internal implementation detail to speed up HTML report' +
' generation. Its format can change at any time. You might be looking' +
' for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json'
)
# The data looks like:
@ -545,7 +558,7 @@ class IncrementalChecker:
def reset(self) -> None:
"""Initialize to empty. Causes all files to be reported."""
self.globals = ""
self.globals = ''
self.files: dict[str, FileInfoDict] = {}
def read(self) -> None:
@ -559,17 +572,17 @@ class IncrementalChecker:
usable = False
else:
usable = True
if status["format"] != self.STATUS_FORMAT:
if status['format'] != self.STATUS_FORMAT:
usable = False
elif status["version"] != coverage.__version__:
elif status['version'] != coverage.__version__:
usable = False
if usable:
self.files = {}
for filename, fileinfo in status["files"].items():
fileinfo["index"]["nums"] = Numbers(*fileinfo["index"]["nums"])
for filename, fileinfo in status['files'].items():
fileinfo['index']['nums'] = Numbers(*fileinfo['index']['nums'])
self.files[filename] = fileinfo
self.globals = status["globals"]
self.globals = status['globals']
else:
self.reset()
@ -578,19 +591,19 @@ class IncrementalChecker:
status_file = os.path.join(self.directory, self.STATUS_FILE)
files = {}
for filename, fileinfo in self.files.items():
index = fileinfo["index"]
index["nums"] = index["nums"].init_args() # type: ignore[typeddict-item]
index = fileinfo['index']
index['nums'] = index['nums'].init_args() # type: ignore[typeddict-item]
files[filename] = fileinfo
status = {
"note": self.NOTE,
"format": self.STATUS_FORMAT,
"version": coverage.__version__,
"globals": self.globals,
"files": files,
'note': self.NOTE,
'format': self.STATUS_FORMAT,
'version': coverage.__version__,
'globals': self.globals,
'files': files,
}
with open(status_file, "w") as fout:
json.dump(status, fout, separators=(",", ":"))
with open(status_file, 'w') as fout:
json.dump(status, fout, separators=(',', ':'))
def check_global_data(self, *data: Any) -> None:
"""Check the global data that can affect incremental reporting."""
@ -609,7 +622,7 @@ class IncrementalChecker:
`rootname` is the name being used for the file.
"""
m = Hasher()
m.update(fr.source().encode("utf-8"))
m.update(fr.source().encode('utf-8'))
add_data_to_hash(data, fr.filename, m)
this_hash = m.hexdigest()
@ -624,19 +637,19 @@ class IncrementalChecker:
def file_hash(self, fname: str) -> str:
"""Get the hash of `fname`'s contents."""
return self.files.get(fname, {}).get("hash", "") # type: ignore[call-overload]
return self.files.get(fname, {}).get('hash', '') # type: ignore[call-overload]
def set_file_hash(self, fname: str, val: str) -> None:
"""Set the hash of `fname`'s contents."""
self.files.setdefault(fname, {})["hash"] = val # type: ignore[typeddict-item]
self.files.setdefault(fname, {})['hash'] = val # type: ignore[typeddict-item]
def index_info(self, fname: str) -> IndexInfoDict:
"""Get the information for index.html for `fname`."""
return self.files.get(fname, {}).get("index", {}) # type: ignore
return self.files.get(fname, {}).get('index', {}) # type: ignore
def set_index_info(self, fname: str, info: IndexInfoDict) -> None:
"""Set the information for index.html for `fname`."""
self.files.setdefault(fname, {})["index"] = info # type: ignore[typeddict-item]
self.files.setdefault(fname, {})['index'] = info # type: ignore[typeddict-item]
# Helpers for templates and generating HTML
@ -648,9 +661,9 @@ def escape(t: str) -> str:
"""
# Convert HTML special chars into HTML entities.
return t.replace("&", "&amp;").replace("<", "&lt;")
return t.replace('&', '&amp;').replace('<', '&lt;')
def pair(ratio: tuple[int, int]) -> str:
"""Format a pair of numbers so JavaScript can read them in an attribute."""
return "{} {}".format(*ratio)
return '{} {}'.format(*ratio)