๐Ÿš€ 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(engine): Add language-agnostic formatting engine with Go adapter

Fuwn 30fb266c d0eb809c

+757
+89
adapter_go.go
··· 1 + package main 2 + 3 + import ( 4 + "github.com/Fuwn/iku/engine" 5 + "go/format" 6 + "go/parser" 7 + "go/token" 8 + "strings" 9 + ) 10 + 11 + type GoAdapter struct{} 12 + 13 + func (a *GoAdapter) Analyze(source []byte) ([]byte, []engine.LineEvent, error) { 14 + formattedSource, err := format.Source(source) 15 + 16 + if err != nil { 17 + return nil, nil, err 18 + } 19 + 20 + tokenFileSet := token.NewFileSet() 21 + parsedFile, err := parser.ParseFile(tokenFileSet, "", formattedSource, parser.ParseComments) 22 + 23 + if err != nil { 24 + return nil, nil, err 25 + } 26 + 27 + formatter := &Formatter{} 28 + lineInformationMap := formatter.buildLineInfo(tokenFileSet, parsedFile) 29 + sourceLines := strings.Split(string(formattedSource), "\n") 30 + events := make([]engine.LineEvent, len(sourceLines)) 31 + insideRawString := false 32 + 33 + for lineIndex, currentLine := range sourceLines { 34 + backtickCount := countRawStringDelimiters(currentLine) 35 + wasInsideRawString := insideRawString 36 + 37 + if backtickCount%2 == 1 { 38 + insideRawString = !insideRawString 39 + } 40 + 41 + event := engine.NewLineEvent(currentLine) 42 + 43 + if wasInsideRawString { 44 + event.InRawString = true 45 + events[lineIndex] = event 46 + 47 + continue 48 + } 49 + 50 + if event.IsBlank { 51 + events[lineIndex] = event 52 + 53 + continue 54 + } 55 + 56 + lineNumber := lineIndex + 1 57 + currentInformation := lineInformationMap[lineNumber] 58 + 59 + if currentInformation != nil { 60 + event.HasASTInfo = true 61 + event.StatementType = currentInformation.statementType 62 + event.IsTopLevel = currentInformation.isTopLevel 63 + event.IsScoped = currentInformation.isScoped 64 + event.IsStartLine = currentInformation.isStartLine 65 + } 66 + 67 + event.IsClosingBrace = isClosingBrace(currentLine) 68 + event.IsOpeningBrace = isOpeningBrace(currentLine) 69 + event.IsCaseLabel = isCaseLabel(currentLine) 70 + event.IsCommentOnly = isCommentOnly(currentLine) 71 + event.IsPackageDecl = isPackageLine(event.TrimmedContent) 72 + events[lineIndex] = event 73 + } 74 + 75 + return formattedSource, events, nil 76 + } 77 + 78 + func MapCommentMode(mode CommentMode) engine.CommentMode { 79 + switch mode { 80 + case CommentsFollow: 81 + return engine.CommentsFollow 82 + case CommentsPrecede: 83 + return engine.CommentsPrecede 84 + case CommentsStandalone: 85 + return engine.CommentsStandalone 86 + default: 87 + return engine.CommentsFollow 88 + } 89 + }
+130
engine/engine.go
··· 1 + package engine 2 + 3 + import "strings" 4 + 5 + type CommentMode int 6 + 7 + const ( 8 + CommentsFollow CommentMode = iota 9 + CommentsPrecede 10 + CommentsStandalone 11 + ) 12 + 13 + type Engine struct { 14 + CommentMode CommentMode 15 + } 16 + 17 + func (e *Engine) Format(events []LineEvent) []string { 18 + resultLines := make([]string, 0, len(events)) 19 + previousWasOpenBrace := false 20 + previousStatementType := "" 21 + previousWasComment := false 22 + previousWasTopLevel := false 23 + previousWasScoped := false 24 + 25 + for eventIndex, event := range events { 26 + if event.InRawString { 27 + resultLines = append(resultLines, event.Content) 28 + 29 + continue 30 + } 31 + 32 + if event.IsBlank { 33 + continue 34 + } 35 + 36 + currentStatementType := event.StatementType 37 + 38 + if event.IsPackageDecl { 39 + currentStatementType = "package" 40 + } 41 + 42 + needsBlankLine := false 43 + currentIsTopLevel := event.HasASTInfo && event.IsTopLevel 44 + currentIsScoped := event.HasASTInfo && event.IsScoped 45 + 46 + if len(resultLines) > 0 && !previousWasOpenBrace && !event.IsClosingBrace && !event.IsCaseLabel { 47 + if currentIsTopLevel && previousWasTopLevel && currentStatementType != previousStatementType { 48 + if !(e.CommentMode == CommentsFollow && previousWasComment) { 49 + needsBlankLine = true 50 + } 51 + } else if event.HasASTInfo && (currentIsScoped || previousWasScoped) { 52 + if !(e.CommentMode == CommentsFollow && previousWasComment) { 53 + needsBlankLine = true 54 + } 55 + } else if currentStatementType != "" && previousStatementType != "" && currentStatementType != previousStatementType { 56 + if !(e.CommentMode == CommentsFollow && previousWasComment) { 57 + needsBlankLine = true 58 + } 59 + } 60 + 61 + if e.CommentMode == CommentsFollow && event.IsCommentOnly && !previousWasComment { 62 + nextIndex := e.findNextNonComment(events, eventIndex+1) 63 + 64 + if nextIndex >= 0 { 65 + next := events[nextIndex] 66 + 67 + if next.HasASTInfo { 68 + nextIsTopLevel := next.IsTopLevel 69 + nextIsScoped := next.IsScoped 70 + 71 + if nextIsTopLevel && previousWasTopLevel && next.StatementType != previousStatementType { 72 + needsBlankLine = true 73 + } else if nextIsScoped || previousWasScoped { 74 + needsBlankLine = true 75 + } else if next.StatementType != "" && previousStatementType != "" && next.StatementType != previousStatementType { 76 + needsBlankLine = true 77 + } 78 + } 79 + } 80 + } 81 + } 82 + 83 + if needsBlankLine { 84 + resultLines = append(resultLines, "") 85 + } 86 + 87 + resultLines = append(resultLines, event.Content) 88 + previousWasOpenBrace = event.IsOpeningBrace || event.IsCaseLabel 89 + previousWasComment = event.IsCommentOnly 90 + 91 + if event.HasASTInfo { 92 + previousStatementType = event.StatementType 93 + previousWasTopLevel = event.IsTopLevel 94 + previousWasScoped = event.IsScoped 95 + } else if currentStatementType != "" { 96 + previousStatementType = currentStatementType 97 + previousWasTopLevel = false 98 + previousWasScoped = false 99 + } 100 + } 101 + 102 + return resultLines 103 + } 104 + 105 + func (e *Engine) FormatToString(events []LineEvent) string { 106 + lines := e.Format(events) 107 + output := strings.Join(lines, "\n") 108 + 109 + if !strings.HasSuffix(output, "\n") { 110 + output += "\n" 111 + } 112 + 113 + return output 114 + } 115 + 116 + func (e *Engine) findNextNonComment(events []LineEvent, startIndex int) int { 117 + for eventIndex := startIndex; eventIndex < len(events); eventIndex++ { 118 + if events[eventIndex].IsBlank { 119 + continue 120 + } 121 + 122 + if events[eventIndex].IsCommentOnly { 123 + continue 124 + } 125 + 126 + return eventIndex 127 + } 128 + 129 + return -1 130 + }
+200
engine/engine_test.go
··· 1 + package engine 2 + 3 + import ( 4 + "strings" 5 + "testing" 6 + ) 7 + 8 + func formatResult(formattingEngine *Engine, events []LineEvent) string { 9 + return strings.Join(formattingEngine.Format(events), "\n") 10 + } 11 + 12 + func TestEngineCollapsesBlanks(t *testing.T) { 13 + events := []LineEvent{ 14 + {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsStartLine: true}, 15 + {Content: "", TrimmedContent: "", IsBlank: true}, 16 + {Content: "", TrimmedContent: "", IsBlank: true}, 17 + {Content: "\ty := 2", TrimmedContent: "y := 2", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsStartLine: true}, 18 + } 19 + formattingEngine := &Engine{CommentMode: CommentsFollow} 20 + result := formatResult(formattingEngine, events) 21 + 22 + if result != "\tx := 1\n\ty := 2" { 23 + t.Errorf("expected blanks collapsed, got:\n%s", result) 24 + } 25 + } 26 + 27 + func TestEngineScopeBoundary(t *testing.T) { 28 + events := []LineEvent{ 29 + {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"}, 30 + {Content: "\tif x > 0 {", TrimmedContent: "if x > 0 {", HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true, IsStartLine: true, IsOpeningBrace: true}, 31 + {Content: "\t\ty := 2", TrimmedContent: "y := 2", HasASTInfo: true, StatementType: "*ast.AssignStmt"}, 32 + {Content: "\t}", TrimmedContent: "}", IsClosingBrace: true, HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true}, 33 + {Content: "\tz := 3", TrimmedContent: "z := 3", HasASTInfo: true, StatementType: "*ast.AssignStmt"}, 34 + } 35 + formattingEngine := &Engine{CommentMode: CommentsFollow} 36 + result := formatResult(formattingEngine, events) 37 + expected := "\tx := 1\n\n\tif x > 0 {\n\t\ty := 2\n\t}\n\n\tz := 3" 38 + 39 + if result != expected { 40 + t.Errorf("expected scope boundaries, got:\n%s\nwant:\n%s", result, expected) 41 + } 42 + } 43 + 44 + func TestEngineStatementTypeTransition(t *testing.T) { 45 + events := []LineEvent{ 46 + {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"}, 47 + {Content: "\tvar a = 3", TrimmedContent: "var a = 3", HasASTInfo: true, StatementType: "var"}, 48 + } 49 + formattingEngine := &Engine{CommentMode: CommentsFollow} 50 + result := formatResult(formattingEngine, events) 51 + expected := "\tx := 1\n\n\tvar a = 3" 52 + 53 + if result != expected { 54 + t.Errorf("expected blank between different types, got:\n%s\nwant:\n%s", result, expected) 55 + } 56 + } 57 + 58 + func TestEngineSuppressAfterOpenBrace(t *testing.T) { 59 + events := []LineEvent{ 60 + {Content: "func main() {", TrimmedContent: "func main() {", HasASTInfo: true, StatementType: "func", IsScoped: true, IsTopLevel: true, IsStartLine: true, IsOpeningBrace: true}, 61 + {Content: "\tif true {", TrimmedContent: "if true {", HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true, IsStartLine: true, IsOpeningBrace: true}, 62 + {Content: "\t\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"}, 63 + {Content: "\t}", TrimmedContent: "}", IsClosingBrace: true}, 64 + } 65 + formattingEngine := &Engine{CommentMode: CommentsFollow} 66 + result := formatResult(formattingEngine, events) 67 + expected := "func main() {\n\tif true {\n\t\tx := 1\n\t}" 68 + 69 + if result != expected { 70 + t.Errorf("should not insert blank after open brace, got:\n%s\nwant:\n%s", result, expected) 71 + } 72 + } 73 + 74 + func TestEngineSuppressBeforeCloseBrace(t *testing.T) { 75 + events := []LineEvent{ 76 + {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsScoped: false}, 77 + {Content: "}", TrimmedContent: "}", IsClosingBrace: true}, 78 + } 79 + formattingEngine := &Engine{CommentMode: CommentsFollow} 80 + result := formatResult(formattingEngine, events) 81 + expected := "\tx := 1\n}" 82 + 83 + if result != expected { 84 + t.Errorf("should not insert blank before close brace, got:\n%s\nwant:\n%s", result, expected) 85 + } 86 + } 87 + 88 + func TestEngineSuppressCaseLabel(t *testing.T) { 89 + events := []LineEvent{ 90 + {Content: "\tcase 1:", TrimmedContent: "case 1:", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsCaseLabel: true, IsOpeningBrace: false}, 91 + {Content: "\t\tfoo()", TrimmedContent: "foo()", HasASTInfo: true, StatementType: "*ast.ExprStmt"}, 92 + {Content: "\tcase 2:", TrimmedContent: "case 2:", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsCaseLabel: true}, 93 + } 94 + formattingEngine := &Engine{CommentMode: CommentsFollow} 95 + result := formatResult(formattingEngine, events) 96 + expected := "\tcase 1:\n\t\tfoo()\n\tcase 2:" 97 + 98 + if result != expected { 99 + t.Errorf("should not insert blank before case label, got:\n%s\nwant:\n%s", result, expected) 100 + } 101 + } 102 + 103 + func TestEngineRawStringPassthrough(t *testing.T) { 104 + events := []LineEvent{ 105 + {Content: "\tx := `", TrimmedContent: "x := `", HasASTInfo: true, StatementType: "*ast.AssignStmt"}, 106 + {Content: "raw line 1", TrimmedContent: "raw line 1", InRawString: true}, 107 + {Content: "", TrimmedContent: "", InRawString: true}, 108 + {Content: "raw line 2`", TrimmedContent: "raw line 2`", InRawString: true}, 109 + {Content: "\ty := 1", TrimmedContent: "y := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"}, 110 + } 111 + formattingEngine := &Engine{CommentMode: CommentsFollow} 112 + result := formatResult(formattingEngine, events) 113 + expected := "\tx := `\nraw line 1\n\nraw line 2`\n\ty := 1" 114 + 115 + if result != expected { 116 + t.Errorf("raw strings should pass through unchanged, got:\n%s\nwant:\n%s", result, expected) 117 + } 118 + } 119 + 120 + func TestEngineTopLevelDifferentTypes(t *testing.T) { 121 + events := []LineEvent{ 122 + {Content: "type Foo struct {", TrimmedContent: "type Foo struct {", HasASTInfo: true, StatementType: "type", IsTopLevel: true, IsScoped: true, IsStartLine: true, IsOpeningBrace: true}, 123 + {Content: "\tX int", TrimmedContent: "X int"}, 124 + {Content: "}", TrimmedContent: "}", IsClosingBrace: true, HasASTInfo: true, StatementType: "type", IsTopLevel: true, IsScoped: true}, 125 + {Content: "var x = 1", TrimmedContent: "var x = 1", HasASTInfo: true, StatementType: "var", IsTopLevel: true, IsStartLine: true}, 126 + } 127 + formattingEngine := &Engine{CommentMode: CommentsFollow} 128 + result := formatResult(formattingEngine, events) 129 + expected := "type Foo struct {\n\tX int\n}\n\nvar x = 1" 130 + 131 + if result != expected { 132 + t.Errorf("expected blank between different top-level types, got:\n%s\nwant:\n%s", result, expected) 133 + } 134 + } 135 + 136 + func TestEngineCommentLookAhead(t *testing.T) { 137 + events := []LineEvent{ 138 + {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"}, 139 + {Content: "\t// comment about if", TrimmedContent: "// comment about if", IsCommentOnly: true}, 140 + {Content: "\tif true {", TrimmedContent: "if true {", HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true, IsStartLine: true, IsOpeningBrace: true}, 141 + {Content: "\t\ty := 2", TrimmedContent: "y := 2", HasASTInfo: true, StatementType: "*ast.AssignStmt"}, 142 + {Content: "\t}", TrimmedContent: "}", IsClosingBrace: true}, 143 + } 144 + formattingEngine := &Engine{CommentMode: CommentsFollow} 145 + result := formatResult(formattingEngine, events) 146 + expected := "\tx := 1\n\n\t// comment about if\n\tif true {\n\t\ty := 2\n\t}" 147 + 148 + if result != expected { 149 + t.Errorf("comment should trigger look-ahead blank, got:\n%s\nwant:\n%s", result, expected) 150 + } 151 + } 152 + 153 + func TestEnginePackageDeclaration(t *testing.T) { 154 + events := []LineEvent{ 155 + {Content: "package main", TrimmedContent: "package main", IsPackageDecl: true}, 156 + {Content: "", TrimmedContent: "", IsBlank: true}, 157 + {Content: "func main() {", TrimmedContent: "func main() {", HasASTInfo: true, StatementType: "func", IsTopLevel: true, IsScoped: true, IsStartLine: true, IsOpeningBrace: true}, 158 + {Content: "}", TrimmedContent: "}", IsClosingBrace: true, HasASTInfo: true, StatementType: "func", IsTopLevel: true, IsScoped: true}, 159 + } 160 + formattingEngine := &Engine{CommentMode: CommentsFollow} 161 + result := formatResult(formattingEngine, events) 162 + expected := "package main\n\nfunc main() {\n}" 163 + 164 + if result != expected { 165 + t.Errorf("package should separate from func, got:\n%s\nwant:\n%s", result, expected) 166 + } 167 + } 168 + 169 + func TestEngineFormatToString(t *testing.T) { 170 + events := []LineEvent{ 171 + {Content: "package main", TrimmedContent: "package main", IsPackageDecl: true}, 172 + } 173 + formattingEngine := &Engine{CommentMode: CommentsFollow} 174 + result := formattingEngine.FormatToString(events) 175 + 176 + if result != "package main\n" { 177 + t.Errorf("FormatToString should end with newline, got: %q", result) 178 + } 179 + } 180 + 181 + func TestEngineFindNextNonComment(t *testing.T) { 182 + events := []LineEvent{ 183 + {Content: "x", TrimmedContent: "x"}, 184 + {Content: "", TrimmedContent: "", IsBlank: true}, 185 + {Content: "// comment", TrimmedContent: "// comment", IsCommentOnly: true}, 186 + {Content: "y", TrimmedContent: "y"}, 187 + } 188 + formattingEngine := &Engine{} 189 + index := formattingEngine.findNextNonComment(events, 1) 190 + 191 + if index != 3 { 192 + t.Errorf("expected index 3, got %d", index) 193 + } 194 + 195 + index = formattingEngine.findNextNonComment(events, 4) 196 + 197 + if index != -1 { 198 + t.Errorf("expected -1 when past end, got %d", index) 199 + } 200 + }
+30
engine/event.go
··· 1 + package engine 2 + 3 + import "strings" 4 + 5 + type LineEvent struct { 6 + Content string 7 + TrimmedContent string 8 + StatementType string 9 + IsTopLevel bool 10 + IsScoped bool 11 + IsStartLine bool 12 + HasASTInfo bool 13 + IsClosingBrace bool 14 + IsOpeningBrace bool 15 + IsCaseLabel bool 16 + IsCommentOnly bool 17 + IsBlank bool 18 + InRawString bool 19 + IsPackageDecl bool 20 + } 21 + 22 + func NewLineEvent(content string) LineEvent { 23 + trimmed := strings.TrimSpace(content) 24 + 25 + return LineEvent{ 26 + Content: content, 27 + TrimmedContent: trimmed, 28 + IsBlank: trimmed == "", 29 + } 30 + }
+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 + }