github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/astutil/astutil_test.go (about) 1 package astutil 2 3 import ( 4 "go/ast" 5 "strconv" 6 "testing" 7 8 "github.com/gopherjs/gopherjs/internal/srctesting" 9 ) 10 11 func TestImportsUnsafe(t *testing.T) { 12 tests := []struct { 13 desc string 14 imports string 15 want bool 16 }{ 17 { 18 desc: "no imports", 19 imports: "", 20 want: false, 21 }, { 22 desc: "other imports", 23 imports: `import "some/other/package"`, 24 want: false, 25 }, { 26 desc: "only unsafe", 27 imports: `import "unsafe"`, 28 want: true, 29 }, { 30 desc: "multi-import decl", 31 imports: `import ( 32 "some/other/package" 33 "unsafe" 34 )`, 35 want: true, 36 }, { 37 desc: "two import decls", 38 imports: `import "some/other/package" 39 import "unsafe"`, 40 want: true, 41 }, 42 } 43 for _, test := range tests { 44 t.Run(test.desc, func(t *testing.T) { 45 src := "package testpackage\n\n" + test.imports 46 file := srctesting.New(t).Parse("test.go", src) 47 got := ImportsUnsafe(file) 48 if got != test.want { 49 t.Fatalf("ImportsUnsafe() returned %t, want %t", got, test.want) 50 } 51 }) 52 } 53 } 54 55 func TestImportName(t *testing.T) { 56 tests := []struct { 57 desc string 58 src string 59 want string 60 }{ 61 { 62 desc: `named import`, 63 src: `import foo "some/other/bar"`, 64 want: `foo`, 65 }, { 66 desc: `unnamed import`, 67 src: `import "some/other/bar"`, 68 want: `bar`, 69 }, { 70 desc: `dot import`, 71 src: `import . "some/other/bar"`, 72 want: ``, 73 }, { 74 desc: `blank import`, 75 src: `import _ "some/other/bar"`, 76 want: ``, 77 }, 78 } 79 for _, test := range tests { 80 t.Run(test.desc, func(t *testing.T) { 81 src := "package testpackage\n\n" + test.src 82 file := srctesting.New(t).Parse("test.go", src) 83 if len(file.Imports) != 1 { 84 t.Fatal(`expected one and only one import`) 85 } 86 importSpec := file.Imports[0] 87 got := ImportName(importSpec) 88 if got != test.want { 89 t.Fatalf(`ImportName() returned %q, want %q`, got, test.want) 90 } 91 }) 92 } 93 } 94 95 func TestFuncKey(t *testing.T) { 96 tests := []struct { 97 desc string 98 src string 99 want string 100 }{ 101 { 102 desc: `top-level function`, 103 src: `func foo() {}`, 104 want: `foo`, 105 }, { 106 desc: `top-level exported function`, 107 src: `func Foo() {}`, 108 want: `Foo`, 109 }, { 110 desc: `method on reference`, 111 src: `func (_ myType) bar() {}`, 112 want: `myType.bar`, 113 }, { 114 desc: `method on pointer`, 115 src: ` func (_ *myType) bar() {}`, 116 want: `myType.bar`, 117 }, { 118 desc: `method on generic reference`, 119 src: ` func (_ myType[T]) bar() {}`, 120 want: `myType.bar`, 121 }, { 122 desc: `method on generic pointer`, 123 src: ` func (_ *myType[T]) bar() {}`, 124 want: `myType.bar`, 125 }, { 126 desc: `method on struct with multiple generics`, 127 src: ` func (_ *myType[T1, T2, T3, T4]) bar() {}`, 128 want: `myType.bar`, 129 }, 130 } 131 for _, test := range tests { 132 t.Run(test.desc, func(t *testing.T) { 133 src := `package testpackage; ` + test.src 134 fdecl := srctesting.ParseFuncDecl(t, src) 135 if got := FuncKey(fdecl); got != test.want { 136 t.Errorf(`Got %q, want %q`, got, test.want) 137 } 138 }) 139 } 140 } 141 142 func TestHasDirectiveOnDecl(t *testing.T) { 143 tests := []struct { 144 desc string 145 src string 146 want bool 147 }{ 148 { 149 desc: `no comment on function`, 150 src: `package testpackage; 151 func foo() {}`, 152 want: false, 153 }, { 154 desc: `no directive on function with comment`, 155 src: `package testpackage; 156 // foo has no directive 157 func foo() {}`, 158 want: false, 159 }, { 160 desc: `wrong directive on function`, 161 src: `package testpackage; 162 //gopherjs:wrong-directive 163 func foo() {}`, 164 want: false, 165 }, { 166 desc: `correct directive on function`, 167 src: `package testpackage; 168 //gopherjs:do-stuff 169 // foo has a directive to do stuff 170 func foo() {}`, 171 want: true, 172 }, { 173 desc: `correct directive in multiline comment on function`, 174 src: `package testpackage; 175 /*gopherjs:do-stuff 176 foo has a directive to do stuff 177 */ 178 func foo() {}`, 179 want: true, 180 }, { 181 desc: `invalid directive in multiline comment on function`, 182 src: `package testpackage; 183 /* 184 gopherjs:do-stuff 185 */ 186 func foo() {}`, 187 want: false, 188 }, { 189 desc: `prefix directive on function`, 190 src: `package testpackage; 191 //gopherjs:do-stuffs 192 func foo() {}`, 193 want: false, 194 }, { 195 desc: `multiple directives on function`, 196 src: `package testpackage; 197 //gopherjs:wrong-directive 198 //gopherjs:do-stuff 199 //gopherjs:another-directive 200 func foo() {}`, 201 want: true, 202 }, { 203 desc: `directive with explanation on function`, 204 src: `package testpackage; 205 //gopherjs:do-stuff 'cause we can 206 func foo() {}`, 207 want: true, 208 }, { 209 desc: `no directive on type declaration`, 210 src: `package testpackage; 211 // Foo has a comment 212 type Foo int`, 213 want: false, 214 }, { 215 desc: `directive on type declaration`, 216 src: `package testpackage; 217 //gopherjs:do-stuff 218 type Foo int`, 219 want: true, 220 }, { 221 desc: `directive on specification, not on declaration`, 222 src: `package testpackage; 223 type ( 224 Foo int 225 226 //gopherjs:do-stuff 227 Bar struct{} 228 )`, 229 want: false, 230 }, { 231 desc: `no directive on const declaration`, 232 src: `package testpackage; 233 const foo = 42`, 234 want: false, 235 }, { 236 desc: `directive on const documentation`, 237 src: `package testpackage; 238 //gopherjs:do-stuff 239 const foo = 42`, 240 want: true, 241 }, { 242 desc: `no directive on var declaration`, 243 src: `package testpackage; 244 var foo = 42`, 245 want: false, 246 }, { 247 desc: `directive on var documentation`, 248 src: `package testpackage; 249 //gopherjs:do-stuff 250 var foo = 42`, 251 want: true, 252 }, { 253 desc: `no directive on var declaration`, 254 src: `package testpackage; 255 import _ "embed"`, 256 want: false, 257 }, { 258 desc: `directive on var documentation`, 259 src: `package testpackage; 260 //gopherjs:do-stuff 261 import _ "embed"`, 262 want: true, 263 }, 264 } 265 266 for _, test := range tests { 267 t.Run(test.desc, func(t *testing.T) { 268 const action = `do-stuff` 269 decl := srctesting.ParseDecl(t, test.src) 270 if got := hasDirective(decl, action); got != test.want { 271 t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, decl, action, got, test.want) 272 } 273 }) 274 } 275 } 276 277 func TestHasDirectiveOnSpec(t *testing.T) { 278 tests := []struct { 279 desc string 280 src string 281 want bool 282 }{ 283 { 284 desc: `no directive on type specification`, 285 src: `package testpackage; 286 type Foo int`, 287 want: false, 288 }, { 289 desc: `directive on declaration, not on specification`, 290 src: `package testpackage; 291 //gopherjs:do-stuff 292 type Foo int`, 293 want: false, 294 }, { 295 desc: `directive in doc on type specification`, 296 src: `package testpackage; 297 type ( 298 //gopherjs:do-stuff 299 Foo int 300 )`, 301 want: true, 302 }, { 303 desc: `directive in line on type specification`, 304 src: `package testpackage; 305 type Foo int //gopherjs:do-stuff`, 306 want: true, 307 }, { 308 desc: `no directive on const specification`, 309 src: `package testpackage; 310 const foo = 42`, 311 want: false, 312 }, { 313 desc: `directive in doc on const specification`, 314 src: `package testpackage; 315 const ( 316 //gopherjs:do-stuff 317 foo = 42 318 )`, 319 want: true, 320 }, { 321 desc: `directive in line on const specification`, 322 src: `package testpackage; 323 const foo = 42 //gopherjs:do-stuff`, 324 want: true, 325 }, { 326 desc: `no directive on var specification`, 327 src: `package testpackage; 328 var foo = 42`, 329 want: false, 330 }, { 331 desc: `directive in doc on var specification`, 332 src: `package testpackage; 333 var ( 334 //gopherjs:do-stuff 335 foo = 42 336 )`, 337 want: true, 338 }, { 339 desc: `directive in line on var specification`, 340 src: `package testpackage; 341 var foo = 42 //gopherjs:do-stuff`, 342 want: true, 343 }, { 344 desc: `no directive on import specification`, 345 src: `package testpackage; 346 import _ "embed"`, 347 want: false, 348 }, { 349 desc: `directive in doc on import specification`, 350 src: `package testpackage; 351 import ( 352 //gopherjs:do-stuff 353 _ "embed" 354 )`, 355 want: true, 356 }, { 357 desc: `directive in line on import specification`, 358 src: `package testpackage; 359 import _ "embed" //gopherjs:do-stuff`, 360 want: true, 361 }, 362 } 363 364 for _, test := range tests { 365 t.Run(test.desc, func(t *testing.T) { 366 const action = `do-stuff` 367 spec := srctesting.ParseSpec(t, test.src) 368 if got := hasDirective(spec, action); got != test.want { 369 t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, spec, action, got, test.want) 370 } 371 }) 372 } 373 } 374 375 func TestHasDirectiveOnFile(t *testing.T) { 376 tests := []struct { 377 desc string 378 src string 379 want bool 380 }{ 381 { 382 desc: `no directive on file`, 383 src: `package testpackage; 384 //gopherjs:do-stuff 385 type Foo int`, 386 want: false, 387 }, { 388 desc: `directive on file`, 389 src: `//gopherjs:do-stuff 390 package testpackage; 391 type Foo int`, 392 want: true, 393 }, 394 } 395 396 for _, test := range tests { 397 t.Run(test.desc, func(t *testing.T) { 398 const action = `do-stuff` 399 file := srctesting.New(t).Parse("test.go", test.src) 400 if got := hasDirective(file, action); got != test.want { 401 t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, file, action, got, test.want) 402 } 403 }) 404 } 405 } 406 407 func TestHasDirectiveOnField(t *testing.T) { 408 tests := []struct { 409 desc string 410 src string 411 want bool 412 }{ 413 { 414 desc: `no directive on struct field`, 415 src: `package testpackage; 416 type Foo struct { 417 bar int 418 }`, 419 want: false, 420 }, { 421 desc: `directive in doc on struct field`, 422 src: `package testpackage; 423 type Foo struct { 424 //gopherjs:do-stuff 425 bar int 426 }`, 427 want: true, 428 }, { 429 desc: `directive in line on struct field`, 430 src: `package testpackage; 431 type Foo struct { 432 bar int //gopherjs:do-stuff 433 }`, 434 want: true, 435 }, { 436 desc: `no directive on interface method`, 437 src: `package testpackage; 438 type Foo interface { 439 Bar(a int) int 440 }`, 441 want: false, 442 }, { 443 desc: `directive in doc on interface method`, 444 src: `package testpackage; 445 type Foo interface { 446 //gopherjs:do-stuff 447 Bar(a int) int 448 }`, 449 want: true, 450 }, { 451 desc: `directive in line on interface method`, 452 src: `package testpackage; 453 type Foo interface { 454 Bar(a int) int //gopherjs:do-stuff 455 }`, 456 want: true, 457 }, 458 } 459 460 for _, test := range tests { 461 t.Run(test.desc, func(t *testing.T) { 462 const action = `do-stuff` 463 spec := srctesting.ParseSpec(t, test.src) 464 tspec := spec.(*ast.TypeSpec) 465 var field *ast.Field 466 switch typeNode := tspec.Type.(type) { 467 case *ast.StructType: 468 field = typeNode.Fields.List[0] 469 case *ast.InterfaceType: 470 field = typeNode.Methods.List[0] 471 default: 472 t.Errorf(`unexpected node type, %T, when finding field`, typeNode) 473 return 474 } 475 if got := hasDirective(field, action); got != test.want { 476 t.Errorf(`hasDirective(%T, %q) returned %t, want %t`, field, action, got, test.want) 477 } 478 }) 479 } 480 } 481 482 func TestEndsWithReturn(t *testing.T) { 483 tests := []struct { 484 desc string 485 src string 486 want bool 487 }{ 488 { 489 desc: "empty function", 490 src: `func foo() {}`, 491 want: false, 492 }, { 493 desc: "implicit return", 494 src: `func foo() { a() }`, 495 want: false, 496 }, { 497 desc: "explicit return", 498 src: `func foo() { a(); return }`, 499 want: true, 500 }, { 501 desc: "labelled return", 502 src: `func foo() { Label: return }`, 503 want: true, 504 }, { 505 desc: "labelled call", 506 src: `func foo() { Label: a() }`, 507 want: false, 508 }, { 509 desc: "return in a block", 510 src: `func foo() { a(); { b(); return; } }`, 511 want: true, 512 }, { 513 desc: "a block without return", 514 src: `func foo() { a(); { b(); c(); } }`, 515 want: false, 516 }, { 517 desc: "conditional block", 518 src: `func foo() { a(); if x { b(); return; } }`, 519 want: false, 520 }, 521 } 522 523 for _, test := range tests { 524 t.Run(test.desc, func(t *testing.T) { 525 fdecl := srctesting.ParseFuncDecl(t, "package testpackage\n"+test.src) 526 got := EndsWithReturn(fdecl.Body.List) 527 if got != test.want { 528 t.Errorf("EndsWithReturn() returned %t, want %t", got, test.want) 529 } 530 }) 531 } 532 } 533 534 func TestSqueezeIdents(t *testing.T) { 535 tests := []struct { 536 desc string 537 count int 538 assign []int 539 }{ 540 { 541 desc: `no squeezing`, 542 count: 5, 543 assign: []int{0, 1, 2, 3, 4}, 544 }, { 545 desc: `missing front`, 546 count: 5, 547 assign: []int{3, 4}, 548 }, { 549 desc: `missing back`, 550 count: 5, 551 assign: []int{0, 1, 2}, 552 }, { 553 desc: `missing several`, 554 count: 10, 555 assign: []int{1, 2, 3, 6, 8}, 556 }, { 557 desc: `empty`, 558 count: 0, 559 assign: []int{}, 560 }, 561 } 562 563 for _, test := range tests { 564 t.Run(test.desc, func(t *testing.T) { 565 input := make([]*ast.Ident, test.count) 566 for _, i := range test.assign { 567 input[i] = ast.NewIdent(strconv.Itoa(i)) 568 } 569 570 result := Squeeze(input) 571 if len(result) != len(test.assign) { 572 t.Errorf("Squeeze() returned a slice %d long, want %d", len(result), len(test.assign)) 573 } 574 for i, id := range input { 575 if i < len(result) { 576 if id == nil { 577 t.Errorf(`Squeeze() returned a nil in result at %d`, i) 578 } else { 579 value, err := strconv.Atoi(id.Name) 580 if err != nil || value != test.assign[i] { 581 t.Errorf(`Squeeze() returned %s at %d instead of %d`, id.Name, i, test.assign[i]) 582 } 583 } 584 } else if id != nil { 585 t.Errorf(`Squeeze() didn't clear out tail of slice, want %d nil`, i) 586 } 587 } 588 }) 589 } 590 }