| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package parser |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/ast" |
| "go/token" |
| "os" |
| "strings" |
| "testing" |
| ) |
| |
| var validFiles = []string{ |
| "parser.go", |
| "parser_test.go", |
| "error_test.go", |
| "short_test.go", |
| } |
| |
| func TestParse(t *testing.T) { |
| for _, filename := range validFiles { |
| _, err := ParseFile(token.NewFileSet(), filename, nil, DeclarationErrors) |
| if err != nil { |
| t.Fatalf("ParseFile(%s): %v", filename, err) |
| } |
| } |
| } |
| |
| func nameFilter(filename string) bool { |
| switch filename { |
| case "parser.go", "interface.go", "parser_test.go": |
| return true |
| case "parser.go.orig": |
| return true // permit but should be ignored by ParseDir |
| } |
| return false |
| } |
| |
| func dirFilter(f os.FileInfo) bool { return nameFilter(f.Name()) } |
| |
| func TestParseDir(t *testing.T) { |
| path := "." |
| pkgs, err := ParseDir(token.NewFileSet(), path, dirFilter, 0) |
| if err != nil { |
| t.Fatalf("ParseDir(%s): %v", path, err) |
| } |
| if n := len(pkgs); n != 1 { |
| t.Errorf("got %d packages; want 1", n) |
| } |
| pkg := pkgs["parser"] |
| if pkg == nil { |
| t.Errorf(`package "parser" not found`) |
| return |
| } |
| if n := len(pkg.Files); n != 3 { |
| t.Errorf("got %d package files; want 3", n) |
| } |
| for filename := range pkg.Files { |
| if !nameFilter(filename) { |
| t.Errorf("unexpected package file: %s", filename) |
| } |
| } |
| } |
| |
| func TestParseExpr(t *testing.T) { |
| // just kicking the tires: |
| // a valid arithmetic expression |
| src := "a + b" |
| x, err := ParseExpr(src) |
| if err != nil { |
| t.Errorf("ParseExpr(%q): %v", src, err) |
| } |
| // sanity check |
| if _, ok := x.(*ast.BinaryExpr); !ok { |
| t.Errorf("ParseExpr(%q): got %T, want *ast.BinaryExpr", src, x) |
| } |
| |
| // a valid type expression |
| src = "struct{x *int}" |
| x, err = ParseExpr(src) |
| if err != nil { |
| t.Errorf("ParseExpr(%q): %v", src, err) |
| } |
| // sanity check |
| if _, ok := x.(*ast.StructType); !ok { |
| t.Errorf("ParseExpr(%q): got %T, want *ast.StructType", src, x) |
| } |
| |
| // an invalid expression |
| src = "a + *" |
| if _, err := ParseExpr(src); err == nil { |
| t.Errorf("ParseExpr(%q): got no error", src) |
| } |
| |
| // a valid expression followed by extra tokens is invalid |
| src = "a[i] := x" |
| if _, err := ParseExpr(src); err == nil { |
| t.Errorf("ParseExpr(%q): got no error", src) |
| } |
| |
| // a semicolon is not permitted unless automatically inserted |
| src = "a + b\n" |
| if _, err := ParseExpr(src); err != nil { |
| t.Errorf("ParseExpr(%q): got error %s", src, err) |
| } |
| src = "a + b;" |
| if _, err := ParseExpr(src); err == nil { |
| t.Errorf("ParseExpr(%q): got no error", src) |
| } |
| |
| // various other stuff following a valid expression |
| const validExpr = "a + b" |
| const anything = "dh3*#D)#_" |
| for _, c := range "!)]};," { |
| src := validExpr + string(c) + anything |
| if _, err := ParseExpr(src); err == nil { |
| t.Errorf("ParseExpr(%q): got no error", src) |
| } |
| } |
| |
| // ParseExpr must not crash |
| for _, src := range valids { |
| ParseExpr(src) |
| } |
| } |
| |
| func TestColonEqualsScope(t *testing.T) { |
| f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { x, y, z := x, y, z }`, 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // RHS refers to undefined globals; LHS does not. |
| as := f.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.AssignStmt) |
| for _, v := range as.Rhs { |
| id := v.(*ast.Ident) |
| if id.Obj != nil { |
| t.Errorf("rhs %s has Obj, should not", id.Name) |
| } |
| } |
| for _, v := range as.Lhs { |
| id := v.(*ast.Ident) |
| if id.Obj == nil { |
| t.Errorf("lhs %s does not have Obj, should", id.Name) |
| } |
| } |
| } |
| |
| func TestVarScope(t *testing.T) { |
| f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { var x, y, z = x, y, z }`, 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // RHS refers to undefined globals; LHS does not. |
| as := f.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.DeclStmt).Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec) |
| for _, v := range as.Values { |
| id := v.(*ast.Ident) |
| if id.Obj != nil { |
| t.Errorf("rhs %s has Obj, should not", id.Name) |
| } |
| } |
| for _, id := range as.Names { |
| if id.Obj == nil { |
| t.Errorf("lhs %s does not have Obj, should", id.Name) |
| } |
| } |
| } |
| |
| func TestObjects(t *testing.T) { |
| const src = ` |
| package p |
| import fmt "fmt" |
| const pi = 3.14 |
| type T struct{} |
| var x int |
| func f() { L: } |
| ` |
| |
| f, err := ParseFile(token.NewFileSet(), "", src, 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| objects := map[string]ast.ObjKind{ |
| "p": ast.Bad, // not in a scope |
| "fmt": ast.Bad, // not resolved yet |
| "pi": ast.Con, |
| "T": ast.Typ, |
| "x": ast.Var, |
| "int": ast.Bad, // not resolved yet |
| "f": ast.Fun, |
| "L": ast.Lbl, |
| } |
| |
| ast.Inspect(f, func(n ast.Node) bool { |
| if ident, ok := n.(*ast.Ident); ok { |
| obj := ident.Obj |
| if obj == nil { |
| if objects[ident.Name] != ast.Bad { |
| t.Errorf("no object for %s", ident.Name) |
| } |
| return true |
| } |
| if obj.Name != ident.Name { |
| t.Errorf("names don't match: obj.Name = %s, ident.Name = %s", obj.Name, ident.Name) |
| } |
| kind := objects[ident.Name] |
| if obj.Kind != kind { |
| t.Errorf("%s: obj.Kind = %s; want %s", ident.Name, obj.Kind, kind) |
| } |
| } |
| return true |
| }) |
| } |
| |
| func TestUnresolved(t *testing.T) { |
| f, err := ParseFile(token.NewFileSet(), "", ` |
| package p |
| // |
| func f1a(int) |
| func f2a(byte, int, float) |
| func f3a(a, b int, c float) |
| func f4a(...complex) |
| func f5a(a s1a, b ...complex) |
| // |
| func f1b(*int) |
| func f2b([]byte, (int), *float) |
| func f3b(a, b *int, c []float) |
| func f4b(...*complex) |
| func f5b(a s1a, b ...[]complex) |
| // |
| type s1a struct { int } |
| type s2a struct { byte; int; s1a } |
| type s3a struct { a, b int; c float } |
| // |
| type s1b struct { *int } |
| type s2b struct { byte; int; *float } |
| type s3b struct { a, b *s3b; c []float } |
| `, 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| want := "int " + // f1a |
| "byte int float " + // f2a |
| "int float " + // f3a |
| "complex " + // f4a |
| "complex " + // f5a |
| // |
| "int " + // f1b |
| "byte int float " + // f2b |
| "int float " + // f3b |
| "complex " + // f4b |
| "complex " + // f5b |
| // |
| "int " + // s1a |
| "byte int " + // s2a |
| "int float " + // s3a |
| // |
| "int " + // s1a |
| "byte int float " + // s2a |
| "float " // s3a |
| |
| // collect unresolved identifiers |
| var buf bytes.Buffer |
| for _, u := range f.Unresolved { |
| buf.WriteString(u.Name) |
| buf.WriteByte(' ') |
| } |
| got := buf.String() |
| |
| if got != want { |
| t.Errorf("\ngot: %s\nwant: %s", got, want) |
| } |
| } |
| |
| var imports = map[string]bool{ |
| `"a"`: true, |
| "`a`": true, |
| `"a/b"`: true, |
| `"a.b"`: true, |
| `"m\x61th"`: true, |
| `"greek/αβ"`: true, |
| `""`: false, |
| |
| // Each of these pairs tests both `` vs "" strings |
| // and also use of invalid characters spelled out as |
| // escape sequences and written directly. |
| // For example `"\x00"` tests import "\x00" |
| // while "`\x00`" tests import `<actual-NUL-byte>`. |
| `"\x00"`: false, |
| "`\x00`": false, |
| `"\x7f"`: false, |
| "`\x7f`": false, |
| `"a!"`: false, |
| "`a!`": false, |
| `"a b"`: false, |
| "`a b`": false, |
| `"a\\b"`: false, |
| "`a\\b`": false, |
| "\"`a`\"": false, |
| "`\"a\"`": false, |
| `"\x80\x80"`: false, |
| "`\x80\x80`": false, |
| `"\xFFFD"`: false, |
| "`\xFFFD`": false, |
| } |
| |
| func TestImports(t *testing.T) { |
| for path, isValid := range imports { |
| src := fmt.Sprintf("package p; import %s", path) |
| _, err := ParseFile(token.NewFileSet(), "", src, 0) |
| switch { |
| case err != nil && isValid: |
| t.Errorf("ParseFile(%s): got %v; expected no error", src, err) |
| case err == nil && !isValid: |
| t.Errorf("ParseFile(%s): got no error; expected one", src) |
| } |
| } |
| } |
| |
| func TestCommentGroups(t *testing.T) { |
| f, err := ParseFile(token.NewFileSet(), "", ` |
| package p /* 1a */ /* 1b */ /* 1c */ // 1d |
| /* 2a |
| */ |
| // 2b |
| const pi = 3.1415 |
| /* 3a */ // 3b |
| /* 3c */ const e = 2.7182 |
| |
| // Example from issue 3139 |
| func ExampleCount() { |
| fmt.Println(strings.Count("cheese", "e")) |
| fmt.Println(strings.Count("five", "")) // before & after each rune |
| // Output: |
| // 3 |
| // 5 |
| } |
| `, ParseComments) |
| if err != nil { |
| t.Fatal(err) |
| } |
| expected := [][]string{ |
| {"/* 1a */", "/* 1b */", "/* 1c */", "// 1d"}, |
| {"/* 2a\n*/", "// 2b"}, |
| {"/* 3a */", "// 3b", "/* 3c */"}, |
| {"// Example from issue 3139"}, |
| {"// before & after each rune"}, |
| {"// Output:", "// 3", "// 5"}, |
| } |
| if len(f.Comments) != len(expected) { |
| t.Fatalf("got %d comment groups; expected %d", len(f.Comments), len(expected)) |
| } |
| for i, exp := range expected { |
| got := f.Comments[i].List |
| if len(got) != len(exp) { |
| t.Errorf("got %d comments in group %d; expected %d", len(got), i, len(exp)) |
| continue |
| } |
| for j, exp := range exp { |
| got := got[j].Text |
| if got != exp { |
| t.Errorf("got %q in group %d; expected %q", got, i, exp) |
| } |
| } |
| } |
| } |
| |
| func getField(file *ast.File, fieldname string) *ast.Field { |
| parts := strings.Split(fieldname, ".") |
| for _, d := range file.Decls { |
| if d, ok := d.(*ast.GenDecl); ok && d.Tok == token.TYPE { |
| for _, s := range d.Specs { |
| if s, ok := s.(*ast.TypeSpec); ok && s.Name.Name == parts[0] { |
| if s, ok := s.Type.(*ast.StructType); ok { |
| for _, f := range s.Fields.List { |
| for _, name := range f.Names { |
| if name.Name == parts[1] { |
| return f |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| // Don't use ast.CommentGroup.Text() - we want to see exact comment text. |
| func commentText(c *ast.CommentGroup) string { |
| var buf bytes.Buffer |
| if c != nil { |
| for _, c := range c.List { |
| buf.WriteString(c.Text) |
| } |
| } |
| return buf.String() |
| } |
| |
| func checkFieldComments(t *testing.T, file *ast.File, fieldname, lead, line string) { |
| f := getField(file, fieldname) |
| if f == nil { |
| t.Fatalf("field not found: %s", fieldname) |
| } |
| if got := commentText(f.Doc); got != lead { |
| t.Errorf("got lead comment %q; expected %q", got, lead) |
| } |
| if got := commentText(f.Comment); got != line { |
| t.Errorf("got line comment %q; expected %q", got, line) |
| } |
| } |
| |
| func TestLeadAndLineComments(t *testing.T) { |
| f, err := ParseFile(token.NewFileSet(), "", ` |
| package p |
| type T struct { |
| /* F1 lead comment */ |
| // |
| F1 int /* F1 */ // line comment |
| // F2 lead |
| // comment |
| F2 int // F2 line comment |
| // f3 lead comment |
| f3 int // f3 line comment |
| } |
| `, ParseComments) |
| if err != nil { |
| t.Fatal(err) |
| } |
| checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") |
| checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") |
| checkFieldComments(t, f, "T.f3", "// f3 lead comment", "// f3 line comment") |
| ast.FileExports(f) |
| checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") |
| checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") |
| if getField(f, "T.f3") != nil { |
| t.Error("not expected to find T.f3") |
| } |
| } |
| |
| // TestIssue9979 verifies that empty statements are contained within their enclosing blocks. |
| func TestIssue9979(t *testing.T) { |
| for _, src := range []string{ |
| "package p; func f() {;}", |
| "package p; func f() {L:}", |
| "package p; func f() {L:;}", |
| "package p; func f() {L:\n}", |
| "package p; func f() {L:\n;}", |
| "package p; func f() { ; }", |
| "package p; func f() { L: }", |
| "package p; func f() { L: ; }", |
| "package p; func f() { L: \n}", |
| "package p; func f() { L: \n; }", |
| } { |
| fset := token.NewFileSet() |
| f, err := ParseFile(fset, "", src, 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var pos, end token.Pos |
| ast.Inspect(f, func(x ast.Node) bool { |
| switch s := x.(type) { |
| case *ast.BlockStmt: |
| pos, end = s.Pos()+1, s.End()-1 // exclude "{", "}" |
| case *ast.LabeledStmt: |
| pos, end = s.Pos()+2, s.End() // exclude "L:" |
| case *ast.EmptyStmt: |
| // check containment |
| if s.Pos() < pos || s.End() > end { |
| t.Errorf("%s: %T[%d, %d] not inside [%d, %d]", src, s, s.Pos(), s.End(), pos, end) |
| } |
| // check semicolon |
| offs := fset.Position(s.Pos()).Offset |
| if ch := src[offs]; ch != ';' != s.Implicit { |
| want := "want ';'" |
| if s.Implicit { |
| want = "but ';' is implicit" |
| } |
| t.Errorf("%s: found %q at offset %d; %s", src, ch, offs, want) |
| } |
| } |
| return true |
| }) |
| } |
| } |
| |
| // TestIncompleteSelection ensures that an incomplete selector |
| // expression is parsed as a (blank) *ast.SelectorExpr, not a |
| // *ast.BadExpr. |
| func TestIncompleteSelection(t *testing.T) { |
| for _, src := range []string{ |
| "package p; var _ = fmt.", // at EOF |
| "package p; var _ = fmt.\ntype X int", // not at EOF |
| } { |
| fset := token.NewFileSet() |
| f, err := ParseFile(fset, "", src, 0) |
| if err == nil { |
| t.Errorf("ParseFile(%s) succeeded unexpectedly", src) |
| continue |
| } |
| |
| const wantErr = "expected selector or type assertion" |
| if !strings.Contains(err.Error(), wantErr) { |
| t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr) |
| } |
| |
| var sel *ast.SelectorExpr |
| ast.Inspect(f, func(n ast.Node) bool { |
| if n, ok := n.(*ast.SelectorExpr); ok { |
| sel = n |
| } |
| return true |
| }) |
| if sel == nil { |
| t.Error("found no *ast.SelectorExpr") |
| continue |
| } |
| const wantSel = "&{fmt _}" |
| if fmt.Sprint(sel) != wantSel { |
| t.Errorf("found selector %s, want %s", sel, wantSel) |
| continue |
| } |
| } |
| } |
| |
| func TestLastLineComment(t *testing.T) { |
| const src = `package main |
| type x int // comment |
| ` |
| fset := token.NewFileSet() |
| f, err := ParseFile(fset, "", src, ParseComments) |
| if err != nil { |
| t.Fatal(err) |
| } |
| comment := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Comment.List[0].Text |
| if comment != "// comment" { |
| t.Errorf("got %q, want %q", comment, "// comment") |
| } |
| } |