1// Copyright 2009 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package parser 6 7import ( 8 "bytes" 9 "fmt" 10 "go/ast" 11 "go/token" 12 "io/fs" 13 "strings" 14 "testing" 15) 16 17var validFiles = []string{ 18 "parser.go", 19 "parser_test.go", 20 "error_test.go", 21 "short_test.go", 22} 23 24func TestParse(t *testing.T) { 25 for _, filename := range validFiles { 26 _, err := ParseFile(token.NewFileSet(), filename, nil, DeclarationErrors) 27 if err != nil { 28 t.Fatalf("ParseFile(%s): %v", filename, err) 29 } 30 } 31} 32 33func nameFilter(filename string) bool { 34 switch filename { 35 case "parser.go", "interface.go", "parser_test.go": 36 return true 37 case "parser.go.orig": 38 return true // permit but should be ignored by ParseDir 39 } 40 return false 41} 42 43func dirFilter(f fs.FileInfo) bool { return nameFilter(f.Name()) } 44 45func TestParseFile(t *testing.T) { 46 src := "package p\nvar _=s[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]" 47 _, err := ParseFile(token.NewFileSet(), "", src, 0) 48 if err == nil { 49 t.Errorf("ParseFile(%s) succeeded unexpectedly", src) 50 } 51} 52 53func TestParseExprFrom(t *testing.T) { 54 src := "s[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]" 55 _, err := ParseExprFrom(token.NewFileSet(), "", src, 0) 56 if err == nil { 57 t.Errorf("ParseExprFrom(%s) succeeded unexpectedly", src) 58 } 59} 60 61func TestParseDir(t *testing.T) { 62 path := "." 63 pkgs, err := ParseDir(token.NewFileSet(), path, dirFilter, 0) 64 if err != nil { 65 t.Fatalf("ParseDir(%s): %v", path, err) 66 } 67 if n := len(pkgs); n != 1 { 68 t.Errorf("got %d packages; want 1", n) 69 } 70 pkg := pkgs["parser"] 71 if pkg == nil { 72 t.Errorf(`package "parser" not found`) 73 return 74 } 75 if n := len(pkg.Files); n != 3 { 76 t.Errorf("got %d package files; want 3", n) 77 } 78 for filename := range pkg.Files { 79 if !nameFilter(filename) { 80 t.Errorf("unexpected package file: %s", filename) 81 } 82 } 83} 84 85func TestIssue42951(t *testing.T) { 86 path := "./testdata/issue42951" 87 _, err := ParseDir(token.NewFileSet(), path, nil, 0) 88 if err != nil { 89 t.Errorf("ParseDir(%s): %v", path, err) 90 } 91} 92 93func TestParseExpr(t *testing.T) { 94 // just kicking the tires: 95 // a valid arithmetic expression 96 src := "a + b" 97 x, err := ParseExpr(src) 98 if err != nil { 99 t.Errorf("ParseExpr(%q): %v", src, err) 100 } 101 // sanity check 102 if _, ok := x.(*ast.BinaryExpr); !ok { 103 t.Errorf("ParseExpr(%q): got %T, want *ast.BinaryExpr", src, x) 104 } 105 106 // a valid type expression 107 src = "struct{x *int}" 108 x, err = ParseExpr(src) 109 if err != nil { 110 t.Errorf("ParseExpr(%q): %v", src, err) 111 } 112 // sanity check 113 if _, ok := x.(*ast.StructType); !ok { 114 t.Errorf("ParseExpr(%q): got %T, want *ast.StructType", src, x) 115 } 116 117 // an invalid expression 118 src = "a + *" 119 x, err = ParseExpr(src) 120 if err == nil { 121 t.Errorf("ParseExpr(%q): got no error", src) 122 } 123 if x == nil { 124 t.Errorf("ParseExpr(%q): got no (partial) result", src) 125 } 126 if _, ok := x.(*ast.BinaryExpr); !ok { 127 t.Errorf("ParseExpr(%q): got %T, want *ast.BinaryExpr", src, x) 128 } 129 130 // a valid expression followed by extra tokens is invalid 131 src = "a[i] := x" 132 if _, err := ParseExpr(src); err == nil { 133 t.Errorf("ParseExpr(%q): got no error", src) 134 } 135 136 // a semicolon is not permitted unless automatically inserted 137 src = "a + b\n" 138 if _, err := ParseExpr(src); err != nil { 139 t.Errorf("ParseExpr(%q): got error %s", src, err) 140 } 141 src = "a + b;" 142 if _, err := ParseExpr(src); err == nil { 143 t.Errorf("ParseExpr(%q): got no error", src) 144 } 145 146 // various other stuff following a valid expression 147 const validExpr = "a + b" 148 const anything = "dh3*#D)#_" 149 for _, c := range "!)]};," { 150 src := validExpr + string(c) + anything 151 if _, err := ParseExpr(src); err == nil { 152 t.Errorf("ParseExpr(%q): got no error", src) 153 } 154 } 155 156 // ParseExpr must not crash 157 for _, src := range valids { 158 ParseExpr(src) 159 } 160} 161 162func TestColonEqualsScope(t *testing.T) { 163 f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { x, y, z := x, y, z }`, 0) 164 if err != nil { 165 t.Fatal(err) 166 } 167 168 // RHS refers to undefined globals; LHS does not. 169 as := f.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.AssignStmt) 170 for _, v := range as.Rhs { 171 id := v.(*ast.Ident) 172 if id.Obj != nil { 173 t.Errorf("rhs %s has Obj, should not", id.Name) 174 } 175 } 176 for _, v := range as.Lhs { 177 id := v.(*ast.Ident) 178 if id.Obj == nil { 179 t.Errorf("lhs %s does not have Obj, should", id.Name) 180 } 181 } 182} 183 184func TestVarScope(t *testing.T) { 185 f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { var x, y, z = x, y, z }`, 0) 186 if err != nil { 187 t.Fatal(err) 188 } 189 190 // RHS refers to undefined globals; LHS does not. 191 as := f.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.DeclStmt).Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec) 192 for _, v := range as.Values { 193 id := v.(*ast.Ident) 194 if id.Obj != nil { 195 t.Errorf("rhs %s has Obj, should not", id.Name) 196 } 197 } 198 for _, id := range as.Names { 199 if id.Obj == nil { 200 t.Errorf("lhs %s does not have Obj, should", id.Name) 201 } 202 } 203} 204 205func TestObjects(t *testing.T) { 206 const src = ` 207package p 208import fmt "fmt" 209const pi = 3.14 210type T struct{} 211var x int 212func f() { L: } 213` 214 215 f, err := ParseFile(token.NewFileSet(), "", src, 0) 216 if err != nil { 217 t.Fatal(err) 218 } 219 220 objects := map[string]ast.ObjKind{ 221 "p": ast.Bad, // not in a scope 222 "fmt": ast.Bad, // not resolved yet 223 "pi": ast.Con, 224 "T": ast.Typ, 225 "x": ast.Var, 226 "int": ast.Bad, // not resolved yet 227 "f": ast.Fun, 228 "L": ast.Lbl, 229 } 230 231 ast.Inspect(f, func(n ast.Node) bool { 232 if ident, ok := n.(*ast.Ident); ok { 233 obj := ident.Obj 234 if obj == nil { 235 if objects[ident.Name] != ast.Bad { 236 t.Errorf("no object for %s", ident.Name) 237 } 238 return true 239 } 240 if obj.Name != ident.Name { 241 t.Errorf("names don't match: obj.Name = %s, ident.Name = %s", obj.Name, ident.Name) 242 } 243 kind := objects[ident.Name] 244 if obj.Kind != kind { 245 t.Errorf("%s: obj.Kind = %s; want %s", ident.Name, obj.Kind, kind) 246 } 247 } 248 return true 249 }) 250} 251 252func TestUnresolved(t *testing.T) { 253 f, err := ParseFile(token.NewFileSet(), "", ` 254package p 255// 256func f1a(int) 257func f2a(byte, int, float) 258func f3a(a, b int, c float) 259func f4a(...complex) 260func f5a(a s1a, b ...complex) 261// 262func f1b(*int) 263func f2b([]byte, (int), *float) 264func f3b(a, b *int, c []float) 265func f4b(...*complex) 266func f5b(a s1a, b ...[]complex) 267// 268type s1a struct { int } 269type s2a struct { byte; int; s1a } 270type s3a struct { a, b int; c float } 271// 272type s1b struct { *int } 273type s2b struct { byte; int; *float } 274type s3b struct { a, b *s3b; c []float } 275`, 0) 276 if err != nil { 277 t.Fatal(err) 278 } 279 280 want := "int " + // f1a 281 "byte int float " + // f2a 282 "int float " + // f3a 283 "complex " + // f4a 284 "complex " + // f5a 285 // 286 "int " + // f1b 287 "byte int float " + // f2b 288 "int float " + // f3b 289 "complex " + // f4b 290 "complex " + // f5b 291 // 292 "int " + // s1a 293 "byte int " + // s2a 294 "int float " + // s3a 295 // 296 "int " + // s1a 297 "byte int float " + // s2a 298 "float " // s3a 299 300 // collect unresolved identifiers 301 var buf bytes.Buffer 302 for _, u := range f.Unresolved { 303 buf.WriteString(u.Name) 304 buf.WriteByte(' ') 305 } 306 got := buf.String() 307 308 if got != want { 309 t.Errorf("\ngot: %s\nwant: %s", got, want) 310 } 311} 312 313var imports = map[string]bool{ 314 `"a"`: true, 315 "`a`": true, 316 `"a/b"`: true, 317 `"a.b"`: true, 318 `"m\x61th"`: true, 319 `"greek/αβ"`: true, 320 `""`: false, 321 322 // Each of these pairs tests both `` vs "" strings 323 // and also use of invalid characters spelled out as 324 // escape sequences and written directly. 325 // For example `"\x00"` tests import "\x00" 326 // while "`\x00`" tests import `<actual-NUL-byte>`. 327 `"\x00"`: false, 328 "`\x00`": false, 329 `"\x7f"`: false, 330 "`\x7f`": false, 331 `"a!"`: false, 332 "`a!`": false, 333 `"a b"`: false, 334 "`a b`": false, 335 `"a\\b"`: false, 336 "`a\\b`": false, 337 "\"`a`\"": false, 338 "`\"a\"`": false, 339 `"\x80\x80"`: false, 340 "`\x80\x80`": false, 341 `"\xFFFD"`: false, 342 "`\xFFFD`": false, 343} 344 345func TestImports(t *testing.T) { 346 for path, isValid := range imports { 347 src := fmt.Sprintf("package p; import %s", path) 348 _, err := ParseFile(token.NewFileSet(), "", src, 0) 349 switch { 350 case err != nil && isValid: 351 t.Errorf("ParseFile(%s): got %v; expected no error", src, err) 352 case err == nil && !isValid: 353 t.Errorf("ParseFile(%s): got no error; expected one", src) 354 } 355 } 356} 357 358func TestCommentGroups(t *testing.T) { 359 f, err := ParseFile(token.NewFileSet(), "", ` 360package p /* 1a */ /* 1b */ /* 1c */ // 1d 361/* 2a 362*/ 363// 2b 364const pi = 3.1415 365/* 3a */ // 3b 366/* 3c */ const e = 2.7182 367 368// Example from issue 3139 369func ExampleCount() { 370 fmt.Println(strings.Count("cheese", "e")) 371 fmt.Println(strings.Count("five", "")) // before & after each rune 372 // Output: 373 // 3 374 // 5 375} 376`, ParseComments) 377 if err != nil { 378 t.Fatal(err) 379 } 380 expected := [][]string{ 381 {"/* 1a */", "/* 1b */", "/* 1c */", "// 1d"}, 382 {"/* 2a\n*/", "// 2b"}, 383 {"/* 3a */", "// 3b", "/* 3c */"}, 384 {"// Example from issue 3139"}, 385 {"// before & after each rune"}, 386 {"// Output:", "// 3", "// 5"}, 387 } 388 if len(f.Comments) != len(expected) { 389 t.Fatalf("got %d comment groups; expected %d", len(f.Comments), len(expected)) 390 } 391 for i, exp := range expected { 392 got := f.Comments[i].List 393 if len(got) != len(exp) { 394 t.Errorf("got %d comments in group %d; expected %d", len(got), i, len(exp)) 395 continue 396 } 397 for j, exp := range exp { 398 got := got[j].Text 399 if got != exp { 400 t.Errorf("got %q in group %d; expected %q", got, i, exp) 401 } 402 } 403 } 404} 405 406func getField(file *ast.File, fieldname string) *ast.Field { 407 parts := strings.Split(fieldname, ".") 408 for _, d := range file.Decls { 409 if d, ok := d.(*ast.GenDecl); ok && d.Tok == token.TYPE { 410 for _, s := range d.Specs { 411 if s, ok := s.(*ast.TypeSpec); ok && s.Name.Name == parts[0] { 412 if s, ok := s.Type.(*ast.StructType); ok { 413 for _, f := range s.Fields.List { 414 for _, name := range f.Names { 415 if name.Name == parts[1] { 416 return f 417 } 418 } 419 } 420 } 421 } 422 } 423 } 424 } 425 return nil 426} 427 428// Don't use ast.CommentGroup.Text() - we want to see exact comment text. 429func commentText(c *ast.CommentGroup) string { 430 var buf bytes.Buffer 431 if c != nil { 432 for _, c := range c.List { 433 buf.WriteString(c.Text) 434 } 435 } 436 return buf.String() 437} 438 439func checkFieldComments(t *testing.T, file *ast.File, fieldname, lead, line string) { 440 f := getField(file, fieldname) 441 if f == nil { 442 t.Fatalf("field not found: %s", fieldname) 443 } 444 if got := commentText(f.Doc); got != lead { 445 t.Errorf("got lead comment %q; expected %q", got, lead) 446 } 447 if got := commentText(f.Comment); got != line { 448 t.Errorf("got line comment %q; expected %q", got, line) 449 } 450} 451 452func TestLeadAndLineComments(t *testing.T) { 453 f, err := ParseFile(token.NewFileSet(), "", ` 454package p 455type T struct { 456 /* F1 lead comment */ 457 // 458 F1 int /* F1 */ // line comment 459 // F2 lead 460 // comment 461 F2 int // F2 line comment 462 // f3 lead comment 463 f3 int // f3 line comment 464} 465`, ParseComments) 466 if err != nil { 467 t.Fatal(err) 468 } 469 checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") 470 checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") 471 checkFieldComments(t, f, "T.f3", "// f3 lead comment", "// f3 line comment") 472 ast.FileExports(f) 473 checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") 474 checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") 475 if getField(f, "T.f3") != nil { 476 t.Error("not expected to find T.f3") 477 } 478} 479 480// TestIssue9979 verifies that empty statements are contained within their enclosing blocks. 481func TestIssue9979(t *testing.T) { 482 for _, src := range []string{ 483 "package p; func f() {;}", 484 "package p; func f() {L:}", 485 "package p; func f() {L:;}", 486 "package p; func f() {L:\n}", 487 "package p; func f() {L:\n;}", 488 "package p; func f() { ; }", 489 "package p; func f() { L: }", 490 "package p; func f() { L: ; }", 491 "package p; func f() { L: \n}", 492 "package p; func f() { L: \n; }", 493 } { 494 fset := token.NewFileSet() 495 f, err := ParseFile(fset, "", src, 0) 496 if err != nil { 497 t.Fatal(err) 498 } 499 500 var pos, end token.Pos 501 ast.Inspect(f, func(x ast.Node) bool { 502 switch s := x.(type) { 503 case *ast.BlockStmt: 504 pos, end = s.Pos()+1, s.End()-1 // exclude "{", "}" 505 case *ast.LabeledStmt: 506 pos, end = s.Pos()+2, s.End() // exclude "L:" 507 case *ast.EmptyStmt: 508 // check containment 509 if s.Pos() < pos || s.End() > end { 510 t.Errorf("%s: %T[%d, %d] not inside [%d, %d]", src, s, s.Pos(), s.End(), pos, end) 511 } 512 // check semicolon 513 offs := fset.Position(s.Pos()).Offset 514 if ch := src[offs]; ch != ';' != s.Implicit { 515 want := "want ';'" 516 if s.Implicit { 517 want = "but ';' is implicit" 518 } 519 t.Errorf("%s: found %q at offset %d; %s", src, ch, offs, want) 520 } 521 } 522 return true 523 }) 524 } 525} 526 527// TestIncompleteSelection ensures that an incomplete selector 528// expression is parsed as a (blank) *ast.SelectorExpr, not a 529// *ast.BadExpr. 530func TestIncompleteSelection(t *testing.T) { 531 for _, src := range []string{ 532 "package p; var _ = fmt.", // at EOF 533 "package p; var _ = fmt.\ntype X int", // not at EOF 534 } { 535 fset := token.NewFileSet() 536 f, err := ParseFile(fset, "", src, 0) 537 if err == nil { 538 t.Errorf("ParseFile(%s) succeeded unexpectedly", src) 539 continue 540 } 541 542 const wantErr = "expected selector or type assertion" 543 if !strings.Contains(err.Error(), wantErr) { 544 t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr) 545 } 546 547 var sel *ast.SelectorExpr 548 ast.Inspect(f, func(n ast.Node) bool { 549 if n, ok := n.(*ast.SelectorExpr); ok { 550 sel = n 551 } 552 return true 553 }) 554 if sel == nil { 555 t.Error("found no *ast.SelectorExpr") 556 continue 557 } 558 const wantSel = "&{fmt _}" 559 if fmt.Sprint(sel) != wantSel { 560 t.Errorf("found selector %s, want %s", sel, wantSel) 561 continue 562 } 563 } 564} 565 566func TestLastLineComment(t *testing.T) { 567 const src = `package main 568type x int // comment 569` 570 fset := token.NewFileSet() 571 f, err := ParseFile(fset, "", src, ParseComments) 572 if err != nil { 573 t.Fatal(err) 574 } 575 comment := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Comment.List[0].Text 576 if comment != "// comment" { 577 t.Errorf("got %q, want %q", comment, "// comment") 578 } 579} 580