From 9f39211f307a078e07e476d694fcfeed7d5abad3 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Thu, 5 Feb 2026 08:37:11 +0000 Subject: feat(formatter): Dispatch adapter by file extension for multi-language support --- formatter.go | 19 +++- formatter_test.go | 28 ++--- main.go | 18 +++- parity_test.go | 308 ------------------------------------------------------ 4 files changed, 42 insertions(+), 331 deletions(-) delete mode 100644 parity_test.go diff --git a/formatter.go b/formatter.go index c6b5902..3d4e873 100644 --- a/formatter.go +++ b/formatter.go @@ -1,6 +1,9 @@ package main -import "github.com/Fuwn/iku/engine" +import ( + "github.com/Fuwn/iku/engine" + "path/filepath" +) type CommentMode int @@ -21,9 +24,8 @@ type lineInformation struct { isStartLine bool } -func (f *Formatter) Format(source []byte) ([]byte, error) { - adapter := &GoAdapter{} - _, events, err := adapter.Analyze(source) +func (f *Formatter) Format(source []byte, filename string) ([]byte, error) { + _, events, err := analyzeSource(source, filename) if err != nil { return nil, err @@ -33,3 +35,12 @@ func (f *Formatter) Format(source []byte) ([]byte, error) { return []byte(formattingEngine.FormatToString(events)), nil } + +func analyzeSource(source []byte, filename string) ([]byte, []engine.LineEvent, error) { + switch filepath.Ext(filename) { + case ".js", ".ts", ".jsx", ".tsx": + return (&EcmaScriptAdapter{}).Analyze(source) + default: + return (&GoAdapter{}).Analyze(source) + } +} diff --git a/formatter_test.go b/formatter_test.go index efc9b6e..6ce7edd 100644 --- a/formatter_test.go +++ b/formatter_test.go @@ -23,7 +23,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -58,7 +58,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -97,7 +97,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -132,7 +132,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -169,7 +169,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -203,7 +203,7 @@ func bar() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -231,7 +231,7 @@ type Foo struct { var x = 1 ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -272,7 +272,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -308,7 +308,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -346,7 +346,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -374,7 +374,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -413,7 +413,7 @@ func main() { } ` formatter := &Formatter{CommentMode: CommentsFollow} - formattedResult, err := formatter.Format([]byte(inputSource)) + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") if err != nil { t.Fatalf("Format error: %v", err) @@ -438,7 +438,7 @@ func main() { formatter := &Formatter{CommentMode: CommentsFollow} for b.Loop() { - _, _ = formatter.Format(inputSource) + _, _ = formatter.Format(inputSource, "test.go") } } @@ -463,6 +463,6 @@ func BenchmarkFormatLarge(b *testing.B) { formatter := &Formatter{CommentMode: CommentsFollow} for b.Loop() { - _, _ = formatter.Format(inputSource) + _, _ = formatter.Format(inputSource, "test.go") } } diff --git a/main.go b/main.go index fd76849..649752b 100644 --- a/main.go +++ b/main.go @@ -96,16 +96,24 @@ func parseCommentMode(commentModeString string) (CommentMode, error) { } } +var supportedFileExtensions = map[string]bool{ + ".go": true, + ".js": true, + ".ts": true, + ".jsx": true, + ".tsx": true, +} + func processDirectory(formatter *Formatter, directoryPath string, exitCode *int) error { - var goFilePaths []string + var sourceFilePaths []string err := filepath.Walk(directoryPath, func(currentPath string, fileInfo os.FileInfo, err error) error { if err != nil { return err } - if !fileInfo.IsDir() && strings.HasSuffix(currentPath, ".go") { - goFilePaths = append(goFilePaths, currentPath) + if !fileInfo.IsDir() && supportedFileExtensions[filepath.Ext(currentPath)] { + sourceFilePaths = append(sourceFilePaths, currentPath) } return nil @@ -120,7 +128,7 @@ func processDirectory(formatter *Formatter, directoryPath string, exitCode *int) semaphore := make(chan struct{}, runtime.NumCPU()) - for _, filePath := range goFilePaths { + for _, filePath := range sourceFilePaths { waitGroup.Add(1) go func(currentFilePath string) { @@ -171,7 +179,7 @@ func processFile(formatter *Formatter, filename string, inputReader io.Reader, o return fmt.Errorf("%s: %v", filename, err) } - formattedResult, err := formatter.Format(sourceContent) + formattedResult, err := formatter.Format(sourceContent, filename) if err != nil { return fmt.Errorf("%s: %v", filename, err) diff --git a/parity_test.go b/parity_test.go deleted file mode 100644 index af747f8..0000000 --- a/parity_test.go +++ /dev/null @@ -1,308 +0,0 @@ -package main - -import ( - "github.com/Fuwn/iku/engine" - "testing" -) - -type parityInput struct { - name string - source string -} - -var parityInputs = []parityInput{ - { - name: "extra blank lines collapsed", - source: `package main - -func main() { - x := 1 - - - y := 2 -} -`, - }, - { - name: "scoped statements", - source: `package main - -func main() { - x := 1 - if x > 0 { - y := 2 - } - z := 3 -} -`, - }, - { - name: "nested scopes", - source: `package main - -func main() { - if true { - x := 1 - if false { - y := 2 - } - z := 3 - } -} -`, - }, - { - name: "for loop", - source: `package main - -func main() { - x := 1 - for i := 0; i < 10; i++ { - y := i - } - z := 2 -} -`, - }, - { - name: "switch statement", - source: `package main - -func main() { - x := 1 - switch x { - case 1: - y := 2 - } - z := 3 -} -`, - }, - { - name: "multiple functions", - source: `package main - -func foo() { - x := 1 -} - - -func bar() { - y := 2 -} -`, - }, - { - name: "type struct before var", - source: `package main - -type Foo struct { - X int -} -var x = 1 -`, - }, - { - name: "different statement types", - source: `package main - -func main() { - x := 1 - y := 2 - var a = 3 - defer cleanup() - defer cleanup2() - go worker() - return -} -`, - }, - { - name: "consecutive ifs", - source: `package main - -func main() { - if err != nil { - return - } - if x > 0 { - y = 1 - } -} -`, - }, - { - name: "case clause with scoped statement", - source: `package main - -func main() { - switch x { - case 1: - foo() - if err != nil { - return - } - } -} -`, - }, - { - name: "defer inline func", - source: `package main - -func main() { - defer func() { _ = file.Close() }() - fileInfo, err := file.Stat() -} -`, - }, - { - name: "case clause assignments only", - source: `package main - -func main() { - switch x { - case "user": - roleStyle = UserStyle - contentStyle = ContentStyle - prefix = "You" - case "assistant": - roleStyle = AssistantStyle - } -} -`, - }, - { - name: "raw string literal", - source: "package main\n\nvar x = `\nline 1\n\nline 2\n`\nvar y = 1\n", - }, - { - name: "mixed top-level declarations", - source: `package main - -import "fmt" - -const x = 1 - -var y = 2 - -type Z struct{} - -func main() { - fmt.Println(x, y) -} -`, - }, - { - name: "empty function body", - source: `package main - -func main() { -} -`, - }, - { - name: "comment before scoped statement", - source: `package main - -func main() { - x := 1 - // this is a comment - if x > 0 { - y := 2 - } -} -`, - }, - { - name: "multiple blank lines between functions", - source: `package main - -func a() {} - - - -func b() {} - - - - -func c() {} -`, - }, - { - name: "select statement", - source: `package main - -func main() { - x := 1 - select { - case <-ch: - y := 2 - } - z := 3 -} -`, - }, - { - name: "range loop", - source: `package main - -func main() { - items := []int{1, 2, 3} - for _, item := range items { - _ = item - } - done := true -} -`, - }, - { - name: "interface declaration", - source: `package main - -type Reader interface { - Read(p []byte) (n int, err error) -} -var x = 1 -`, - }, -} - -func TestEngineParityWithFormatter(t *testing.T) { - for _, commentMode := range []CommentMode{CommentsFollow, CommentsPrecede, CommentsStandalone} { - for _, input := range parityInputs { - name := input.name - - switch commentMode { - case CommentsPrecede: - name += "/precede" - case CommentsStandalone: - name += "/standalone" - } - - t.Run(name, func(t *testing.T) { - formatter := &Formatter{CommentMode: commentMode} - oldResult, err := formatter.Format([]byte(input.source)) - - if err != nil { - t.Fatalf("old formatter error: %v", err) - } - - adapter := &GoAdapter{} - _, events, err := adapter.Analyze([]byte(input.source)) - - if err != nil { - t.Fatalf("adapter error: %v", err) - } - - formattingEngine := &engine.Engine{CommentMode: MapCommentMode(commentMode)} - newResult := formattingEngine.FormatToString(events) - - if string(oldResult) != newResult { - t.Errorf("parity mismatch\nold:\n%s\nnew:\n%s", oldResult, newResult) - } - }) - } - } -} -- cgit v1.2.3