From 23c86cee027e133181577f749f7716e1ad0986cb Mon Sep 17 00:00:00 2001 From: Brian McGee Date: Fri, 12 Jan 2024 10:08:13 +0000 Subject: [PATCH] feat: dependency cycle detection Signed-off-by: Brian McGee --- internal/cli/format.go | 28 ++++++++++++++++++++++++++-- internal/cli/format_test.go | 21 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/internal/cli/format.go b/internal/cli/format.go index ad19c30..a6df44b 100644 --- a/internal/cli/format.go +++ b/internal/cli/format.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "os/signal" + "strings" "syscall" "time" @@ -66,8 +67,7 @@ func (f *Format) Run() error { formatters := make(map[string]*format.Formatter) - // detect dependency cycles and broken dependencies - // todo dependency cycle detection + // detect broken dependencies for name, config := range cfg.Formatters { before := config.Before if before != "" { @@ -79,6 +79,30 @@ func (f *Format) Run() error { } } + // dependency cycle detection + for name, config := range cfg.Formatters { + var ok bool + var history []string + childName := name + for { + // add to history + history = append(history, childName) + + if config.Before == "" { + break + } else if config.Before == name { + return fmt.Errorf("formatter cycle detected %v", strings.Join(history, " -> ")) + } + + // load child config + childName = config.Before + config, ok = cfg.Formatters[config.Before] + if !ok { + return fmt.Errorf("formatter not found: %v", config.Before) + } + } + } + // init formatters for name, config := range cfg.Formatters { if !includeFormatter(name) { diff --git a/internal/cli/format_test.go b/internal/cli/format_test.go index e96e2f3..cc89893 100644 --- a/internal/cli/format_test.go +++ b/internal/cli/format_test.go @@ -39,6 +39,27 @@ func TestAllowMissingFormatter(t *testing.T) { as.NoError(err) } +func TestDependencyCycle(t *testing.T) { + as := require.New(t) + + tempDir := t.TempDir() + configPath := tempDir + "/treefmt.toml" + + test.WriteConfig(t, configPath, format.Config{ + Formatters: map[string]*format.FormatterConfig{ + "a": {Command: "echo", Before: "b"}, + "b": {Command: "echo", Before: "c"}, + "c": {Command: "echo", Before: "a"}, + "d": {Command: "echo", Before: "e"}, + "e": {Command: "echo", Before: "f"}, + "f": {Command: "echo"}, + }, + }) + + _, err := cmd(t, "--config-file", configPath, "--tree-root", tempDir) + as.ErrorContains(err, "formatter cycle detected a -> b -> c") +} + func TestSpecifyingFormatters(t *testing.T) { as := require.New(t)