1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package astutil_test
16
17 import (
18 "testing"
19
20 "cuelang.org/go/cue/ast"
21 "cuelang.org/go/cue/ast/astutil"
22 "cuelang.org/go/cue/format"
23 "cuelang.org/go/internal"
24
25 "github.com/go-quicktest/qt"
26 )
27
28 func TestSanitize(t *testing.T) {
29 testCases := []struct {
30 desc string
31 file *ast.File
32 want string
33 }{{
34 desc: "Take existing import and rename it",
35 file: func() *ast.File {
36 spec := ast.NewImport(nil, "list")
37 spec.AddComment(internal.NewComment(true, "will be renamed"))
38 return &ast.File{Decls: []ast.Decl{
39 &ast.ImportDecl{Specs: []*ast.ImportSpec{spec}},
40 &ast.EmbedDecl{
41 Expr: ast.NewStruct(
42 ast.NewIdent("list"), ast.NewCall(
43 ast.NewSel(&ast.Ident{Name: "list", Node: spec},
44 "Min")),
45 )},
46 }}
47 }(),
48 want: `import (
49 // will be renamed
50 list_1 "list"
51 )
52
53 {
54 list: list_1.Min()
55 }
56 `,
57 }, {
58 desc: "Take existing import and rename it",
59 file: func() *ast.File {
60 spec := ast.NewImport(nil, "list")
61 return &ast.File{Decls: []ast.Decl{
62 &ast.ImportDecl{Specs: []*ast.ImportSpec{spec}},
63 &ast.Field{
64 Label: ast.NewIdent("a"),
65 Value: ast.NewStruct(
66 ast.NewIdent("list"), ast.NewCall(
67 ast.NewSel(&ast.Ident{Name: "list", Node: spec}, "Min")),
68 ),
69 },
70 }}
71 }(),
72 want: `import list_1 "list"
73
74 a: {
75 list: list_1.Min()
76 }
77 `,
78 }, {
79 desc: "One import added, one removed",
80 file: &ast.File{Decls: []ast.Decl{
81 &ast.ImportDecl{Specs: []*ast.ImportSpec{
82 {Path: ast.NewString("foo")},
83 }},
84 &ast.Field{
85 Label: ast.NewIdent("a"),
86 Value: ast.NewCall(
87 ast.NewSel(&ast.Ident{
88 Name: "bar",
89 Node: &ast.ImportSpec{Path: ast.NewString("bar")},
90 }, "Min")),
91 },
92 }},
93 want: `import "bar"
94
95 a: bar.Min()
96 `,
97 }, {
98 desc: "Rename duplicate import",
99 file: func() *ast.File {
100 spec1 := ast.NewImport(nil, "bar")
101 spec2 := ast.NewImport(nil, "foo/bar")
102 spec3 := ast.NewImport(ast.NewIdent("bar"), "foo")
103 return &ast.File{Decls: []ast.Decl{
104 internal.NewComment(false, "File comment"),
105 &ast.Package{Name: ast.NewIdent("pkg")},
106 &ast.Field{
107 Label: ast.NewIdent("a"),
108 Value: ast.NewStruct(
109 ast.NewIdent("b"), ast.NewCall(
110 ast.NewSel(&ast.Ident{Name: "bar", Node: spec1}, "A")),
111 ast.NewIdent("c"), ast.NewCall(
112 ast.NewSel(&ast.Ident{Name: "bar", Node: spec2}, "A")),
113 ast.NewIdent("d"), ast.NewCall(
114 ast.NewSel(&ast.Ident{Name: "bar", Node: spec3}, "A")),
115 ),
116 },
117 }}
118 }(),
119 want: `// File comment
120
121 package pkg
122
123 import (
124 "bar"
125 bar_1 "foo/bar"
126 bar_5 "foo"
127 )
128
129 a: {
130 b: bar.A()
131 c: bar_1.A()
132 d: bar_5.A()
133 }
134 `,
135 }, {
136 desc: "Rename duplicate import, reuse and drop",
137 file: func() *ast.File {
138 spec1 := ast.NewImport(nil, "bar")
139 spec2 := ast.NewImport(nil, "foo/bar")
140 spec3 := ast.NewImport(ast.NewIdent("bar"), "foo")
141 return &ast.File{Decls: []ast.Decl{
142 &ast.ImportDecl{Specs: []*ast.ImportSpec{
143 spec3,
144 ast.NewImport(nil, "foo"),
145 }},
146 &ast.Field{
147 Label: ast.NewIdent("a"),
148 Value: ast.NewStruct(
149 ast.NewIdent("b"), ast.NewCall(
150 ast.NewSel(&ast.Ident{Name: "bar", Node: spec1}, "A")),
151 ast.NewIdent("c"), ast.NewCall(
152 ast.NewSel(&ast.Ident{Name: "bar", Node: spec2}, "A")),
153 ast.NewIdent("d"), ast.NewCall(
154 ast.NewSel(&ast.Ident{Name: "bar", Node: spec3}, "A")),
155 ),
156 },
157 }}
158 }(),
159 want: `import (
160 bar "foo"
161 bar_1 "bar"
162 bar_5 "foo/bar"
163 )
164
165 a: {
166 b: bar_1.A()
167 c: bar_5.A()
168 d: bar.A()
169 }
170 `,
171 }, {
172 desc: "Reuse different import",
173 file: &ast.File{Decls: []ast.Decl{
174 &ast.Package{Name: ast.NewIdent("pkg")},
175 &ast.ImportDecl{Specs: []*ast.ImportSpec{
176 {Path: ast.NewString("bar")},
177 }},
178 &ast.Field{
179 Label: ast.NewIdent("a"),
180 Value: ast.NewStruct(
181 ast.NewIdent("list"), ast.NewCall(
182 ast.NewSel(&ast.Ident{
183 Name: "bar",
184 Node: &ast.ImportSpec{Path: ast.NewString("bar")},
185 }, "Min")),
186 ),
187 },
188 }},
189 want: `package pkg
190
191 import "bar"
192
193 a: {
194 list: bar.Min()
195 }
196 `,
197 }, {
198 desc: "Clear reference that does not exist in scope",
199 file: &ast.File{Decls: []ast.Decl{
200 &ast.Field{
201 Label: ast.NewIdent("a"),
202 Value: ast.NewStruct(
203 ast.NewIdent("b"), &ast.Ident{
204 Name: "c",
205 Node: ast.NewString("foo"),
206 },
207 ast.NewIdent("d"), ast.NewIdent("e"),
208 ),
209 },
210 }},
211 want: `a: {
212 b: c
213 d: e
214 }
215 `,
216 }, {
217 desc: "Unshadow possible reference to other file",
218 file: &ast.File{Decls: []ast.Decl{
219 &ast.Field{
220 Label: ast.NewIdent("a"),
221 Value: ast.NewStruct(
222 ast.NewIdent("b"), &ast.Ident{
223 Name: "c",
224 Node: ast.NewString("foo"),
225 },
226 ast.NewIdent("c"), ast.NewIdent("d"),
227 ),
228 },
229 }},
230 want: `a: {
231 b: c_1
232 c: d
233 }
234
235 let c_1 = c
236 `,
237 }, {
238 desc: "Add alias to shadowed field",
239 file: func() *ast.File {
240 field := &ast.Field{
241 Label: ast.NewIdent("a"),
242 Value: ast.NewString("b"),
243 }
244 return &ast.File{Decls: []ast.Decl{
245 field,
246 &ast.Field{
247 Label: ast.NewIdent("c"),
248 Value: ast.NewStruct(
249 ast.NewIdent("a"), ast.NewStruct(),
250 ast.NewIdent("b"), &ast.Ident{
251 Name: "a",
252 Node: field.Value,
253 },
254 ast.NewIdent("c"), ast.NewIdent("d"),
255 ),
256 },
257 }}
258 }(),
259 want: `a_1=a: "b"
260 c: {
261 a: {}
262 b: a_1
263 c: d
264 }
265 `,
266 }, {
267 desc: "Add let clause to shadowed field",
268
269 file: func() *ast.File {
270 field := &ast.Field{
271 Label: ast.NewIdent("a"),
272 Value: ast.NewString("b"),
273 }
274 return &ast.File{Decls: []ast.Decl{
275 field,
276 &ast.Field{
277 Label: ast.NewIdent("c"),
278 Value: ast.NewStruct(
279 ast.NewIdent("a"), ast.NewStruct(),
280
281 ast.NewIdent("b"), &ast.Ident{
282 Name: "a",
283 Node: field.Value,
284 },
285 ast.NewIdent("c"), ast.NewIdent("d"),
286 ast.NewIdent("e"), &ast.Ident{
287 Name: "a",
288 Node: field.Value,
289 },
290 ),
291 },
292 }}
293 }(),
294 want: `a_1=a: "b"
295 c: {
296 a: {}
297 b: a_1
298 c: d
299 e: a_1
300 }
301 `,
302 }, {
303 desc: "Add let clause to shadowed field",
304
305 file: func() *ast.File {
306 fieldX := &ast.Field{
307 Label: &ast.Alias{
308 Ident: ast.NewIdent("X"),
309 Expr: ast.NewIdent("a"),
310 },
311 Value: ast.NewString("b"),
312 }
313 fieldY := &ast.Field{
314 Label: &ast.Alias{
315 Ident: ast.NewIdent("Y"),
316 Expr: ast.NewIdent("q"),
317 },
318 Value: ast.NewString("b"),
319 }
320 return &ast.File{Decls: []ast.Decl{
321 fieldX,
322 fieldY,
323 &ast.Field{
324 Label: ast.NewIdent("c"),
325 Value: ast.NewStruct(
326 ast.NewIdent("a"), ast.NewStruct(),
327 ast.NewIdent("b"), &ast.Ident{
328 Name: "X",
329 Node: fieldX,
330 },
331 ast.NewIdent("c"), ast.NewIdent("d"),
332 ast.NewIdent("e"), &ast.Ident{
333 Name: "a",
334 Node: fieldX.Value,
335 },
336 ast.NewIdent("f"), &ast.Ident{
337 Name: "Y",
338 Node: fieldY,
339 },
340 ),
341 },
342 }}
343 }(),
344 want: `
345 let X_1 = X
346 X=a: "b"
347 Y=q: "b"
348 c: {
349 a: {}
350 b: X
351 c: d
352 e: X_1
353 f: Y
354 }
355 `,
356 }, {
357 desc: "Add let clause to nested shadowed field",
358
359 file: func() *ast.File {
360 field := &ast.Field{
361 Label: ast.NewIdent("a"),
362 Value: ast.NewString("b"),
363 }
364 return &ast.File{Decls: []ast.Decl{
365 &ast.Field{
366 Label: ast.NewIdent("b"),
367 Value: ast.NewStruct(
368 field,
369 ast.NewIdent("b"), ast.NewStruct(
370 ast.NewIdent("a"), ast.NewString("bar"),
371 ast.NewIdent("b"), &ast.Ident{
372 Name: "a",
373 Node: field.Value,
374 },
375 ast.NewIdent("e"), &ast.Ident{
376 Name: "a",
377 Node: field.Value,
378 },
379 ),
380 ),
381 },
382 }}
383 }(),
384 want: `b: {
385 a_1=a: "b"
386 b: {
387 a: "bar"
388 b: a_1
389 e: a_1
390 }
391 }
392 `,
393 }, {
394 desc: "Add let clause to nested shadowed field with alias",
395
396 file: func() *ast.File {
397 field := &ast.Field{
398 Label: &ast.Alias{
399 Ident: ast.NewIdent("X"),
400 Expr: ast.NewIdent("a"),
401 },
402 Value: ast.NewString("b"),
403 }
404 return &ast.File{Decls: []ast.Decl{
405 &ast.Field{
406 Label: ast.NewIdent("b"),
407 Value: ast.NewStruct(
408 field,
409 ast.NewIdent("b"), ast.NewStruct(
410 ast.NewIdent("a"), ast.NewString("bar"),
411 ast.NewIdent("b"), &ast.Ident{
412 Name: "a",
413 Node: field.Value,
414 },
415 ast.NewIdent("e"), &ast.Ident{
416 Name: "a",
417 Node: field.Value,
418 },
419 ),
420 ),
421 },
422 }}
423 }(),
424 want: `b: {
425 let X_1 = X
426 X=a: "b"
427 b: {
428 a: "bar"
429 b: X_1
430 e: X_1
431 }
432 }
433 `,
434 }}
435 for _, tc := range testCases {
436 t.Run(tc.desc, func(t *testing.T) {
437 err := astutil.Sanitize(tc.file)
438 if err != nil {
439 t.Fatal(err)
440 }
441
442 b, errs := format.Node(tc.file)
443 if errs != nil {
444 t.Fatal(errs)
445 }
446
447 got := string(b)
448 qt.Assert(t, qt.Equals(got, tc.want))
449 })
450 }
451 }
452
453
454 func TestX(t *testing.T) {
455 t.Skip()
456
457 field := &ast.Field{
458 Label: &ast.Alias{
459 Ident: ast.NewIdent("X"),
460 Expr: ast.NewIdent("a"),
461 },
462 Value: ast.NewString("b"),
463 }
464
465 file := &ast.File{Decls: []ast.Decl{
466 &ast.Field{
467 Label: ast.NewIdent("b"),
468 Value: ast.NewStruct(
469 field,
470 ast.NewIdent("b"), ast.NewStruct(
471 ast.NewIdent("a"), ast.NewString("bar"),
472 ast.NewIdent("b"), &ast.Ident{
473 Name: "a",
474 Node: field.Value,
475 },
476 ast.NewIdent("e"), &ast.Ident{
477 Name: "a",
478 Node: field.Value,
479 },
480 ),
481 ),
482 },
483 }}
484
485 err := astutil.Sanitize(file)
486 if err != nil {
487 t.Fatal(err)
488 }
489
490 b, errs := format.Node(file)
491 if errs != nil {
492 t.Fatal(errs)
493 }
494
495 t.Error(string(b))
496 }
497
View as plain text