From dafebf1e9216ec8835effd69263f1b55e10218f5 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 16 Dec 2023 20:03:12 -0500 Subject: [PATCH] refactor: no need for our own AST dump function --- coverage/parser.py | 76 +++----------------------------------------- lab/show_ast.py | 11 ------- tests/test_parser.py | 40 ++--------------------- 3 files changed, 7 insertions(+), 120 deletions(-) delete mode 100644 lab/show_ast.py diff --git a/coverage/parser.py b/coverage/parser.py index fd3276e6e..0aa7a81a7 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -690,7 +690,10 @@ def __init__( # Dump the AST so that failing tests have helpful output. print(f"Statements: {self.statements}") print(f"Multiline map: {self.multiline}") - ast_dump(self.root_node) + dumpkw: Dict[str, Any] = {} + if sys.version_info >= (3, 9): + dumpkw["indent"] = 4 + print(ast.dump(self.root_node, include_attributes=True, **dumpkw)) self.arcs: Set[TArc] = set() @@ -1350,74 +1353,3 @@ def _code_object__ClassDef(self, node: ast.ClassDef) -> None: _code_object__DictComp = _make_expression_code_method("dictionary comprehension") _code_object__SetComp = _make_expression_code_method("set comprehension") _code_object__ListComp = _make_expression_code_method("list comprehension") - - -# Code only used when dumping the AST for debugging. - -SKIP_DUMP_FIELDS = ["ctx"] - -def _is_simple_value(value: Any) -> bool: - """Is `value` simple enough to be displayed on a single line?""" - return ( - value in [None, [], (), {}, set(), frozenset(), Ellipsis] or - isinstance(value, (bytes, int, float, str)) - ) - -def ast_dump( - node: ast.AST, - depth: int = 0, - print: Callable[[str], None] = print, # pylint: disable=redefined-builtin -) -> None: - """Dump the AST for `node`. - - This recursively walks the AST, printing a readable version. - - """ - indent = " " * depth - lineno = getattr(node, "lineno", None) - if lineno is not None: - linemark = f" @ {node.lineno},{node.col_offset}" - if hasattr(node, "end_lineno"): - assert hasattr(node, "end_col_offset") - linemark += ":" - if node.end_lineno != node.lineno: - linemark += f"{node.end_lineno}," - linemark += f"{node.end_col_offset}" - else: - linemark = "" - head = f"{indent}<{node.__class__.__name__}{linemark}" - - named_fields = [ - (name, value) - for name, value in ast.iter_fields(node) - if name not in SKIP_DUMP_FIELDS - ] - if not named_fields: - print(f"{head}>") - elif len(named_fields) == 1 and _is_simple_value(named_fields[0][1]): - field_name, value = named_fields[0] - print(f"{head} {field_name}: {value!r}>") - else: - print(head) - if 0: - print("{}# mro: {}".format( # type: ignore[unreachable] - indent, ", ".join(c.__name__ for c in node.__class__.__mro__[1:]), - )) - next_indent = indent + " " - for field_name, value in named_fields: - prefix = f"{next_indent}{field_name}:" - if _is_simple_value(value): - print(f"{prefix} {value!r}") - elif isinstance(value, list): - print(f"{prefix} [") - for n in value: - if _is_simple_value(n): - print(f"{next_indent} {n!r}") - else: - ast_dump(n, depth + 8, print=print) - print(f"{next_indent}]") - else: - print(prefix) - ast_dump(value, depth + 8, print=print) - - print(f"{indent}>") diff --git a/lab/show_ast.py b/lab/show_ast.py deleted file mode 100644 index 5e5bd04a5..000000000 --- a/lab/show_ast.py +++ /dev/null @@ -1,11 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt - -"""Dump the AST of a file.""" - -import ast -import sys - -from coverage.parser import ast_dump - -ast_dump(ast.parse(open(sys.argv[1], "rb").read())) diff --git a/tests/test_parser.py b/tests/test_parser.py index 5de9c420c..01e7dfdd7 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -5,21 +5,16 @@ from __future__ import annotations -import ast -import os.path import textwrap -import warnings - -from typing import List import pytest from coverage import env from coverage.exceptions import NotPython -from coverage.parser import ast_dump, PythonParser +from coverage.parser import PythonParser -from tests.coveragetest import CoverageTest, TESTS_DIR -from tests.helpers import arcz_to_arcs, re_lines, xfail_pypy38 +from tests.coveragetest import CoverageTest +from tests.helpers import arcz_to_arcs, xfail_pypy38 class PythonParserTest(CoverageTest): @@ -517,32 +512,3 @@ def test_missing_line_ending(self) -> None: parser = self.parse_file("abrupt.py") assert parser.statements == {1} - - -def test_ast_dump() -> None: - # Run the AST_DUMP code to make sure it doesn't fail, with some light - # assertions. Use parser.py as the test code since it is the longest file, - # and fitting, since it's the AST_DUMP code. - import coverage.parser - files = [ - coverage.parser.__file__, - os.path.join(TESTS_DIR, "stress_phystoken.tok"), - ] - for fname in files: - with open(fname) as f: - source = f.read() - num_lines = len(source.splitlines()) - with warnings.catch_warnings(): - # stress_phystoken.tok has deprecation warnings, suppress them. - warnings.filterwarnings("ignore", message=r".*invalid escape sequence") - ast_root = ast.parse(source) - result: List[str] = [] - ast_dump(ast_root, print=result.append) - if num_lines < 100: - continue - assert len(result) > 5 * num_lines - assert result[0] == "" - result_text = "\n".join(result) - assert len(re_lines(r"^\s+>", result_text)) > num_lines - assert len(re_lines(r"", result_text)) > num_lines // 2