Skip to content

Commit 56d8561

Browse files
committed
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 <[email protected]>
1 parent 2ca6139 commit 56d8561

File tree

7 files changed

+55
-41
lines changed

7 files changed

+55
-41
lines changed

cli/cli.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ package cli
33
import (
44
"os"
55

6+
"github.com/gobwas/glob"
7+
68
"git.numtide.com/numtide/treefmt/format"
79
"git.numtide.com/numtide/treefmt/walk"
810
"github.com/alecthomas/kong"
911
"github.com/charmbracelet/log"
10-
"github.com/gobwas/glob"
1112
)
1213

1314
func New() *Format {
@@ -36,8 +37,8 @@ type Format struct {
3637

3738
CpuProfile string `optional:"" help:"The file into which a cpu profile will be written."`
3839

39-
excludes []glob.Glob
40-
formatters map[string]*format.Formatter
40+
formatters map[string]*format.Formatter
41+
globalExcludes []glob.Glob
4142

4243
filesCh chan *walk.File
4344
processedCh chan *walk.File

cli/format.go

+15-5
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,15 @@ func (f *Format) Run() (err error) {
9797
}
9898

9999
// compile global exclude globs
100-
if f.excludes, err = format.CompileGlobs(cfg.Global.Excludes); err != nil {
100+
if f.globalExcludes, err = format.CompileGlobs(cfg.Global.Excludes); err != nil {
101101
return fmt.Errorf("failed to compile global excludes: %w", err)
102102
}
103103

104104
// initialise formatters
105105
f.formatters = make(map[string]*format.Formatter)
106106

107107
for name, formatterCfg := range cfg.Formatters {
108-
formatter, err := format.NewFormatter(name, f.TreeRoot, formatterCfg, f.excludes)
108+
formatter, err := format.NewFormatter(name, f.TreeRoot, formatterCfg)
109109

110110
if errors.Is(err, format.ErrCommandNotFound) && f.AllowMissingFormatter {
111111
log.Debugf("formatter command not found: %v", name)
@@ -390,19 +390,29 @@ func (f *Format) applyFormatters(ctx context.Context) func() error {
390390
// iterate the files channel
391391
for file := range f.filesCh {
392392

393-
// determine a list of formatters that are interested in file
393+
// first check if this file has been globally excluded
394+
if format.PathMatches(file.RelPath, f.globalExcludes) {
395+
log.Debugf("path matched global excludes: %s", file.RelPath)
396+
// mark it as processed and continue to the next
397+
f.processedCh <- file
398+
continue
399+
}
400+
401+
// check if any formatters are interested in this file
394402
var matches []*format.Formatter
395403
for _, formatter := range f.formatters {
396404
if formatter.Wants(file) {
397405
matches = append(matches, formatter)
398406
}
399407
}
400408

409+
// see if any formatters matched
401410
if len(matches) == 0 {
402411
if f.OnUnmatched == log.FatalLevel {
403-
return fmt.Errorf("no formatter for path: %s", file.Path)
412+
return fmt.Errorf("no formatter for path: %s", file.RelPath)
404413
}
405-
log.Logf(f.OnUnmatched, "no formatter for path: %s", file.Path)
414+
log.Logf(f.OnUnmatched, "no formatter for path: %s", file.RelPath)
415+
// mark it as processed and continue to the next
406416
f.processedCh <- file
407417
} else {
408418
// record the match

cli/format_test.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,22 @@ func TestOnUnmatched(t *testing.T) {
3939
paths := []string{
4040
"go/go.mod",
4141
"haskell/haskell.cabal",
42-
"haskell/treefmt.toml",
4342
"html/scripts/.gitkeep",
44-
"nixpkgs.toml",
4543
"python/requirements.txt",
46-
"rust/Cargo.toml",
47-
"touch.toml",
48-
"treefmt.toml",
44+
// these should not be reported as they're in the global excludes
45+
// - "nixpkgs.toml"
46+
// - "touch.toml"
47+
// - "treefmt.toml"
48+
// - "rust/Cargo.toml"
49+
// - "haskell/treefmt.toml"
4950
}
5051

5152
out, err := cmd(t, "-C", tempDir, "--allow-missing-formatter", "--on-unmatched", "fatal")
52-
as.ErrorContains(err, fmt.Sprintf("no formatter for path: %s/%s", tempDir, paths[0]))
53+
as.ErrorContains(err, fmt.Sprintf("no formatter for path: %s", paths[0]))
5354

5455
checkOutput := func(level string, output []byte) {
5556
for _, p := range paths {
56-
as.Contains(string(output), fmt.Sprintf("%s format: no formatter for path: %s/%s", level, tempDir, p))
57+
as.Contains(string(output), fmt.Sprintf("%s format: no formatter for path: %s", level, p))
5758
}
5859
}
5960

config/config_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ func TestReadConfigFile(t *testing.T) {
1414

1515
as.NotNil(cfg)
1616

17+
as.Equal([]string{"*.toml"}, cfg.Global.Excludes)
18+
1719
// python
1820
python, ok := cfg.Formatters["python"]
1921
as.True(ok, "python formatter not found")

format/formatter.go

-2
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ func NewFormatter(
100100
name string,
101101
treeRoot string,
102102
cfg *config.Formatter,
103-
globalExcludes []glob.Glob,
104103
) (*Formatter, error) {
105104
var err error
106105

@@ -136,7 +135,6 @@ func NewFormatter(
136135
if err != nil {
137136
return nil, fmt.Errorf("failed to compile formatter '%v' excludes: %w", f.name, err)
138137
}
139-
f.excludes = append(f.excludes, globalExcludes...)
140138

141139
return &f, nil
142140
}

nix/treefmt.nix

+23-24
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,32 @@
2222
statix.enable = true;
2323
};
2424

25-
settings.formatter = {
26-
deadnix = {
27-
priority = 1;
28-
};
25+
settings = {
26+
global.excludes = [
27+
"LICENSE"
28+
# let's not mess with the test folder
29+
"test/**"
30+
# unsupported extensions
31+
"*.{gif,png,svg,tape,mts,lock,mod,sum,toml,env,envrc,gitignore}"
32+
];
2933

30-
statix = {
31-
priority = 2;
32-
};
34+
formatter = {
35+
deadnix = {
36+
priority = 1;
37+
};
3338

34-
alejandra = {
35-
priority = 3;
36-
};
39+
statix = {
40+
priority = 2;
41+
};
42+
43+
alejandra = {
44+
priority = 3;
45+
};
3746

38-
prettier = {
39-
options = ["--tab-width" "4"];
40-
includes = [
41-
"*.css"
42-
"*.html"
43-
"*.js"
44-
"*.json"
45-
"*.jsx"
46-
"*.md"
47-
"*.mdx"
48-
"*.scss"
49-
"*.ts"
50-
"*.yaml"
51-
];
47+
prettier = {
48+
options = ["--tab-width" "4"];
49+
includes = ["*.{css,html,js,json,jsx,md,mdx,scss,ts,yaml}"];
50+
};
5251
};
5352
};
5453
};

test/examples/treefmt.toml

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# One CLI to format the code tree - https://git.numtide.com/numtide/treefmt
22

3+
[global]
4+
excludes = ["*.toml"]
5+
36
[formatter.python]
47
command = "black"
58
includes = ["*.py"]

0 commit comments

Comments
 (0)