fix(lint): preserve explicit empty file scopes
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
71529076b3
commit
5da4a1dbd1
6 changed files with 116 additions and 10 deletions
|
|
@ -47,6 +47,10 @@ func Detect(path string) []string {
|
|||
return sortedDetectedLanguages(seen)
|
||||
}
|
||||
|
||||
if shouldSkipTraversalRoot(path) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
_ = filepath.WalkDir(path, func(currentPath string, entry fs.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -48,3 +48,12 @@ func TestDetectFromFiles_Good(t *testing.T) {
|
|||
func TestDetect_MissingPathReturnsEmptySlice(t *testing.T) {
|
||||
assert.Equal(t, []string{}, Detect(filepath.Join(t.TempDir(), "missing")))
|
||||
}
|
||||
|
||||
func TestDetect_Good_SkipsHiddenRootDirectory(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
hiddenDir := filepath.Join(dir, ".core")
|
||||
require.NoError(t, os.MkdirAll(hiddenDir, 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(hiddenDir, "main.go"), []byte("package main\n"), 0o644))
|
||||
|
||||
assert.Equal(t, []string{}, Detect(hiddenDir))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,20 @@ func DetectLanguage(filename string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func shouldSkipTraversalRoot(path string) bool {
|
||||
cleanedPath := filepath.Clean(path)
|
||||
if cleanedPath == "." {
|
||||
return false
|
||||
}
|
||||
|
||||
base := filepath.Base(cleanedPath)
|
||||
if base == "." || base == string(filepath.Separator) {
|
||||
return false
|
||||
}
|
||||
|
||||
return IsExcludedDir(base)
|
||||
}
|
||||
|
||||
// Scanner walks directory trees and matches files against lint rules.
|
||||
type Scanner struct {
|
||||
matcher *Matcher
|
||||
|
|
@ -82,6 +96,10 @@ func NewScanner(rules []Rule) (*Scanner, error) {
|
|||
func (s *Scanner) ScanDir(root string) ([]Finding, error) {
|
||||
var findings []Finding
|
||||
|
||||
if shouldSkipTraversalRoot(root) {
|
||||
return findings, nil
|
||||
}
|
||||
|
||||
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -238,6 +238,32 @@ func TestScanDir_Good_Subdirectories(t *testing.T) {
|
|||
require.Len(t, findings, 1)
|
||||
}
|
||||
|
||||
func TestScanDir_Good_SkipsHiddenRootDirectory(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
hiddenDir := filepath.Join(dir, ".git")
|
||||
require.NoError(t, os.MkdirAll(hiddenDir, 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(hiddenDir, "main.go"), []byte("// TODO: hidden\n"), 0o644))
|
||||
|
||||
rules := []Rule{
|
||||
{
|
||||
ID: "test-001",
|
||||
Title: "Found a TODO",
|
||||
Severity: "low",
|
||||
Languages: []string{"go"},
|
||||
Pattern: `TODO`,
|
||||
Fix: "Remove TODO",
|
||||
Detection: "regex",
|
||||
},
|
||||
}
|
||||
|
||||
s, err := NewScanner(rules)
|
||||
require.NoError(t, err)
|
||||
|
||||
findings, err := s.ScanDir(hiddenDir)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, findings)
|
||||
}
|
||||
|
||||
func TestScanDir_Bad_NonexistentDir(t *testing.T) {
|
||||
rules := []Rule{
|
||||
{
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ func (s *Service) Run(ctx context.Context, input RunInput) (Report, error) {
|
|||
input.FailOn = config.FailOn
|
||||
}
|
||||
|
||||
files, err := s.scopeFiles(input.Path, config, input, schedule)
|
||||
files, scoped, err := s.scopeFiles(input.Path, config, input, schedule)
|
||||
if err != nil {
|
||||
return Report{}, err
|
||||
}
|
||||
|
|
@ -119,8 +119,21 @@ func (s *Service) Run(ctx context.Context, input RunInput) (Report, error) {
|
|||
report.Summary.Passed = passesThreshold(report.Summary, input.FailOn)
|
||||
return report, nil
|
||||
}
|
||||
if scoped && len(files) == 0 {
|
||||
report := Report{
|
||||
Project: projectName(input.Path),
|
||||
Timestamp: startedAt,
|
||||
Duration: time.Since(startedAt).Round(time.Millisecond).String(),
|
||||
Languages: []string{},
|
||||
Tools: []ToolRun{},
|
||||
Findings: []Finding{},
|
||||
Summary: Summarise(nil),
|
||||
}
|
||||
report.Summary.Passed = passesThreshold(report.Summary, input.FailOn)
|
||||
return report, nil
|
||||
}
|
||||
|
||||
languages := s.languagesForInput(input, files)
|
||||
languages := s.languagesForInput(input, files, scoped)
|
||||
selectedAdapters := s.selectAdapters(config, languages, input, schedule)
|
||||
|
||||
var findings []Finding
|
||||
|
|
@ -302,30 +315,33 @@ func (s *Service) RemoveHook(projectPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) languagesForInput(input RunInput, files []string) []string {
|
||||
func (s *Service) languagesForInput(input RunInput, files []string, scoped bool) []string {
|
||||
if input.Lang != "" {
|
||||
return []string{input.Lang}
|
||||
}
|
||||
if len(files) > 0 {
|
||||
if scoped {
|
||||
return detectFromFiles(files)
|
||||
}
|
||||
return Detect(input.Path)
|
||||
}
|
||||
|
||||
func (s *Service) scopeFiles(projectPath string, config LintConfig, input RunInput, schedule *Schedule) ([]string, error) {
|
||||
func (s *Service) scopeFiles(projectPath string, config LintConfig, input RunInput, schedule *Schedule) ([]string, bool, error) {
|
||||
if len(input.Files) > 0 {
|
||||
return slices.Clone(input.Files), nil
|
||||
return slices.Clone(input.Files), true, nil
|
||||
}
|
||||
if input.Hook {
|
||||
return s.stagedFiles(projectPath)
|
||||
files, err := s.stagedFiles(projectPath)
|
||||
return files, true, err
|
||||
}
|
||||
if schedule != nil && len(schedule.Paths) > 0 {
|
||||
return collectConfiguredFiles(projectPath, schedule.Paths, config.Exclude)
|
||||
files, err := collectConfiguredFiles(projectPath, schedule.Paths, config.Exclude)
|
||||
return files, true, err
|
||||
}
|
||||
if !slices.Equal(config.Paths, DefaultConfig().Paths) || !slices.Equal(config.Exclude, DefaultConfig().Exclude) {
|
||||
return collectConfiguredFiles(projectPath, config.Paths, config.Exclude)
|
||||
files, err := collectConfiguredFiles(projectPath, config.Paths, config.Exclude)
|
||||
return files, true, err
|
||||
}
|
||||
return nil, nil
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (s *Service) selectAdapters(config LintConfig, languages []string, input RunInput, schedule *Schedule) []Adapter {
|
||||
|
|
@ -394,6 +410,9 @@ func collectConfiguredFiles(projectPath string, paths []string, excludes []strin
|
|||
if err != nil {
|
||||
return nil, coreerr.E("collectConfiguredFiles", "stat "+absolutePath, err)
|
||||
}
|
||||
if info.IsDir() && shouldSkipTraversalRoot(absolutePath) {
|
||||
continue
|
||||
}
|
||||
|
||||
addFile := func(candidate string) {
|
||||
relativePath := relativeConfiguredPath(projectPath, candidate)
|
||||
|
|
|
|||
|
|
@ -130,6 +130,36 @@ func Run() {
|
|||
assert.False(t, report.Summary.Passed)
|
||||
}
|
||||
|
||||
func TestServiceRun_Good_SkipsHiddenConfiguredRootDirectory(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/test\n"), 0o644))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(dir, ".hidden"), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, ".hidden", "scoped.go"), []byte(`package sample
|
||||
|
||||
type service struct{}
|
||||
|
||||
func (service) Process(string) error { return nil }
|
||||
|
||||
func Run() {
|
||||
svc := service{}
|
||||
_ = svc.Process("scoped")
|
||||
}
|
||||
`), 0o644))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(dir, ".core"), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, ".core", "lint.yaml"), []byte("paths:\n - .hidden\n"), 0o644))
|
||||
|
||||
svc := &Service{adapters: []Adapter{newCatalogAdapter()}}
|
||||
report, err := svc.Run(context.Background(), RunInput{
|
||||
Path: dir,
|
||||
FailOn: "warning",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, report.Findings)
|
||||
assert.Empty(t, report.Tools)
|
||||
assert.True(t, report.Summary.Passed)
|
||||
}
|
||||
|
||||
func TestServiceRun_Good_UsesNamedSchedule(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/test\n"), 0o644))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue