馃殌 Grammar-Aware Code Formatter: Structure through separation (supports Go, JavaScript, TypeScript, JSX, and TSX)
go
formatter
code-formatter
javascript
typescript
jsx
tsx
1package engine
2
3import (
4 "strings"
5 "testing"
6)
7
8func formatResult(formattingEngine *Engine, events []LineEvent) string {
9 result := formattingEngine.FormatToString(events)
10
11 return strings.TrimSuffix(result, "\n")
12}
13
14func TestEngineCollapsesBlanks(t *testing.T) {
15 events := []LineEvent{
16 {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsStartLine: true},
17 {Content: "", TrimmedContent: "", IsBlank: true},
18 {Content: "", TrimmedContent: "", IsBlank: true},
19 {Content: "\ty := 2", TrimmedContent: "y := 2", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsStartLine: true},
20 }
21 formattingEngine := &Engine{CommentMode: CommentsFollow}
22 result := formatResult(formattingEngine, events)
23
24 if result != "\tx := 1\n\ty := 2" {
25 t.Errorf("expected blanks collapsed, got:\n%s", result)
26 }
27}
28
29func TestEngineScopeBoundary(t *testing.T) {
30 events := []LineEvent{
31 {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
32 {Content: "\tif x > 0 {", TrimmedContent: "if x > 0 {", HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
33 {Content: "\t\ty := 2", TrimmedContent: "y := 2", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
34 {Content: "\t}", TrimmedContent: "}", IsClosingBrace: true, HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true},
35 {Content: "\tz := 3", TrimmedContent: "z := 3", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
36 }
37 formattingEngine := &Engine{CommentMode: CommentsFollow}
38 result := formatResult(formattingEngine, events)
39 expected := "\tx := 1\n\n\tif x > 0 {\n\t\ty := 2\n\t}\n\n\tz := 3"
40
41 if result != expected {
42 t.Errorf("expected scope boundaries, got:\n%s\nwant:\n%s", result, expected)
43 }
44}
45
46func TestEngineStatementTypeTransition(t *testing.T) {
47 events := []LineEvent{
48 {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
49 {Content: "\tvar a = 3", TrimmedContent: "var a = 3", HasASTInfo: true, StatementType: "var"},
50 }
51 formattingEngine := &Engine{CommentMode: CommentsFollow}
52 result := formatResult(formattingEngine, events)
53 expected := "\tx := 1\n\n\tvar a = 3"
54
55 if result != expected {
56 t.Errorf("expected blank between different types, got:\n%s\nwant:\n%s", result, expected)
57 }
58}
59
60func TestEngineSuppressAfterOpenBrace(t *testing.T) {
61 events := []LineEvent{
62 {Content: "func main() {", TrimmedContent: "func main() {", HasASTInfo: true, StatementType: "func", IsScoped: true, IsTopLevel: true, IsStartLine: true, IsOpeningBrace: true},
63 {Content: "\tif true {", TrimmedContent: "if true {", HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
64 {Content: "\t\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
65 {Content: "\t}", TrimmedContent: "}", IsClosingBrace: true},
66 }
67 formattingEngine := &Engine{CommentMode: CommentsFollow}
68 result := formatResult(formattingEngine, events)
69 expected := "func main() {\n\tif true {\n\t\tx := 1\n\t}"
70
71 if result != expected {
72 t.Errorf("should not insert blank after open brace, got:\n%s\nwant:\n%s", result, expected)
73 }
74}
75
76func TestEngineSuppressBeforeCloseBrace(t *testing.T) {
77 events := []LineEvent{
78 {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsScoped: false},
79 {Content: "}", TrimmedContent: "}", IsClosingBrace: true},
80 }
81 formattingEngine := &Engine{CommentMode: CommentsFollow}
82 result := formatResult(formattingEngine, events)
83 expected := "\tx := 1\n}"
84
85 if result != expected {
86 t.Errorf("should not insert blank before close brace, got:\n%s\nwant:\n%s", result, expected)
87 }
88}
89
90func TestEngineSuppressCaseLabel(t *testing.T) {
91 events := []LineEvent{
92 {Content: "\tcase 1:", TrimmedContent: "case 1:", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsCaseLabel: true, IsOpeningBrace: false},
93 {Content: "\t\tfoo()", TrimmedContent: "foo()", HasASTInfo: true, StatementType: "*ast.ExprStmt"},
94 {Content: "\tcase 2:", TrimmedContent: "case 2:", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsCaseLabel: true},
95 }
96 formattingEngine := &Engine{CommentMode: CommentsFollow}
97 result := formatResult(formattingEngine, events)
98 expected := "\tcase 1:\n\t\tfoo()\n\tcase 2:"
99
100 if result != expected {
101 t.Errorf("should not insert blank before case label, got:\n%s\nwant:\n%s", result, expected)
102 }
103}
104
105func TestEngineRawStringPassthrough(t *testing.T) {
106 events := []LineEvent{
107 {Content: "\tx := `", TrimmedContent: "x := `", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
108 {Content: "raw line 1", TrimmedContent: "raw line 1", InRawString: true},
109 {Content: "", TrimmedContent: "", InRawString: true},
110 {Content: "raw line 2`", TrimmedContent: "raw line 2`", InRawString: true},
111 {Content: "\ty := 1", TrimmedContent: "y := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
112 }
113 formattingEngine := &Engine{CommentMode: CommentsFollow}
114 result := formatResult(formattingEngine, events)
115 expected := "\tx := `\nraw line 1\n\nraw line 2`\n\ty := 1"
116
117 if result != expected {
118 t.Errorf("raw strings should pass through unchanged, got:\n%s\nwant:\n%s", result, expected)
119 }
120}
121
122func TestEngineTopLevelDifferentTypes(t *testing.T) {
123 events := []LineEvent{
124 {Content: "type Foo struct {", TrimmedContent: "type Foo struct {", HasASTInfo: true, StatementType: "type", IsTopLevel: true, IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
125 {Content: "\tX int", TrimmedContent: "X int"},
126 {Content: "}", TrimmedContent: "}", IsClosingBrace: true, HasASTInfo: true, StatementType: "type", IsTopLevel: true, IsScoped: true},
127 {Content: "var x = 1", TrimmedContent: "var x = 1", HasASTInfo: true, StatementType: "var", IsTopLevel: true, IsStartLine: true},
128 }
129 formattingEngine := &Engine{CommentMode: CommentsFollow}
130 result := formatResult(formattingEngine, events)
131 expected := "type Foo struct {\n\tX int\n}\n\nvar x = 1"
132
133 if result != expected {
134 t.Errorf("expected blank between different top-level types, got:\n%s\nwant:\n%s", result, expected)
135 }
136}
137
138func TestEngineCommentLookAhead(t *testing.T) {
139 events := []LineEvent{
140 {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
141 {Content: "\t// comment about if", TrimmedContent: "// comment about if", IsCommentOnly: true},
142 {Content: "\tif true {", TrimmedContent: "if true {", HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
143 {Content: "\t\ty := 2", TrimmedContent: "y := 2", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
144 {Content: "\t}", TrimmedContent: "}", IsClosingBrace: true},
145 }
146 formattingEngine := &Engine{CommentMode: CommentsFollow}
147 result := formatResult(formattingEngine, events)
148 expected := "\tx := 1\n\n\t// comment about if\n\tif true {\n\t\ty := 2\n\t}"
149
150 if result != expected {
151 t.Errorf("comment should trigger look-ahead blank, got:\n%s\nwant:\n%s", result, expected)
152 }
153}
154
155func TestEnginePackageDeclaration(t *testing.T) {
156 events := []LineEvent{
157 {Content: "package main", TrimmedContent: "package main", IsPackageDecl: true},
158 {Content: "", TrimmedContent: "", IsBlank: true},
159 {Content: "func main() {", TrimmedContent: "func main() {", HasASTInfo: true, StatementType: "func", IsTopLevel: true, IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
160 {Content: "}", TrimmedContent: "}", IsClosingBrace: true, HasASTInfo: true, StatementType: "func", IsTopLevel: true, IsScoped: true},
161 }
162 formattingEngine := &Engine{CommentMode: CommentsFollow}
163 result := formatResult(formattingEngine, events)
164 expected := "package main\n\nfunc main() {\n}"
165
166 if result != expected {
167 t.Errorf("package should separate from func, got:\n%s\nwant:\n%s", result, expected)
168 }
169}
170
171func TestEngineFormatToString(t *testing.T) {
172 events := []LineEvent{
173 {Content: "package main", TrimmedContent: "package main", IsPackageDecl: true},
174 }
175 formattingEngine := &Engine{CommentMode: CommentsFollow}
176 result := formattingEngine.FormatToString(events)
177
178 if result != "package main\n" {
179 t.Errorf("FormatToString should end with newline, got: %q", result)
180 }
181}
182
183func TestEngineFindNextNonComment(t *testing.T) {
184 events := []LineEvent{
185 {Content: "x", TrimmedContent: "x"},
186 {Content: "", TrimmedContent: "", IsBlank: true},
187 {Content: "// comment", TrimmedContent: "// comment", IsCommentOnly: true},
188 {Content: "y", TrimmedContent: "y"},
189 }
190 formattingEngine := &Engine{}
191 index := formattingEngine.findNextNonComment(events, 1)
192
193 if index != 3 {
194 t.Errorf("expected index 3, got %d", index)
195 }
196
197 index = formattingEngine.findNextNonComment(events, 4)
198
199 if index != -1 {
200 t.Errorf("expected -1 when past end, got %d", index)
201 }
202}