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