Skip to content

Commit

Permalink
refactor: no need for our own AST dump function
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Dec 19, 2023
1 parent 1a04c58 commit dafebf1
Show file tree
Hide file tree
Showing 3 changed files with 7 additions and 120 deletions.
76 changes: 4 additions & 72 deletions coverage/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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}>")
11 changes: 0 additions & 11 deletions lab/show_ast.py

This file was deleted.

40 changes: 3 additions & 37 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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] == "<Module"
assert result[-1] == ">"
result_text = "\n".join(result)
assert len(re_lines(r"^\s+>", result_text)) > num_lines
assert len(re_lines(r"<Name @ \d+,\d+(:\d+)? id: '\w+'>", result_text)) > num_lines // 2

0 comments on commit dafebf1

Please sign in to comment.