mirror of
https://github.com/numtide/treefmt.git
synced 2024-10-03 20:18:11 +03:00
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 <brian@bmcgee.ie>
This commit is contained in:
parent
2ca613901d
commit
56d8561125
@ -3,11 +3,12 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
|
|
||||||
"git.numtide.com/numtide/treefmt/format"
|
"git.numtide.com/numtide/treefmt/format"
|
||||||
"git.numtide.com/numtide/treefmt/walk"
|
"git.numtide.com/numtide/treefmt/walk"
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gobwas/glob"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func New() *Format {
|
func New() *Format {
|
||||||
@ -36,8 +37,8 @@ type Format struct {
|
|||||||
|
|
||||||
CpuProfile string `optional:"" help:"The file into which a cpu profile will be written."`
|
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
|
filesCh chan *walk.File
|
||||||
processedCh chan *walk.File
|
processedCh chan *walk.File
|
||||||
|
@ -97,7 +97,7 @@ func (f *Format) Run() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compile global exclude globs
|
// 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)
|
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)
|
f.formatters = make(map[string]*format.Formatter)
|
||||||
|
|
||||||
for name, formatterCfg := range cfg.Formatters {
|
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 {
|
if errors.Is(err, format.ErrCommandNotFound) && f.AllowMissingFormatter {
|
||||||
log.Debugf("formatter command not found: %v", name)
|
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
|
// iterate the files channel
|
||||||
for file := range f.filesCh {
|
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
|
var matches []*format.Formatter
|
||||||
for _, formatter := range f.formatters {
|
for _, formatter := range f.formatters {
|
||||||
if formatter.Wants(file) {
|
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 len(matches) == 0 {
|
||||||
if f.OnUnmatched == log.FatalLevel {
|
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
|
f.processedCh <- file
|
||||||
} else {
|
} else {
|
||||||
// record the match
|
// record the match
|
||||||
|
@ -39,21 +39,22 @@ func TestOnUnmatched(t *testing.T) {
|
|||||||
paths := []string{
|
paths := []string{
|
||||||
"go/go.mod",
|
"go/go.mod",
|
||||||
"haskell/haskell.cabal",
|
"haskell/haskell.cabal",
|
||||||
"haskell/treefmt.toml",
|
|
||||||
"html/scripts/.gitkeep",
|
"html/scripts/.gitkeep",
|
||||||
"nixpkgs.toml",
|
|
||||||
"python/requirements.txt",
|
"python/requirements.txt",
|
||||||
"rust/Cargo.toml",
|
// these should not be reported as they're in the global excludes
|
||||||
"touch.toml",
|
// - "nixpkgs.toml"
|
||||||
"treefmt.toml",
|
// - "touch.toml"
|
||||||
|
// - "treefmt.toml"
|
||||||
|
// - "rust/Cargo.toml"
|
||||||
|
// - "haskell/treefmt.toml"
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := cmd(t, "-C", tempDir, "--allow-missing-formatter", "--on-unmatched", "fatal")
|
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) {
|
checkOutput := func(level string, output []byte) {
|
||||||
for _, p := range paths {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ func TestReadConfigFile(t *testing.T) {
|
|||||||
|
|
||||||
as.NotNil(cfg)
|
as.NotNil(cfg)
|
||||||
|
|
||||||
|
as.Equal([]string{"*.toml"}, cfg.Global.Excludes)
|
||||||
|
|
||||||
// python
|
// python
|
||||||
python, ok := cfg.Formatters["python"]
|
python, ok := cfg.Formatters["python"]
|
||||||
as.True(ok, "python formatter not found")
|
as.True(ok, "python formatter not found")
|
||||||
|
@ -100,7 +100,6 @@ func NewFormatter(
|
|||||||
name string,
|
name string,
|
||||||
treeRoot string,
|
treeRoot string,
|
||||||
cfg *config.Formatter,
|
cfg *config.Formatter,
|
||||||
globalExcludes []glob.Glob,
|
|
||||||
) (*Formatter, error) {
|
) (*Formatter, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -136,7 +135,6 @@ func NewFormatter(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to compile formatter '%v' excludes: %w", f.name, err)
|
return nil, fmt.Errorf("failed to compile formatter '%v' excludes: %w", f.name, err)
|
||||||
}
|
}
|
||||||
f.excludes = append(f.excludes, globalExcludes...)
|
|
||||||
|
|
||||||
return &f, nil
|
return &f, nil
|
||||||
}
|
}
|
||||||
|
@ -22,33 +22,32 @@
|
|||||||
statix.enable = true;
|
statix.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
settings.formatter = {
|
settings = {
|
||||||
deadnix = {
|
global.excludes = [
|
||||||
priority = 1;
|
"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 = {
|
formatter = {
|
||||||
priority = 2;
|
deadnix = {
|
||||||
};
|
priority = 1;
|
||||||
|
};
|
||||||
|
|
||||||
alejandra = {
|
statix = {
|
||||||
priority = 3;
|
priority = 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
prettier = {
|
alejandra = {
|
||||||
options = ["--tab-width" "4"];
|
priority = 3;
|
||||||
includes = [
|
};
|
||||||
"*.css"
|
|
||||||
"*.html"
|
prettier = {
|
||||||
"*.js"
|
options = ["--tab-width" "4"];
|
||||||
"*.json"
|
includes = ["*.{css,html,js,json,jsx,md,mdx,scss,ts,yaml}"];
|
||||||
"*.jsx"
|
};
|
||||||
"*.md"
|
|
||||||
"*.mdx"
|
|
||||||
"*.scss"
|
|
||||||
"*.ts"
|
|
||||||
"*.yaml"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
# One CLI to format the code tree - https://git.numtide.com/numtide/treefmt
|
# One CLI to format the code tree - https://git.numtide.com/numtide/treefmt
|
||||||
|
|
||||||
|
[global]
|
||||||
|
excludes = ["*.toml"]
|
||||||
|
|
||||||
[formatter.python]
|
[formatter.python]
|
||||||
command = "black"
|
command = "black"
|
||||||
includes = ["*.py"]
|
includes = ["*.py"]
|
||||||
|
Loading…
Reference in New Issue
Block a user