@@ -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"\n Scanning 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