Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions internal/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func findJavaFilesGit(root string) ([]string, error) {
if line == "" {
continue
}
if isExcludedInfraPath(line) {
continue
}

// ls-files output is relative to git root.
absPath := filepath.Join(gitRoot, line)
// Keep only files under target root (avoids picking up
Expand All @@ -91,25 +95,39 @@ func findJavaFilesGit(root string) ([]string, error) {
return files, nil
}

func isExcludedInfraPath(path string) bool {
parts := strings.FieldsFunc(filepath.Clean(path), func(r rune) bool {
return r == '/' || r == filepath.Separator
})
for i, part := range parts {
if part == "vendor" || part == "node_modules" || part == ".git" || part == ".mvn" || part == ".gradle" {
return true
}
if part == "wrapper" && i > 0 && parts[i-1] == "gradle" {
return true
}
}
return false
}

// findJavaFilesWalk is the recursive filesystem walk fallback.
// It skips vendor/, node_modules/, .git/, .mvn/, .gradle/,
// and gradle/wrapper/ (build-wrapper/infrastructure directories).
func findJavaFilesWalk(root string) ([]string, error) {
var files []string
rootParent := filepath.Dir(filepath.Clean(root))

err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

if info.IsDir() {
name := info.Name()
// Skip common non-source and build-wrapper/infrastructure directories
if name == "vendor" || name == "node_modules" || name == ".git" || name == ".mvn" || name == ".gradle" {
return filepath.SkipDir
relPath, err := filepath.Rel(rootParent, path)
if err != nil {
return err
}
// Skip gradle/wrapper build wrapper infrastructure
if name == "wrapper" && filepath.Base(filepath.Dir(path)) == "gradle" {
if isExcludedInfraPath(relPath) {
return filepath.SkipDir
}
return nil
Expand Down
125 changes: 125 additions & 0 deletions internal/discovery/discovery_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package discovery

import (
"os"
"os/exec"
"path/filepath"
"reflect"
"sort"
"testing"
)

func TestIsExcludedInfraPath(t *testing.T) {
t.Parallel()

tests := []struct {
name string
path string
want bool
}{
{name: "maven wrapper nested", path: "complete/.mvn/wrapper/MavenWrapperDownloader.java", want: true},
{name: "maven wrapper root", path: ".mvn/wrapper/X.java", want: true},
{name: "vendor", path: "vendor/foo/Bar.java", want: true},
{name: "node modules", path: "node_modules/pkg/Thing.java", want: true},
{name: "git dir", path: ".git/hooks/whatever.java", want: true},
{name: "gradle cache", path: ".gradle/cache/Y.java", want: true},
{name: "gradle wrapper", path: "proj/gradle/wrapper/GradleWrapperMain.java", want: true},
{name: "normal source true positive", path: "src/main/java/com/example/HotelServiceEvent.java", want: false},
{name: "normal app", path: "src/main/java/com/example/App.java", want: false},
{name: "mvn substring only", path: "mvnutil/src/Main.java", want: false},
{name: "gradle substring only", path: "mygradlestuff/src/X.java", want: false},
{name: "wrapper without gradle parent", path: "wrapper/Foo.java", want: false},
{name: "wrapper under src", path: "src/wrapper/Helper.java", want: false},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := isExcludedInfraPath(tc.path); got != tc.want {
t.Fatalf("isExcludedInfraPath(%q) = %v, want %v", tc.path, got, tc.want)
}
})
}
}

func TestFindJavaFilesWalkExcludesInfraPaths(t *testing.T) {
root := t.TempDir()

mustWriteFile(t, root, "src/main/java/Live.java")
mustWriteFile(t, root, ".mvn/wrapper/MavenWrapperDownloader.java")
mustWriteFile(t, root, "gradle/wrapper/GradleWrapperMain.java")
mustWriteFile(t, root, "vendor/x/V.java")

files, err := FindJavaFiles(root)
if err != nil {
t.Fatalf("FindJavaFiles() error = %v", err)
}

got := relPaths(t, root, files)
want := []string{"src/main/java/Live.java"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("FindJavaFiles() = %v, want %v", got, want)
}
}

func TestFindJavaFilesWithGitExcludesInfraPaths(t *testing.T) {
if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not available")
}

root := t.TempDir()
mustWriteFile(t, root, "src/main/java/Live.java")
mustWriteFile(t, root, ".mvn/wrapper/MavenWrapperDownloader.java")
mustWriteFile(t, root, "gradle/wrapper/GradleWrapperMain.java")
mustWriteFile(t, root, "vendor/x/V.java")

git(t, root, "init")
git(t, root, "add", ".")

files, err := FindJavaFilesWithGit(root, true)
if err != nil {
t.Fatalf("FindJavaFilesWithGit() error = %v", err)
}

got := relPaths(t, root, files)
want := []string{"src/main/java/Live.java"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("FindJavaFilesWithGit() = %v, want %v", got, want)
}
}

func mustWriteFile(t *testing.T, root, rel string) {
t.Helper()
path := filepath.Join(root, filepath.FromSlash(rel))
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", filepath.Dir(path), err)
}
if err := os.WriteFile(path, []byte("class X {}\n"), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
}

func relPaths(t *testing.T, root string, files []string) []string {
t.Helper()
rel := make([]string, 0, len(files))
for _, file := range files {
p, err := filepath.Rel(root, file)
if err != nil {
t.Fatalf("filepath.Rel(%q, %q) error = %v", root, file, err)
}
rel = append(rel, filepath.ToSlash(p))
}
sort.Strings(rel)
return rel
}

func git(t *testing.T, dir string, args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v failed: %v\n%s", args, err, out)
}
}
Loading