@@ -146,6 +146,27 @@ def generic_visit(self, node: ast.AST) -> None:
146146# Multi-language support helpers
147147# =============================================================================
148148
149+ _VCS_EXCLUDES = frozenset ({".git" , ".hg" , ".svn" })
150+
151+
152+ def parse_dir_excludes (patterns : frozenset [str ]) -> tuple [frozenset [str ], tuple [str , ...], tuple [str , ...]]:
153+ """Split glob patterns into exact names, prefixes, and suffixes.
154+
155+ Patterns ending with ``*`` become prefix matches, patterns starting with ``*``
156+ become suffix matches, and plain strings become exact matches.
157+ """
158+ exact : set [str ] = set ()
159+ prefixes : list [str ] = []
160+ suffixes : list [str ] = []
161+ for p in patterns :
162+ if p .endswith ("*" ):
163+ prefixes .append (p [:- 1 ])
164+ elif p .startswith ("*" ):
165+ suffixes .append (p [1 :])
166+ else :
167+ exact .add (p )
168+ return frozenset (exact ), tuple (prefixes ), tuple (suffixes )
169+
149170
150171def get_files_for_language (
151172 module_root_path : Path , ignore_paths : list [Path ] | None = None , language : Language | None = None
@@ -164,37 +185,44 @@ def get_files_for_language(
164185 if ignore_paths is None :
165186 ignore_paths = []
166187
188+ all_patterns : frozenset [str ]
167189 if language is not None :
168190 support = get_language_support (language )
169191 extensions = support .file_extensions
192+ all_patterns = support .dir_excludes | _VCS_EXCLUDES
170193 else :
171194 extensions = tuple (get_supported_extensions ())
172-
173- # Default directory patterns to always exclude for JS/TS
174- js_ts_default_excludes = {
175- "node_modules" ,
176- "dist" ,
177- "build" ,
178- ".next" ,
179- ".nuxt" ,
180- "coverage" ,
181- ".cache" ,
182- ".turbo" ,
183- ".vercel" ,
184- "__pycache__" ,
185- }
186-
187- files = []
188- for ext in extensions :
189- pattern = f"*{ ext } "
190- for file_path in module_root_path .rglob (pattern ):
191- # Check explicit ignore paths
192- if any (file_path .is_relative_to (ignore_path ) for ignore_path in ignore_paths ):
193- continue
194- # Check default JS/TS excludes in path parts
195- if any (part in js_ts_default_excludes for part in file_path .parts ):
196- continue
197- files .append (file_path )
195+ all_patterns = _VCS_EXCLUDES
196+ for lang in Language :
197+ if is_language_supported (lang ):
198+ all_patterns = all_patterns | get_language_support (lang ).dir_excludes
199+
200+ dir_excludes , prefixes , suffixes = parse_dir_excludes (all_patterns )
201+
202+ ignore_dirs : set [str ] = set ()
203+ ignore_files : set [Path ] = set ()
204+ for p in ignore_paths :
205+ p = Path (p ) if not isinstance (p , Path ) else p
206+ if p .is_file ():
207+ ignore_files .add (p )
208+ else :
209+ ignore_dirs .add (str (p ))
210+
211+ files : list [Path ] = []
212+ for dirpath , dirnames , filenames in os .walk (module_root_path ):
213+ dirnames [:] = [
214+ d
215+ for d in dirnames
216+ if d not in dir_excludes
217+ and not (prefixes and d .startswith (prefixes ))
218+ and not (suffixes and d .endswith (suffixes ))
219+ and str (Path (dirpath ) / d ) not in ignore_dirs
220+ ]
221+ for fname in filenames :
222+ if fname .endswith (extensions ):
223+ fpath = Path (dirpath , fname )
224+ if fpath not in ignore_files :
225+ files .append (fpath )
198226 return files
199227
200228
0 commit comments