Skip to content

Commit bb5fd9e

Browse files
committed
Revert "ast.NodeVisitor for import tracking (RustPython#7229)"
This reverts commit 804a7e4.
1 parent 804a7e4 commit bb5fd9e

File tree

2 files changed

+47
-79
lines changed

2 files changed

+47
-79
lines changed

scripts/update_lib/cmd_todo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def compute_test_todo_list(
334334
test_order = lib_test_order[lib_name].index(test_name)
335335
else:
336336
# Extract lib name from test name (test_foo -> foo)
337-
lib_name = test_name.removeprefix("test_").removeprefix("_test")
337+
lib_name = test_name.removeprefix("test_")
338338
test_order = 0 # Default order for tests not in DEPENDENCIES
339339

340340
# Check if corresponding lib is up-to-date

scripts/update_lib/deps.py

Lines changed: 46 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import difflib
1212
import functools
1313
import pathlib
14+
import re
1415
import shelve
1516
import subprocess
1617

@@ -31,98 +32,62 @@
3132
# === Import parsing utilities ===
3233

3334

34-
class ImportVisitor(ast.NodeVisitor):
35-
def __init__(self) -> None:
36-
self.test_imports = set()
37-
self.lib_imports = set()
35+
def _extract_top_level_code(content: str) -> str:
36+
"""Extract only top-level code from Python content for faster parsing."""
37+
def_idx = content.find("\ndef ")
38+
class_idx = content.find("\nclass ")
3839

39-
def add_import(self, name: str) -> None:
40-
"""
41-
Add an `import` to its correct slot (`test_imports` or `lib_imports`).
40+
indices = [i for i in (def_idx, class_idx) if i != -1]
41+
if indices:
42+
content = content[: min(indices)]
43+
return content.rstrip("\n")
4244

43-
Parameters
44-
----------
45-
name : str
46-
Module name.
47-
"""
48-
if name.startswith("test.support"):
49-
return
5045

51-
real_name = name.split(".", 1)[-1]
52-
if name.startswith("test."):
53-
self.test_imports.add(real_name)
54-
else:
55-
self.lib_imports.add(real_name)
56-
57-
def visit_Import(self, node):
58-
for alias in node.names:
59-
self.add_import(alias.name)
60-
61-
def visit_ImportFrom(self, node):
62-
try:
63-
module = node.module
64-
except AttributeError:
65-
# Ignore `from . import my_internal_module`
66-
return
67-
68-
for name in node.names:
69-
self.add_import(f"{module}.{name}")
70-
71-
def visit_Call(self, node) -> None:
72-
"""
73-
In test files, there's sometimes use of:
74-
75-
```python
76-
import test.support
77-
from test.support import script_helper
78-
79-
script = support.findfile("_test_atexit.py")
80-
script_helper.run_test_script(script)
81-
```
82-
83-
This imports "_test_atexit.py" but does not show as an import node.
84-
"""
85-
func = node.func
86-
if not isinstance(func, ast.Attribute):
87-
return
46+
_FROM_TEST_IMPORT_RE = re.compile(r"^from test import (.+)", re.MULTILINE)
47+
_FROM_TEST_DOT_RE = re.compile(r"^from test\.(\w+)", re.MULTILINE)
48+
_IMPORT_TEST_DOT_RE = re.compile(r"^import test\.(\w+)", re.MULTILINE)
8849

89-
value = func.value
90-
if not isinstance(value, ast.Name):
91-
return
9250

93-
if (value.id != "support") or (func.attr != "findfile"):
94-
return
51+
def parse_test_imports(content: str) -> set[str]:
52+
"""Parse test file content and extract test package dependencies."""
53+
content = _extract_top_level_code(content)
54+
imports = set()
9555

96-
arg = node.args[0]
97-
if not isinstance(arg, ast.Constant):
98-
return
56+
for match in _FROM_TEST_IMPORT_RE.finditer(content):
57+
import_list = match.group(1)
58+
for part in import_list.split(","):
59+
name = part.split()[0].strip()
60+
if name and name not in ("support", "__init__"):
61+
imports.add(name)
9962

100-
target = arg.value
101-
if not target.endswith(".py"):
102-
return
63+
for match in _FROM_TEST_DOT_RE.finditer(content):
64+
dep = match.group(1)
65+
if dep not in ("support", "__init__"):
66+
imports.add(dep)
10367

104-
target = target.removesuffix(".py")
105-
self.add_import(f"test.{target}")
68+
for match in _IMPORT_TEST_DOT_RE.finditer(content):
69+
dep = match.group(1)
70+
if dep not in ("support", "__init__"):
71+
imports.add(dep)
10672

73+
return imports
10774

108-
def parse_test_imports(content: str) -> set[str]:
109-
"""Parse test file content and extract test package dependencies."""
110-
if not (tree := safe_parse_ast(content)):
111-
return set()
11275

113-
visitor = ImportVisitor()
114-
visitor.visit(tree)
115-
return visitor.test_imports
76+
_IMPORT_RE = re.compile(r"^import\s+(\w[\w.]*)", re.MULTILINE)
77+
_FROM_IMPORT_RE = re.compile(r"^from\s+(\w[\w.]*)\s+import", re.MULTILINE)
11678

11779

11880
def parse_lib_imports(content: str) -> set[str]:
11981
"""Parse library file and extract all imported module names."""
120-
if not (tree := safe_parse_ast(content)):
121-
return set()
82+
imports = set()
12283

123-
visitor = ImportVisitor()
124-
visitor.visit(tree)
125-
return visitor.lib_imports
84+
for match in _IMPORT_RE.finditer(content):
85+
imports.add(match.group(1))
86+
87+
for match in _FROM_IMPORT_RE.finditer(content):
88+
imports.add(match.group(1))
89+
90+
return imports
12691

12792

12893
# === TODO marker utilities ===
@@ -139,7 +104,7 @@ def filter_rustpython_todo(content: str) -> str:
139104

140105
def count_rustpython_todo(content: str) -> int:
141106
"""Count lines containing RustPython TODO markers."""
142-
return content.count(TODO_MARKER)
107+
return sum(1 for line in content.splitlines() if TODO_MARKER in line)
143108

144109

145110
def count_todo_in_path(path: pathlib.Path) -> int:
@@ -148,7 +113,10 @@ def count_todo_in_path(path: pathlib.Path) -> int:
148113
content = safe_read_text(path)
149114
return count_rustpython_todo(content) if content else 0
150115

151-
return sum(count_rustpython_todo(content) for _, content in read_python_files(path))
116+
total = 0
117+
for _, content in read_python_files(path):
118+
total += count_rustpython_todo(content)
119+
return total
152120

153121

154122
# === Test utilities ===

0 commit comments

Comments
 (0)