cuelang.org/go@v0.10.1/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 "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 &ast.CommentGroup{List: []*ast.Comment{{Text: "// 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 // Resolve both identifiers to same clause. 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 // Remove this reference. 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 // Resolve both identifiers to same clause. 305 file: func() *ast.File { 306 fieldX := &ast.Field{ 307 Label: &ast.Alias{ 308 Ident: ast.NewIdent("X"), 309 Expr: ast.NewIdent("a"), // shadowed 310 }, 311 Value: ast.NewString("b"), 312 } 313 fieldY := &ast.Field{ 314 Label: &ast.Alias{ 315 Ident: ast.NewIdent("Y"), // shadowed 316 Expr: ast.NewIdent("q"), // not shadowed 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 // Resolve both identifiers to same clause. 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 // Resolve both identifiers to same clause. 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 // For testing purposes: do not remove. 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 }