@@ -319,46 +319,59 @@ public ServiceDetectionResult detect(List<CodeNode> nodes, List<CodeEdge> edges,
319319 * Scan the filesystem recursively for build files that indicate service/module boundaries.
320320 * More reliable than scanning node file paths since not all build files produce CodeNodes.
321321 */
322+ /** Directories to skip entirely during filesystem walk. */
323+ private static final java .util .Set <String > SKIP_DIRS = java .util .Set .of (
324+ "node_modules" , ".git" , "target" , "build" , "dist" , ".gradle" ,
325+ ".idea" , ".vscode" , "__pycache__" , ".tox" , ".eggs" , "venv" ,
326+ ".venv" , "vendor" , ".bundle" , "_build" , "deps"
327+ );
328+
322329 private void scanFilesystemForBuildFiles (Path root , Path projectRoot , Map <String , ModuleInfo > modules ) {
323- try (var stream = Files .walk (root , 10 )) {
324- stream .filter (Files ::isRegularFile )
325- .filter (p -> {
326- String name = p .getFileName ().toString ();
327- return BUILD_FILES .containsKey (name )
328- || name .endsWith (CSPROJ_EXTENSION ) || name .endsWith (FSPROJ_EXTENSION )
329- || name .endsWith (VBPROJ_EXTENSION ) || name .endsWith (GEMSPEC_EXTENSION )
330- || name .endsWith (CABAL_EXTENSION ) || name .endsWith (NIMBLE_EXTENSION );
331- })
332- .sorted () // deterministic
333- .forEach (p -> {
334- String name = p .getFileName ().toString ();
335- String relDir = projectRoot .relativize (p .getParent ()).toString ()
336- .replace ('\\' , '/' );
337- if (relDir .equals ("." )) relDir = "" ;
338-
339- // Skip node_modules, .git, target, build directories
340- if (relDir .contains ("node_modules" ) || relDir .contains (".git/" )
341- || relDir .contains ("/target/" ) || relDir .startsWith ("target/" )
342- || relDir .contains ("/build/" ) || relDir .startsWith ("build/" )) {
343- return ;
344- }
330+ try {
331+ Files .walkFileTree (root , java .util .EnumSet .noneOf (java .nio .file .FileVisitOption .class ),
332+ 10 , new java .nio .file .SimpleFileVisitor <Path >() {
333+
334+ @ Override
335+ public java .nio .file .FileVisitResult preVisitDirectory (Path dir , java .nio .file .attribute .BasicFileAttributes attrs ) {
336+ String dirName = dir .getFileName () != null ? dir .getFileName ().toString () : "" ;
337+ if (SKIP_DIRS .contains (dirName )) {
338+ return java .nio .file .FileVisitResult .SKIP_SUBTREE ;
339+ }
340+ return java .nio .file .FileVisitResult .CONTINUE ;
341+ }
345342
346- if (name .endsWith (CSPROJ_EXTENSION ) || name .endsWith (FSPROJ_EXTENSION )
347- || name .endsWith (VBPROJ_EXTENSION )) {
348- modules .putIfAbsent (relDir , new ModuleInfo (relDir , "dotnet" , name ));
349- } else if (name .endsWith (GEMSPEC_EXTENSION )) {
350- modules .putIfAbsent (relDir , new ModuleInfo (relDir , "ruby" , name ));
351- } else if (name .endsWith (CABAL_EXTENSION )) {
352- modules .putIfAbsent (relDir , new ModuleInfo (relDir , "haskell" , name ));
353- } else if (name .endsWith (NIMBLE_EXTENSION )) {
354- modules .putIfAbsent (relDir , new ModuleInfo (relDir , "nim" , name ));
355- } else {
356- String buildTool = BUILD_FILES .get (name );
357- if (buildTool != null ) {
358- registerModule (modules , relDir , buildTool , name );
359- }
343+ @ Override
344+ public java .nio .file .FileVisitResult visitFile (Path file , java .nio .file .attribute .BasicFileAttributes attrs ) {
345+ String name = file .getFileName ().toString ();
346+ boolean isBuildFile = BUILD_FILES .containsKey (name )
347+ || name .endsWith (CSPROJ_EXTENSION ) || name .endsWith (FSPROJ_EXTENSION )
348+ || name .endsWith (VBPROJ_EXTENSION ) || name .endsWith (GEMSPEC_EXTENSION )
349+ || name .endsWith (CABAL_EXTENSION ) || name .endsWith (NIMBLE_EXTENSION );
350+
351+ if (!isBuildFile ) return java .nio .file .FileVisitResult .CONTINUE ;
352+
353+ String relDir = projectRoot .relativize (file .getParent ()).toString ()
354+ .replace ('\\' , '/' );
355+ if (relDir .equals ("." )) relDir = "" ;
356+
357+ if (name .endsWith (CSPROJ_EXTENSION ) || name .endsWith (FSPROJ_EXTENSION )
358+ || name .endsWith (VBPROJ_EXTENSION )) {
359+ modules .putIfAbsent (relDir , new ModuleInfo (relDir , "dotnet" , name ));
360+ } else if (name .endsWith (GEMSPEC_EXTENSION )) {
361+ modules .putIfAbsent (relDir , new ModuleInfo (relDir , "ruby" , name ));
362+ } else if (name .endsWith (CABAL_EXTENSION )) {
363+ modules .putIfAbsent (relDir , new ModuleInfo (relDir , "haskell" , name ));
364+ } else if (name .endsWith (NIMBLE_EXTENSION )) {
365+ modules .putIfAbsent (relDir , new ModuleInfo (relDir , "nim" , name ));
366+ } else {
367+ String buildTool = BUILD_FILES .get (name );
368+ if (buildTool != null ) {
369+ registerModule (modules , relDir , buildTool , name );
360370 }
361- });
371+ }
372+ return java .nio .file .FileVisitResult .CONTINUE ;
373+ }
374+ });
362375 } catch (IOException e ) {
363376 log .warn ("Could not scan filesystem for build files: {}" , e .getMessage ());
364377 }
0 commit comments