diff --git a/pkg/commands/fmt.go b/pkg/commands/fmt.go index 3292af3e9e5a..ab1aef45b697 100644 --- a/pkg/commands/fmt.go +++ b/pkg/commands/fmt.go @@ -113,14 +113,11 @@ func (c *fmtCommand) preRunE(_ *cobra.Command, _ []string) error { } func (c *fmtCommand) execute(_ *cobra.Command, args []string) error { - paths, err := cleanArgs(args) - if err != nil { - return fmt.Errorf("failed to clean arguments: %w", err) - } + paths := cleanArgs(args) c.log.Infof("Formatting Go files...") - err = c.runner.Run(paths) + err := c.runner.Run(paths) if err != nil { return fmt.Errorf("failed to process files: %w", err) } @@ -134,25 +131,15 @@ func (c *fmtCommand) persistentPostRun(_ *cobra.Command, _ []string) { } } -func cleanArgs(args []string) ([]string, error) { +func cleanArgs(args []string) []string { if len(args) == 0 { - abs, err := filepath.Abs(".") - if err != nil { - return nil, err - } - - return []string{abs}, nil + return []string{"."} } var expanded []string for _, arg := range args { - abs, err := filepath.Abs(strings.ReplaceAll(arg, "...", "")) - if err != nil { - return nil, err - } - - expanded = append(expanded, abs) + expanded = append(expanded, filepath.Clean(strings.ReplaceAll(arg, "...", ""))) } - return expanded, nil + return expanded } diff --git a/pkg/goformat/runner.go b/pkg/goformat/runner.go index f87626158179..650fb8f5eaeb 100644 --- a/pkg/goformat/runner.go +++ b/pkg/goformat/runner.go @@ -223,8 +223,14 @@ func NewRunnerOptions(cfg *config.Config, diff, diffColored, stdin bool) (Runner return RunnerOptions{}, fmt.Errorf("get base path: %w", err) } + // Required to be consistent with `RunnerOptions.MatchAnyPattern`. + absBasePath, err := filepath.Abs(basePath) + if err != nil { + return RunnerOptions{}, err + } + opts := RunnerOptions{ - basePath: basePath, + basePath: absBasePath, generated: cfg.Formatters.Exclusions.Generated, diff: diff || diffColored, colors: diffColored, @@ -251,7 +257,12 @@ func (o RunnerOptions) MatchAnyPattern(path string) (bool, error) { return false, nil } - rel, err := filepath.Rel(o.basePath, path) + abs, err := filepath.Abs(path) + if err != nil { + return false, err + } + + rel, err := filepath.Rel(o.basePath, abs) if err != nil { return false, err } @@ -272,7 +283,7 @@ func skipDir(name string) bool { return true default: - return strings.HasPrefix(name, ".") + return strings.HasPrefix(name, ".") && name != "." } } diff --git a/pkg/goformat/runner_test.go b/pkg/goformat/runner_test.go new file mode 100644 index 000000000000..943998f06897 --- /dev/null +++ b/pkg/goformat/runner_test.go @@ -0,0 +1,140 @@ +package goformat + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/golangci/golangci-lint/v2/pkg/config" +) + +func TestRunnerOptions_MatchAnyPattern(t *testing.T) { + testCases := []struct { + desc string + cfg *config.Config + filename string + + assertMatch assert.BoolAssertionFunc + expectedCount int + }{ + { + desc: "match", + cfg: &config.Config{ + Formatters: config.Formatters{ + Exclusions: config.FormatterExclusions{ + Paths: []string{`generated\.go`}, + }, + }, + }, + filename: "generated.go", + assertMatch: assert.True, + expectedCount: 1, + }, + { + desc: "no match", + cfg: &config.Config{ + Formatters: config.Formatters{ + Exclusions: config.FormatterExclusions{ + Paths: []string{`excluded\.go`}, + }, + }, + }, + filename: "test.go", + assertMatch: assert.False, + expectedCount: 0, + }, + { + desc: "no patterns", + cfg: &config.Config{}, + filename: "test.go", + assertMatch: assert.False, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + + testFile := filepath.Join(tmpDir, test.filename) + + err := os.WriteFile(testFile, []byte("package main"), 0o600) + require.NoError(t, err) + + test.cfg.SetConfigDir(tmpDir) + + opts, err := NewRunnerOptions(test.cfg, false, false, false) + require.NoError(t, err) + + match, err := opts.MatchAnyPattern(testFile) + require.NoError(t, err) + + test.assertMatch(t, match) + + require.Len(t, opts.patterns, len(test.cfg.Formatters.Exclusions.Paths)) + + if len(opts.patterns) == 0 { + assert.Empty(t, opts.excludedPathCounter) + } else { + assert.Equal(t, test.expectedCount, opts.excludedPathCounter[opts.patterns[0]]) + } + }) + } +} + +// File structure: +// +// tmp +// ├── project (`realDir`) +// │ ├── .golangci.yml +// │ └── test.go +// └── somewhere +// └── symlink (to "project") +func TestRunnerOptions_MatchAnyPattern_withSymlinks(t *testing.T) { + tmpDir := t.TempDir() + + testFile := filepath.Join(tmpDir, "project", "test.go") + + realDir := filepath.Dir(testFile) + + err := os.MkdirAll(realDir, 0o755) + require.NoError(t, err) + + err = os.WriteFile(testFile, []byte("package main"), 0o600) + require.NoError(t, err) + + symlink := filepath.Join(tmpDir, "somewhere", "symlink") + + err = os.MkdirAll(filepath.Dir(symlink), 0o755) + require.NoError(t, err) + + err = os.Symlink(realDir, symlink) + require.NoError(t, err) + + cfg := &config.Config{ + Formatters: config.Formatters{ + Exclusions: config.FormatterExclusions{ + Paths: []string{`^[^/\\]+\.go$`}, + }, + }, + } + + cfg.SetConfigDir(symlink) + + opts, err := NewRunnerOptions(cfg, false, false, false) + require.NoError(t, err) + + match, err := opts.MatchAnyPattern(filepath.Join(symlink, "test.go")) + require.NoError(t, err) + + assert.True(t, match) + + require.NotEmpty(t, opts.patterns) + require.Len(t, opts.patterns, len(cfg.Formatters.Exclusions.Paths)) + + assert.Equal(t, 1, opts.excludedPathCounter[opts.patterns[0]]) +}