Skip to content

Commit e2153e0

Browse files
committed
Keep source file filtering independent of parser grammars
1 parent 15603f7 commit e2153e0

7 files changed

Lines changed: 97 additions & 45 deletions

File tree

‎codewiki/src/be/dependency_analyzer/analysis/analysis_service.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111
from pathlib import Path
1212
from typing import TYPE_CHECKING, Any
1313

14-
from codewiki.src.be.dependency_analyzer.analysis.call_graph_analyzer import (
15-
CallGraphAnalyzer,
16-
is_skipped_source_file,
17-
)
14+
from codewiki.src.be.dependency_analyzer.analysis.call_graph_analyzer import CallGraphAnalyzer
1815
from codewiki.src.be.dependency_analyzer.analysis.cloning import (
1916
cleanup_repository,
2017
clone_repository,
@@ -23,6 +20,7 @@
2320
from codewiki.src.be.dependency_analyzer.analysis.repo_analyzer import RepoAnalyzer
2421
from codewiki.src.be.dependency_analyzer.models.analysis import AnalysisResult
2522
from codewiki.src.be.dependency_analyzer.models.core import Repository
23+
from codewiki.src.be.dependency_analyzer.utils.file_selection import is_skipped_source_file
2624
from codewiki.src.be.dependency_analyzer.utils.security import assert_safe_path, safe_open_text
2725

2826
if TYPE_CHECKING: # pragma: no cover

‎codewiki/src/be/dependency_analyzer/analysis/call_graph_analyzer.py‎

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,6 @@
2828
logger = logging.getLogger(__name__)
2929

3030

31-
def is_skipped_source_file(file_info: dict, base_dir: str) -> bool:
32-
"""Reuse explicit analyzer exclusions when retaining symbol-free files."""
33-
language = file_info.get("language")
34-
if language == "go":
35-
from codewiki.src.be.dependency_analyzer.analyzers.go import is_skipped_go_file
36-
37-
return is_skipped_go_file(file_info["path"])
38-
if language == "zig":
39-
from codewiki.src.be.dependency_analyzer.analyzers.zig import is_skipped_zig_file
40-
41-
return is_skipped_zig_file(file_info["path"])
42-
if language == "php":
43-
from codewiki.src.be.dependency_analyzer.analyzers.php import is_template_file
44-
45-
return is_template_file(str(Path(base_dir) / file_info["path"]))
46-
return False
47-
48-
4931
class TimeoutError(BaseException):
5032
"""Raised when file parsing exceeds timeout.
5133

‎codewiki/src/be/dependency_analyzer/analyzers/go.py‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from tree_sitter_language_pack import get_parser
5252

5353
from codewiki.src.be.dependency_analyzer.models.core import CallRelationship, Node
54+
from codewiki.src.be.dependency_analyzer.utils.file_selection import is_skipped_go_file
5455

5556
logger = logging.getLogger(__name__)
5657

@@ -222,12 +223,6 @@ def split_go_package_ref(callee: str) -> tuple[str, str] | None:
222223
return None
223224

224225

225-
def is_skipped_go_file(relative_path: str) -> bool:
226-
"""`_test.go` files and vendored dependencies yield no components."""
227-
posix = Path(relative_path).as_posix()
228-
return posix.endswith("_test.go") or "vendor" in posix.split("/")[:-1]
229-
230-
231226
def package_dir_of(relative_path: str) -> str:
232227
return posixpath.dirname(Path(relative_path).as_posix())
233228

‎codewiki/src/be/dependency_analyzer/analyzers/php.py‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
from tree_sitter import Parser, Language
1414
import tree_sitter_php
1515
from codewiki.src.be.dependency_analyzer.models.core import Node, CallRelationship
16+
from codewiki.src.be.dependency_analyzer.utils.file_selection import (
17+
PHP_TEMPLATE_DIRECTORIES,
18+
PHP_TEMPLATE_PATTERNS,
19+
is_php_template_file,
20+
)
1621

1722
logger = logging.getLogger(__name__)
1823

@@ -63,22 +68,13 @@
6368
}
6469

6570
# Template file patterns to skip
66-
TEMPLATE_PATTERNS: Set[str] = {".blade.php", ".phtml", ".twig.php"}
67-
TEMPLATE_DIRECTORIES: Set[str] = {"views", "templates", "resources/views"}
71+
TEMPLATE_PATTERNS = PHP_TEMPLATE_PATTERNS
72+
TEMPLATE_DIRECTORIES = PHP_TEMPLATE_DIRECTORIES
6873

6974
# Maximum recursion depth to prevent stack overflow
7075
MAX_RECURSION_DEPTH = 100
7176

7277

73-
def is_template_file(file_path: str) -> bool:
74-
"""The same exclusion policy for parsing and the source-file inventory."""
75-
file_str = str(file_path)
76-
return any(file_str.endswith(pattern) for pattern in TEMPLATE_PATTERNS) or any(
77-
f"/{directory}/" in file_str or f"\\{directory}\\" in file_str
78-
for directory in TEMPLATE_DIRECTORIES
79-
)
80-
81-
8278
class NamespaceResolver:
8379
"""Resolves PHP class names to fully qualified names using use statements."""
8480

@@ -147,7 +143,7 @@ def __init__(self, file_path: str, content: str, repo_path: str = None):
147143

148144
def _is_template_file(self) -> bool:
149145
"""Check if file is a PHP template that should be skipped."""
150-
return is_template_file(str(self.file_path))
146+
return is_php_template_file(str(self.file_path))
151147

152148
def _get_module_path(self) -> str:
153149
"""Get module path for the file."""

‎codewiki/src/be/dependency_analyzer/analyzers/zig.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
from tree_sitter_language_pack import get_parser
5151

5252
from codewiki.src.be.dependency_analyzer.models.core import CallRelationship, Node
53+
from codewiki.src.be.dependency_analyzer.utils.file_selection import is_skipped_zig_file
5354

5455
logger = logging.getLogger(__name__)
5556

@@ -61,7 +62,6 @@
6162
"opaque": ("class", "opaque"),
6263
}
6364

64-
_SKIPPED_DIRS = {"zig-cache", ".zig-cache", "zig-out"}
6565
_EXTERNAL_IMPORTS = {"std", "builtin", "root"}
6666
# Constructors by convention: their result is a value of the owning type.
6767
_CONSTRUCTOR_NAMES = {"init", "initCapacity", "create", "new", "open", "parse"}
@@ -84,10 +84,6 @@
8484
_UNKNOWN = ("unknown",)
8585

8686

87-
def is_skipped_zig_file(relative_path: str) -> bool:
88-
return any(part in _SKIPPED_DIRS for part in Path(relative_path).parts[:-1])
89-
90-
9187
@lru_cache(maxsize=64)
9288
def _parse_build_zig(path: str, mtime_ns: int) -> tuple[tuple[str, str], ...]:
9389
try:
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Explicit source-file exclusions, without importing optional parser grammars."""
2+
3+
from pathlib import Path
4+
5+
PHP_TEMPLATE_PATTERNS = {".blade.php", ".phtml", ".twig.php"}
6+
PHP_TEMPLATE_DIRECTORIES = {"views", "templates", "resources/views"}
7+
ZIG_SKIPPED_DIRS = {"zig-cache", ".zig-cache", "zig-out"}
8+
9+
10+
def is_skipped_go_file(relative_path: str) -> bool:
11+
"""`_test.go` files and vendored dependencies yield no components."""
12+
posix = Path(relative_path).as_posix()
13+
return posix.endswith("_test.go") or "vendor" in posix.split("/")[:-1]
14+
15+
16+
def is_skipped_zig_file(relative_path: str) -> bool:
17+
return any(part in ZIG_SKIPPED_DIRS for part in Path(relative_path).parts[:-1])
18+
19+
20+
def is_php_template_file(file_path: str) -> bool:
21+
file_str = str(file_path)
22+
return any(file_str.endswith(pattern) for pattern in PHP_TEMPLATE_PATTERNS) or any(
23+
f"/{directory}/" in file_str or f"\\{directory}\\" in file_str
24+
for directory in PHP_TEMPLATE_DIRECTORIES
25+
)
26+
27+
28+
def is_skipped_source_file(file_info: dict, base_dir: str) -> bool:
29+
"""Apply the same exclusions for parsing and symbol-free file retention."""
30+
language = file_info.get("language")
31+
if language == "go":
32+
return is_skipped_go_file(file_info["path"])
33+
if language == "zig":
34+
return is_skipped_zig_file(file_info["path"])
35+
if language == "php":
36+
return is_php_template_file(str(Path(base_dir) / file_info["path"]))
37+
return False

‎tests/test_module_scope.py‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import asyncio
6+
import builtins
67
import json
78
from pathlib import Path
89
from types import SimpleNamespace
@@ -191,6 +192,53 @@ def test_capped_artifact_does_not_hide_symbol_free_source_code(tmp_path):
191192
assert get_module_token_count(components) >= count_tokens(source)
192193

193194

195+
@pytest.mark.parametrize(
196+
("language", "included", "excluded"),
197+
[
198+
("go", "source.go", "vendor/source.go"),
199+
("zig", "source.zig", "zig-out/source.zig"),
200+
("php", "source.php", "templates/source.php"),
201+
],
202+
)
203+
def test_file_exclusions_do_not_import_parsers(monkeypatch, language, included, excluded):
204+
from codewiki.src.be.dependency_analyzer.utils.file_selection import is_skipped_source_file
205+
206+
original_import = builtins.__import__
207+
208+
def without_grammars(name, *args, **kwargs):
209+
if name.startswith(("tree_sitter", "codewiki.src.be.dependency_analyzer.analyzers")):
210+
raise ModuleNotFoundError(name)
211+
return original_import(name, *args, **kwargs)
212+
213+
monkeypatch.setattr(builtins, "__import__", without_grammars)
214+
assert not is_skipped_source_file({"language": language, "path": included}, "/repo")
215+
assert is_skipped_source_file({"language": language, "path": excluded}, "/repo")
216+
217+
218+
def test_symbol_free_file_survives_missing_php_grammar(tmp_path, monkeypatch):
219+
repo = tmp_path / "repo"
220+
repo.mkdir()
221+
source = "<?php\nconst CAPACITY = 16;\n"
222+
(repo / "source.php").write_text(source)
223+
templates = repo / "templates"
224+
templates.mkdir()
225+
(templates / "view.php").write_text("<?php echo 'view';\n")
226+
original_import = builtins.__import__
227+
228+
def without_php(name, *args, **kwargs):
229+
if name in ("tree_sitter_php", "codewiki.src.be.dependency_analyzer.analyzers.php"):
230+
raise ModuleNotFoundError(name)
231+
return original_import(name, *args, **kwargs)
232+
233+
monkeypatch.setattr(builtins, "__import__", without_php)
234+
components, leaves = DependencyGraphBuilder(
235+
_config(repo, tmp_path / "out")
236+
).build_dependency_graph()
237+
assert component_files(list(components), components) == {"source.php"}
238+
assert components["source.php::__file__"].source_code == source
239+
assert complete_file_candidates(leaves, components) == ["source.php::__file__"]
240+
241+
194242
def test_recursive_size_uses_child_files_not_whole_graph(tmp_path):
195243
components = {
196244
cid: _node(cid, source)

0 commit comments

Comments
 (0)