Skip to content

Commit d2d2d6c

Browse files
committed
perf(version-scanner): optimize ignore logic and exclude caches/noise
1 parent 8ca523c commit d2d2d6c

3 files changed

Lines changed: 56 additions & 20 deletions

File tree

scripts/version_scanner/.scannerignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,14 @@ repositories.bzl
2020
*.png
2121
*.gif
2222
*.ico
23+
*.pdf
24+
25+
# Ignore caches and temporary directories
26+
.ruff_cache
27+
.pytest_cache
28+
.mypy_cache
29+
.coverage
30+
.htmlcov
31+
32+
# Ignore data files
33+
*.csv

scripts/version_scanner/tests/unit/test_version_scanner.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,9 @@ def test_scan_repository_wildcard_ignores(tmp_path):
405405
]
406406
)
407407
def test__should_ignore(rel_path, name, ignore_patterns, expected):
408-
from version_scanner import _should_ignore
409-
assert _should_ignore(rel_path, name, ignore_patterns) is expected
408+
from version_scanner import _should_ignore, _preprocess_ignore_patterns
409+
preprocessed = _preprocess_ignore_patterns(ignore_patterns)
410+
assert _should_ignore(rel_path, name, preprocessed) is expected
410411

411412

412413
def test_load_ignore_file(tmp_path):

scripts/version_scanner/version_scanner.py

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,32 @@ def read_package_file(file_path: str) -> List[str]:
499499
return packages
500500

501501

502-
def _should_ignore(rel_path: str, name: str, ignore_patterns: List[str]) -> bool:
502+
def _preprocess_ignore_patterns(ignore_patterns: List[str]) -> List[Tuple[str, str]]:
503+
"""Preprocesses ignore patterns into a classified list for faster matching.
504+
505+
Args:
506+
ignore_patterns: A list of raw ignore patterns from .scannerignore.
507+
508+
Returns:
509+
A list of tuples (type, pattern) where type is 'anchored', 'subpath', or 'filename'.
510+
"""
511+
if not ignore_patterns:
512+
return []
513+
514+
preprocessed = []
515+
for pattern in ignore_patterns:
516+
pattern_lower = pattern.lower()
517+
if '/' in pattern:
518+
if pattern_lower.startswith('/'):
519+
preprocessed.append(('anchored', pattern_lower[1:]))
520+
else:
521+
preprocessed.append(('subpath', pattern_lower))
522+
else:
523+
preprocessed.append(('filename', pattern_lower))
524+
return preprocessed
525+
526+
527+
def _should_ignore(rel_path: str, name: str, preprocessed_patterns: List[Tuple[str, str]]) -> bool:
503528
"""Check if a file or directory matches any of the ignore patterns.
504529
505530
Directories and files can be ignored by providing an ignore pattern in the
@@ -508,29 +533,25 @@ def _should_ignore(rel_path: str, name: str, ignore_patterns: List[str]) -> bool
508533
Args:
509534
rel_path: The relative path of the file or directory from the scan root.
510535
name: The name of the file or directory (basename).
511-
ignore_patterns: A list of ignore patterns (glob-like or subpaths).
536+
preprocessed_patterns: A list of preprocessed ignore patterns.
512537
513538
Returns:
514539
True if the file or directory should be ignored, False otherwise.
515540
"""
516-
if not ignore_patterns:
541+
if not preprocessed_patterns:
517542
return False
518543
name_lower = name.lower()
519544
rel_path_norm = rel_path.replace(os.sep, '/').lower()
520545

521-
for pattern in ignore_patterns:
522-
pattern_lower = pattern.lower()
523-
if '/' in pattern:
524-
if pattern_lower.startswith('/'):
525-
p = pattern_lower[1:]
526-
if fnmatch.fnmatchcase(rel_path_norm, p):
527-
return True
528-
else:
529-
p = pattern_lower
530-
if fnmatch.fnmatchcase(rel_path_norm, p) or fnmatch.fnmatchcase(rel_path_norm, f"*/{p}"):
531-
return True
532-
else:
533-
if fnmatch.fnmatchcase(name_lower, pattern_lower):
546+
for p_type, p_val in preprocessed_patterns:
547+
if p_type == 'anchored':
548+
if fnmatch.fnmatchcase(rel_path_norm, p_val):
549+
return True
550+
elif p_type == 'subpath':
551+
if fnmatch.fnmatchcase(rel_path_norm, p_val) or fnmatch.fnmatchcase(rel_path_norm, f"*/{p_val}"):
552+
return True
553+
elif p_type == 'filename':
554+
if fnmatch.fnmatchcase(name_lower, p_val):
534555
return True
535556
return False
536557

@@ -588,6 +609,9 @@ def scan_repository(
588609
print(f"Error compiling regex for rule {rule['name']}: {e}", file=sys.stderr)
589610
continue
590611

612+
# Preprocess ignore patterns once
613+
preprocessed_ignores = _preprocess_ignore_patterns(ignore_dirs)
614+
591615
print(f"\nScanning repository: {root_path}")
592616
if target_packages:
593617
print(f"Filtering for packages: {target_packages}")
@@ -602,13 +626,13 @@ def get_rel_path(name):
602626
# Prune ignore directories (case-insensitive)
603627
dirs[:] = [
604628
d for d in dirs
605-
if not _should_ignore(get_rel_path(d), d, ignore_dirs)
629+
if not _should_ignore(get_rel_path(d), d, preprocessed_ignores)
606630
]
607631

608632
# Filter ignore files (case-insensitive)
609633
files = [
610634
f for f in files
611-
if not _should_ignore(get_rel_path(f), f, ignore_dirs)
635+
if not _should_ignore(get_rel_path(f), f, preprocessed_ignores)
612636
]
613637

614638
# Layout-agnostic generic subdirectory filtering

0 commit comments

Comments
 (0)