Skip to content

Commit 0151347

Browse files
committed
Add the Zuban type checker code
1 parent d4f39b2 commit 0151347

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed

conformance/src/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def update_type_checker_info(
235235

236236
existing_info["version"] = type_checker.get_version()
237237
if not skip_timing:
238-
existing_info["test_duration"] = round(test_duration, 1)
238+
existing_info["test_duration"] = round(test_duration, 2)
239239

240240
version_file.parent.mkdir(parents=True, exist_ok=True)
241241
with open(version_file, "w") as f:

conformance/src/reporting.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ def generate_summary_html(root_dir: Path) -> str:
5050

5151
summary_html.append(f"<th class='tc-header'><div class='tc-name'>{version}</div>")
5252
if test_duration is not None:
53-
summary_html.append(f"<div class='tc-time'>{test_duration:.1f}sec</div>")
53+
if test_duration < 1:
54+
duration = f"{test_duration:.2f}"
55+
else:
56+
duration = f"{test_duration:.1f}"
57+
summary_html.append(f"<div class='tc-time'>{duration}sec</div>")
5458
summary_html.append("</th>")
5559

5660
summary_html.append("</tr>")
@@ -90,6 +94,12 @@ def generate_summary_html(root_dir: Path) -> str:
9094

9195
raw_notes = results.get("notes", "").strip()
9296
conformance = results.get("conformant", "Unknown")
97+
if conformance == "Unknown":
98+
# Try to look up the automated test results and use
99+
# that if the test passes
100+
automated = results.get("conformance_automated")
101+
if automated == "Pass":
102+
conformance = "Pass"
93103
notes = "".join(
94104
[f"<p>{note}</p>" for note in raw_notes.split("\n")]
95105
)

conformance/src/type_checker.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,78 @@ def parse_errors(self, output: Sequence[str]) -> dict[int, list[str]]:
273273
line_to_errors.setdefault(int(lineno), []).append(line)
274274
return line_to_errors
275275

276+
class ZubanLSTypeChecker(MypyTypeChecker):
277+
@property
278+
def name(self) -> str:
279+
return "zuban"
280+
281+
def install(self) -> bool:
282+
try:
283+
# Uninstall any existing version if present.
284+
run(
285+
[sys.executable, "-m", "pip", "uninstall", "zuban", "-y"],
286+
check=True,
287+
)
288+
289+
# Install the latest version.
290+
run(
291+
[sys.executable, "-m", "pip", "install", "zuban"],
292+
check=True,
293+
)
294+
295+
# Run "mypy --version" to ensure that it's installed and to work
296+
# around timing issues caused by malware scanners on some systems.
297+
self.get_version()
298+
299+
return True
300+
except CalledProcessError:
301+
print("Unable to install zuban")
302+
return False
303+
304+
def get_version(self) -> str:
305+
proc = run(["zmypy", "--version"], stdout=PIPE, text=True)
306+
return proc.stdout.strip().replace("zmypy", "zuban")
307+
308+
def run_tests(self, test_files: Sequence[str]) -> dict[str, str]:
309+
command = [
310+
"zmypy",
311+
".",
312+
"--disable-error-code",
313+
"empty-body",
314+
"--enable-error-code",
315+
"deprecated",
316+
"--no-warn-unreachable",
317+
"--no-mypy-compatible",
318+
]
319+
proc = run(command, stdout=PIPE, text=True, encoding="utf-8")
320+
lines = proc.stdout.split("\n")
321+
322+
# Add results to a dictionary keyed by the file name.
323+
results_dict: dict[str, str] = {}
324+
for line in lines:
325+
file_name = line.split(":")[0].strip()
326+
results_dict[file_name] = results_dict.get(file_name, "") + line + "\n"
327+
328+
return results_dict
329+
330+
def parse_errors(self, output: Sequence[str]) -> dict[int, list[str]]:
331+
# narrowing_typeguard.py:102: error: TypeGuard functions must have a positional argument [valid-type]
332+
line_to_errors: dict[int, list[str]] = {}
333+
for line in output:
334+
if line.count(":") < 3:
335+
continue
336+
_, lineno, kind, _ = line.split(":", maxsplit=3)
337+
kind = kind.strip()
338+
if kind != "error":
339+
continue
340+
line_to_errors.setdefault(int(lineno), []).append(line)
341+
return line_to_errors
342+
343+
276344

277345
TYPE_CHECKERS: Sequence[TypeChecker] = (
278346
MypyTypeChecker(),
279347
PyrightTypeChecker(),
280348
*([] if os.name == "nt" else [PyreTypeChecker()]),
349+
ZubanLSTypeChecker(),
281350
)

0 commit comments

Comments
 (0)