๐Ÿš€ Grammar-Aware Code Formatter: Structure through separation (supports Go, JavaScript, TypeScript, JSX, and TSX)
go formatter code-formatter javascript typescript jsx tsx
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(formatter): Dispatch adapter by file extension for multi-language support

Fuwn 9f39211f e240a346

+42 -331
+15 -4
formatter.go
··· 1 1 package main 2 2 3 - import "github.com/Fuwn/iku/engine" 3 + import ( 4 + "github.com/Fuwn/iku/engine" 5 + "path/filepath" 6 + ) 4 7 5 8 type CommentMode int 6 9 ··· 21 24 isStartLine bool 22 25 } 23 26 24 - func (f *Formatter) Format(source []byte) ([]byte, error) { 25 - adapter := &GoAdapter{} 26 - _, events, err := adapter.Analyze(source) 27 + func (f *Formatter) Format(source []byte, filename string) ([]byte, error) { 28 + _, events, err := analyzeSource(source, filename) 27 29 28 30 if err != nil { 29 31 return nil, err ··· 33 35 34 36 return []byte(formattingEngine.FormatToString(events)), nil 35 37 } 38 + 39 + func analyzeSource(source []byte, filename string) ([]byte, []engine.LineEvent, error) { 40 + switch filepath.Ext(filename) { 41 + case ".js", ".ts", ".jsx", ".tsx": 42 + return (&EcmaScriptAdapter{}).Analyze(source) 43 + default: 44 + return (&GoAdapter{}).Analyze(source) 45 + } 46 + }
+14 -14
formatter_test.go
··· 23 23 } 24 24 ` 25 25 formatter := &Formatter{CommentMode: CommentsFollow} 26 - formattedResult, err := formatter.Format([]byte(inputSource)) 26 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 27 27 28 28 if err != nil { 29 29 t.Fatalf("Format error: %v", err) ··· 58 58 } 59 59 ` 60 60 formatter := &Formatter{CommentMode: CommentsFollow} 61 - formattedResult, err := formatter.Format([]byte(inputSource)) 61 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 62 62 63 63 if err != nil { 64 64 t.Fatalf("Format error: %v", err) ··· 97 97 } 98 98 ` 99 99 formatter := &Formatter{CommentMode: CommentsFollow} 100 - formattedResult, err := formatter.Format([]byte(inputSource)) 100 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 101 101 102 102 if err != nil { 103 103 t.Fatalf("Format error: %v", err) ··· 132 132 } 133 133 ` 134 134 formatter := &Formatter{CommentMode: CommentsFollow} 135 - formattedResult, err := formatter.Format([]byte(inputSource)) 135 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 136 136 137 137 if err != nil { 138 138 t.Fatalf("Format error: %v", err) ··· 169 169 } 170 170 ` 171 171 formatter := &Formatter{CommentMode: CommentsFollow} 172 - formattedResult, err := formatter.Format([]byte(inputSource)) 172 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 173 173 174 174 if err != nil { 175 175 t.Fatalf("Format error: %v", err) ··· 203 203 } 204 204 ` 205 205 formatter := &Formatter{CommentMode: CommentsFollow} 206 - formattedResult, err := formatter.Format([]byte(inputSource)) 206 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 207 207 208 208 if err != nil { 209 209 t.Fatalf("Format error: %v", err) ··· 231 231 var x = 1 232 232 ` 233 233 formatter := &Formatter{CommentMode: CommentsFollow} 234 - formattedResult, err := formatter.Format([]byte(inputSource)) 234 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 235 235 236 236 if err != nil { 237 237 t.Fatalf("Format error: %v", err) ··· 272 272 } 273 273 ` 274 274 formatter := &Formatter{CommentMode: CommentsFollow} 275 - formattedResult, err := formatter.Format([]byte(inputSource)) 275 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 276 276 277 277 if err != nil { 278 278 t.Fatalf("Format error: %v", err) ··· 308 308 } 309 309 ` 310 310 formatter := &Formatter{CommentMode: CommentsFollow} 311 - formattedResult, err := formatter.Format([]byte(inputSource)) 311 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 312 312 313 313 if err != nil { 314 314 t.Fatalf("Format error: %v", err) ··· 346 346 } 347 347 ` 348 348 formatter := &Formatter{CommentMode: CommentsFollow} 349 - formattedResult, err := formatter.Format([]byte(inputSource)) 349 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 350 350 351 351 if err != nil { 352 352 t.Fatalf("Format error: %v", err) ··· 374 374 } 375 375 ` 376 376 formatter := &Formatter{CommentMode: CommentsFollow} 377 - formattedResult, err := formatter.Format([]byte(inputSource)) 377 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 378 378 379 379 if err != nil { 380 380 t.Fatalf("Format error: %v", err) ··· 413 413 } 414 414 ` 415 415 formatter := &Formatter{CommentMode: CommentsFollow} 416 - formattedResult, err := formatter.Format([]byte(inputSource)) 416 + formattedResult, err := formatter.Format([]byte(inputSource), "test.go") 417 417 418 418 if err != nil { 419 419 t.Fatalf("Format error: %v", err) ··· 438 438 formatter := &Formatter{CommentMode: CommentsFollow} 439 439 440 440 for b.Loop() { 441 - _, _ = formatter.Format(inputSource) 441 + _, _ = formatter.Format(inputSource, "test.go") 442 442 } 443 443 } 444 444 ··· 463 463 formatter := &Formatter{CommentMode: CommentsFollow} 464 464 465 465 for b.Loop() { 466 - _, _ = formatter.Format(inputSource) 466 + _, _ = formatter.Format(inputSource, "test.go") 467 467 } 468 468 }
+13 -5
main.go
··· 96 96 } 97 97 } 98 98 99 + var supportedFileExtensions = map[string]bool{ 100 + ".go": true, 101 + ".js": true, 102 + ".ts": true, 103 + ".jsx": true, 104 + ".tsx": true, 105 + } 106 + 99 107 func processDirectory(formatter *Formatter, directoryPath string, exitCode *int) error { 100 - var goFilePaths []string 108 + var sourceFilePaths []string 101 109 102 110 err := filepath.Walk(directoryPath, func(currentPath string, fileInfo os.FileInfo, err error) error { 103 111 if err != nil { 104 112 return err 105 113 } 106 114 107 - if !fileInfo.IsDir() && strings.HasSuffix(currentPath, ".go") { 108 - goFilePaths = append(goFilePaths, currentPath) 115 + if !fileInfo.IsDir() && supportedFileExtensions[filepath.Ext(currentPath)] { 116 + sourceFilePaths = append(sourceFilePaths, currentPath) 109 117 } 110 118 111 119 return nil ··· 120 128 121 129 semaphore := make(chan struct{}, runtime.NumCPU()) 122 130 123 - for _, filePath := range goFilePaths { 131 + for _, filePath := range sourceFilePaths { 124 132 waitGroup.Add(1) 125 133 126 134 go func(currentFilePath string) { ··· 171 179 return fmt.Errorf("%s: %v", filename, err) 172 180 } 173 181 174 - formattedResult, err := formatter.Format(sourceContent) 182 + formattedResult, err := formatter.Format(sourceContent, filename) 175 183 176 184 if err != nil { 177 185 return fmt.Errorf("%s: %v", filename, err)
-308
parity_test.go
··· 1 - package main 2 - 3 - import ( 4 - "github.com/Fuwn/iku/engine" 5 - "testing" 6 - ) 7 - 8 - type parityInput struct { 9 - name string 10 - source string 11 - } 12 - 13 - var parityInputs = []parityInput{ 14 - { 15 - name: "extra blank lines collapsed", 16 - source: `package main 17 - 18 - func main() { 19 - x := 1 20 - 21 - 22 - y := 2 23 - } 24 - `, 25 - }, 26 - { 27 - name: "scoped statements", 28 - source: `package main 29 - 30 - func main() { 31 - x := 1 32 - if x > 0 { 33 - y := 2 34 - } 35 - z := 3 36 - } 37 - `, 38 - }, 39 - { 40 - name: "nested scopes", 41 - source: `package main 42 - 43 - func main() { 44 - if true { 45 - x := 1 46 - if false { 47 - y := 2 48 - } 49 - z := 3 50 - } 51 - } 52 - `, 53 - }, 54 - { 55 - name: "for loop", 56 - source: `package main 57 - 58 - func main() { 59 - x := 1 60 - for i := 0; i < 10; i++ { 61 - y := i 62 - } 63 - z := 2 64 - } 65 - `, 66 - }, 67 - { 68 - name: "switch statement", 69 - source: `package main 70 - 71 - func main() { 72 - x := 1 73 - switch x { 74 - case 1: 75 - y := 2 76 - } 77 - z := 3 78 - } 79 - `, 80 - }, 81 - { 82 - name: "multiple functions", 83 - source: `package main 84 - 85 - func foo() { 86 - x := 1 87 - } 88 - 89 - 90 - func bar() { 91 - y := 2 92 - } 93 - `, 94 - }, 95 - { 96 - name: "type struct before var", 97 - source: `package main 98 - 99 - type Foo struct { 100 - X int 101 - } 102 - var x = 1 103 - `, 104 - }, 105 - { 106 - name: "different statement types", 107 - source: `package main 108 - 109 - func main() { 110 - x := 1 111 - y := 2 112 - var a = 3 113 - defer cleanup() 114 - defer cleanup2() 115 - go worker() 116 - return 117 - } 118 - `, 119 - }, 120 - { 121 - name: "consecutive ifs", 122 - source: `package main 123 - 124 - func main() { 125 - if err != nil { 126 - return 127 - } 128 - if x > 0 { 129 - y = 1 130 - } 131 - } 132 - `, 133 - }, 134 - { 135 - name: "case clause with scoped statement", 136 - source: `package main 137 - 138 - func main() { 139 - switch x { 140 - case 1: 141 - foo() 142 - if err != nil { 143 - return 144 - } 145 - } 146 - } 147 - `, 148 - }, 149 - { 150 - name: "defer inline func", 151 - source: `package main 152 - 153 - func main() { 154 - defer func() { _ = file.Close() }() 155 - fileInfo, err := file.Stat() 156 - } 157 - `, 158 - }, 159 - { 160 - name: "case clause assignments only", 161 - source: `package main 162 - 163 - func main() { 164 - switch x { 165 - case "user": 166 - roleStyle = UserStyle 167 - contentStyle = ContentStyle 168 - prefix = "You" 169 - case "assistant": 170 - roleStyle = AssistantStyle 171 - } 172 - } 173 - `, 174 - }, 175 - { 176 - name: "raw string literal", 177 - source: "package main\n\nvar x = `\nline 1\n\nline 2\n`\nvar y = 1\n", 178 - }, 179 - { 180 - name: "mixed top-level declarations", 181 - source: `package main 182 - 183 - import "fmt" 184 - 185 - const x = 1 186 - 187 - var y = 2 188 - 189 - type Z struct{} 190 - 191 - func main() { 192 - fmt.Println(x, y) 193 - } 194 - `, 195 - }, 196 - { 197 - name: "empty function body", 198 - source: `package main 199 - 200 - func main() { 201 - } 202 - `, 203 - }, 204 - { 205 - name: "comment before scoped statement", 206 - source: `package main 207 - 208 - func main() { 209 - x := 1 210 - // this is a comment 211 - if x > 0 { 212 - y := 2 213 - } 214 - } 215 - `, 216 - }, 217 - { 218 - name: "multiple blank lines between functions", 219 - source: `package main 220 - 221 - func a() {} 222 - 223 - 224 - 225 - func b() {} 226 - 227 - 228 - 229 - 230 - func c() {} 231 - `, 232 - }, 233 - { 234 - name: "select statement", 235 - source: `package main 236 - 237 - func main() { 238 - x := 1 239 - select { 240 - case <-ch: 241 - y := 2 242 - } 243 - z := 3 244 - } 245 - `, 246 - }, 247 - { 248 - name: "range loop", 249 - source: `package main 250 - 251 - func main() { 252 - items := []int{1, 2, 3} 253 - for _, item := range items { 254 - _ = item 255 - } 256 - done := true 257 - } 258 - `, 259 - }, 260 - { 261 - name: "interface declaration", 262 - source: `package main 263 - 264 - type Reader interface { 265 - Read(p []byte) (n int, err error) 266 - } 267 - var x = 1 268 - `, 269 - }, 270 - } 271 - 272 - func TestEngineParityWithFormatter(t *testing.T) { 273 - for _, commentMode := range []CommentMode{CommentsFollow, CommentsPrecede, CommentsStandalone} { 274 - for _, input := range parityInputs { 275 - name := input.name 276 - 277 - switch commentMode { 278 - case CommentsPrecede: 279 - name += "/precede" 280 - case CommentsStandalone: 281 - name += "/standalone" 282 - } 283 - 284 - t.Run(name, func(t *testing.T) { 285 - formatter := &Formatter{CommentMode: commentMode} 286 - oldResult, err := formatter.Format([]byte(input.source)) 287 - 288 - if err != nil { 289 - t.Fatalf("old formatter error: %v", err) 290 - } 291 - 292 - adapter := &GoAdapter{} 293 - _, events, err := adapter.Analyze([]byte(input.source)) 294 - 295 - if err != nil { 296 - t.Fatalf("adapter error: %v", err) 297 - } 298 - 299 - formattingEngine := &engine.Engine{CommentMode: MapCommentMode(commentMode)} 300 - newResult := formattingEngine.FormatToString(events) 301 - 302 - if string(oldResult) != newResult { 303 - t.Errorf("parity mismatch\nold:\n%s\nnew:\n%s", oldResult, newResult) 304 - } 305 - }) 306 - } 307 - } 308 - }