From 56d8561125827b7c98c5642c48e950fa8ee0a81f Mon Sep 17 00:00:00 2001 From: Brian McGee Date: Fri, 14 Jun 2024 10:30:13 +0100 Subject: [PATCH] feat: improve unmatched logic Separates global excludes processing from `Formatter.Wants`. This removes redundant processing of global excludes in each `Formatter.Wants` call. If a file has been globally excluded, we do not emit an `on-unmatched` log message. This should help reduce as reported in #317. Signed-off-by: Brian McGee --- cli/cli.go | 7 +++--- cli/format.go | 20 ++++++++++++---- cli/format_test.go | 15 ++++++------ config/config_test.go | 2 ++ format/formatter.go | 2 -- nix/treefmt.nix | 47 +++++++++++++++++++------------------- test/examples/treefmt.toml | 3 +++ 7 files changed, 55 insertions(+), 41 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index 8d52400..dce7358 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -3,11 +3,12 @@ package cli import ( "os" + "github.com/gobwas/glob" + "git.numtide.com/numtide/treefmt/format" "git.numtide.com/numtide/treefmt/walk" "github.com/alecthomas/kong" "github.com/charmbracelet/log" - "github.com/gobwas/glob" ) func New() *Format { @@ -36,8 +37,8 @@ type Format struct { CpuProfile string `optional:"" help:"The file into which a cpu profile will be written."` - excludes []glob.Glob - formatters map[string]*format.Formatter + formatters map[string]*format.Formatter + globalExcludes []glob.Glob filesCh chan *walk.File processedCh chan *walk.File diff --git a/cli/format.go b/cli/format.go index 94acd76..4aabba5 100644 --- a/cli/format.go +++ b/cli/format.go @@ -97,7 +97,7 @@ func (f *Format) Run() (err error) { } // compile global exclude globs - if f.excludes, err = format.CompileGlobs(cfg.Global.Excludes); err != nil { + if f.globalExcludes, err = format.CompileGlobs(cfg.Global.Excludes); err != nil { return fmt.Errorf("failed to compile global excludes: %w", err) } @@ -105,7 +105,7 @@ func (f *Format) Run() (err error) { f.formatters = make(map[string]*format.Formatter) for name, formatterCfg := range cfg.Formatters { - formatter, err := format.NewFormatter(name, f.TreeRoot, formatterCfg, f.excludes) + formatter, err := format.NewFormatter(name, f.TreeRoot, formatterCfg) if errors.Is(err, format.ErrCommandNotFound) && f.AllowMissingFormatter { log.Debugf("formatter command not found: %v", name) @@ -390,7 +390,15 @@ func (f *Format) applyFormatters(ctx context.Context) func() error { // iterate the files channel for file := range f.filesCh { - // determine a list of formatters that are interested in file + // first check if this file has been globally excluded + if format.PathMatches(file.RelPath, f.globalExcludes) { + log.Debugf("path matched global excludes: %s", file.RelPath) + // mark it as processed and continue to the next + f.processedCh <- file + continue + } + + // check if any formatters are interested in this file var matches []*format.Formatter for _, formatter := range f.formatters { if formatter.Wants(file) { @@ -398,11 +406,13 @@ func (f *Format) applyFormatters(ctx context.Context) func() error { } } + // see if any formatters matched if len(matches) == 0 { if f.OnUnmatched == log.FatalLevel { - return fmt.Errorf("no formatter for path: %s", file.Path) + return fmt.Errorf("no formatter for path: %s", file.RelPath) } - log.Logf(f.OnUnmatched, "no formatter for path: %s", file.Path) + log.Logf(f.OnUnmatched, "no formatter for path: %s", file.RelPath) + // mark it as processed and continue to the next f.processedCh <- file } else { // record the match diff --git a/cli/format_test.go b/cli/format_test.go index e0c7715..8196361 100644 --- a/cli/format_test.go +++ b/cli/format_test.go @@ -39,21 +39,22 @@ func TestOnUnmatched(t *testing.T) { paths := []string{ "go/go.mod", "haskell/haskell.cabal", - "haskell/treefmt.toml", "html/scripts/.gitkeep", - "nixpkgs.toml", "python/requirements.txt", - "rust/Cargo.toml", - "touch.toml", - "treefmt.toml", + // these should not be reported as they're in the global excludes + // - "nixpkgs.toml" + // - "touch.toml" + // - "treefmt.toml" + // - "rust/Cargo.toml" + // - "haskell/treefmt.toml" } out, err := cmd(t, "-C", tempDir, "--allow-missing-formatter", "--on-unmatched", "fatal") - as.ErrorContains(err, fmt.Sprintf("no formatter for path: %s/%s", tempDir, paths[0])) + as.ErrorContains(err, fmt.Sprintf("no formatter for path: %s", paths[0])) checkOutput := func(level string, output []byte) { for _, p := range paths { - as.Contains(string(output), fmt.Sprintf("%s format: no formatter for path: %s/%s", level, tempDir, p)) + as.Contains(string(output), fmt.Sprintf("%s format: no formatter for path: %s", level, p)) } } diff --git a/config/config_test.go b/config/config_test.go index 078b0fb..abb9344 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -14,6 +14,8 @@ func TestReadConfigFile(t *testing.T) { as.NotNil(cfg) + as.Equal([]string{"*.toml"}, cfg.Global.Excludes) + // python python, ok := cfg.Formatters["python"] as.True(ok, "python formatter not found") diff --git a/format/formatter.go b/format/formatter.go index 1db9164..a01646e 100644 --- a/format/formatter.go +++ b/format/formatter.go @@ -100,7 +100,6 @@ func NewFormatter( name string, treeRoot string, cfg *config.Formatter, - globalExcludes []glob.Glob, ) (*Formatter, error) { var err error @@ -136,7 +135,6 @@ func NewFormatter( if err != nil { return nil, fmt.Errorf("failed to compile formatter '%v' excludes: %w", f.name, err) } - f.excludes = append(f.excludes, globalExcludes...) return &f, nil } diff --git a/nix/treefmt.nix b/nix/treefmt.nix index cc50122..1243198 100644 --- a/nix/treefmt.nix +++ b/nix/treefmt.nix @@ -22,33 +22,32 @@ statix.enable = true; }; - settings.formatter = { - deadnix = { - priority = 1; - }; + settings = { + global.excludes = [ + "LICENSE" + # let's not mess with the test folder + "test/**" + # unsupported extensions + "*.{gif,png,svg,tape,mts,lock,mod,sum,toml,env,envrc,gitignore}" + ]; - statix = { - priority = 2; - }; + formatter = { + deadnix = { + priority = 1; + }; - alejandra = { - priority = 3; - }; + statix = { + priority = 2; + }; - prettier = { - options = ["--tab-width" "4"]; - includes = [ - "*.css" - "*.html" - "*.js" - "*.json" - "*.jsx" - "*.md" - "*.mdx" - "*.scss" - "*.ts" - "*.yaml" - ]; + alejandra = { + priority = 3; + }; + + prettier = { + options = ["--tab-width" "4"]; + includes = ["*.{css,html,js,json,jsx,md,mdx,scss,ts,yaml}"]; + }; }; }; }; diff --git a/test/examples/treefmt.toml b/test/examples/treefmt.toml index 064a58f..2291ca8 100644 --- a/test/examples/treefmt.toml +++ b/test/examples/treefmt.toml @@ -1,5 +1,8 @@ # One CLI to format the code tree - https://git.numtide.com/numtide/treefmt +[global] +excludes = ["*.toml"] + [formatter.python] command = "black" includes = ["*.py"]