Skip to content
Merged
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
4 changes: 3 additions & 1 deletion scanner/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ func ReadExternalDeps(ctx context.Context, root string, manifestByteBudget int64
return nil
}
if info.IsDir() {
if IgnoredDirs[info.Name()] {
// The walk root is what the user asked for, so it is never one of
// the hardcoded ignored directories.
if IgnoredDirs[info.Name()] && path != root {
return filepath.SkipDir
}
return nil
Expand Down
113 changes: 113 additions & 0 deletions scanner/ignoretestdata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package scanner

import (
"context"
"os"
"path/filepath"
"testing"
)

// Fixture repositories under testdata are not the project's source. Scanning
// them inflates file counts and lets a fixture's language change the project's
// reported coverage — a Swift fixture made the whole project report partial.
// Go's own toolchain ignores the directory for the same reason.
func TestScanSkipsTestdataDirectories(t *testing.T) {
root := t.TempDir()
write := func(rel, body string) {
t.Helper()
path := filepath.Join(root, filepath.FromSlash(rel))
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
write("main.go", "package main\n\nfunc main() {}\n")
write("testdata/fixture/main.go", "package main\n\nfunc main() {}\n")
write("internal/testdata/nested/app.go", "package nested\n")

files, err := ScanFiles(context.Background(), root, NewGitIgnoreCache(root), nil, nil)
if err != nil {
t.Fatalf("scan: %v", err)
}
for _, file := range files {
if file.Path != "main.go" {
t.Errorf("scan returned %q, want only main.go — testdata must be skipped at any depth", file.Path)
}
}
if len(files) != 1 {
t.Fatalf("scan returned %d files, want 1", len(files))
}
}

// The hardcoded ignore list must never apply to the directory the user asked
// for. `codemap testdata/`, or `cd vendor && codemap .`, previously matched the
// root's own base name and returned Files: 0 with no error and no explanation.
func TestIgnoredDirNameScansWhenItIsTheRoot(t *testing.T) {
for _, name := range []string{"testdata", "vendor", "node_modules"} {
t.Run(name, func(t *testing.T) {
root := filepath.Join(t.TempDir(), name)
if err := os.MkdirAll(root, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "app.go"), []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}

files, err := ScanFiles(context.Background(), root, NewGitIgnoreCache(root), nil, nil)
if err != nil {
t.Fatalf("scan: %v", err)
}
if len(files) != 1 || files[0].Path != "app.go" {
t.Fatalf("scan of %s/ as the root returned %v, want [app.go]", name, files)
}
})
}
}

// Nested ignored directories are still skipped when the root is itself named
// like one: the exemption covers the root only, not every directory sharing
// its name.
func TestIgnoredDirRootStillSkipsNestedIgnoredDirs(t *testing.T) {
root := filepath.Join(t.TempDir(), "testdata")
nested := filepath.Join(root, "fixture", "testdata")
if err := os.MkdirAll(nested, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "app.go"), []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(nested, "deep.go"), []byte("package deep\n"), 0o644); err != nil {
t.Fatal(err)
}

files, err := ScanFiles(context.Background(), root, NewGitIgnoreCache(root), nil, nil)
if err != nil {
t.Fatalf("scan: %v", err)
}
if len(files) != 1 || files[0].Path != "app.go" {
t.Fatalf("scan returned %v, want only [app.go] — nested testdata must still be skipped", files)
}
}

// A fixture is still scannable when it is itself the root, which is how the
// fixture tests in this package use them.
func TestTestdataFixtureScansWhenItIsTheRoot(t *testing.T) {
root := t.TempDir()
fixture := filepath.Join(root, "testdata", "fixture")
if err := os.MkdirAll(fixture, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(fixture, "app.go"), []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}

files, err := ScanFiles(context.Background(), fixture, NewGitIgnoreCache(fixture), nil, nil)
if err != nil {
t.Fatalf("scan: %v", err)
}
if len(files) != 1 || files[0].Path != "app.go" {
t.Fatalf("scan of the fixture root returned %v, want [app.go]", files)
}
}
17 changes: 12 additions & 5 deletions scanner/walker.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,13 @@ func (c *GitIgnoreCache) ShouldIgnore(absPath string) bool {

// IgnoredDirs are directories to skip during scanning
var IgnoredDirs = map[string]bool{
".git": true,
"node_modules": true,
"vendor": true,
".git": true,
"node_modules": true,
"vendor": true,
// Go's own toolchain ignores testdata, and fixture repositories inside it
// are not the project's source: scanning them inflates file counts and
// lets a fixture's language change the project's reported coverage.
"testdata": true,
"Pods": true,
"build": true,
"DerivedData": true,
Expand Down Expand Up @@ -247,8 +251,11 @@ func ScanFiles(ctx context.Context, root string, cache *GitIgnoreCache, only []s

name := info.Name()

// Fast path: skip hardcoded ignored dirs/files
if IgnoredDirs[name] {
// Fast path: skip hardcoded ignored dirs/files. Never applied to the
// walk root itself: a user who runs `codemap testdata/` or scans from
// inside vendor/ asked for exactly that tree, and skipping it here
// returns Files: 0 with no explanation.
if IgnoredDirs[name] && path != absRoot {
if info.IsDir() {
return filepath.SkipDir
}
Expand Down
Loading