// format parses src, prints the corresponding AST, verifies the resulting // src is syntactically correct, and returns the resulting src or an error // if any. func format(src []byte, mode checkMode) ([]byte, error) { // parse src f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { return nil, fmt.Errorf("parse: %s\n%s", err, src) } // filter exports if necessary if mode&export != 0 { ast.FileExports(f) // ignore result f.Comments = nil // don't print comments that are not in AST } // determine printer configuration cfg := Config{Tabwidth: tabwidth} if mode&rawFormat != 0 { cfg.Mode |= RawFormat } // print AST var buf bytes.Buffer if err := cfg.Fprint(&buf, fset, f); err != nil { return nil, fmt.Errorf("print: %s", err) } // make sure formatted output is syntactically correct res := buf.Bytes() if _, err := parser.ParseFile(fset, "", res, 0); err != nil { return nil, fmt.Errorf("re-parse: %s\n%s", err, buf.Bytes()) } return res, nil }
func TestIssue8518(t *testing.T) { fset := token.NewFileSet() imports := make(testImporter) conf := Config{ Error: func(err error) { t.Log(err) }, // don't exit after first error Importer: imports, } makePkg := func(path, src string) { f, err := parser.ParseFile(fset, path, src, 0) if err != nil { t.Fatal(err) } pkg, _ := conf.Check(path, fset, []*ast.File{f}, nil) // errors logged via conf.Error imports[path] = pkg } const libSrc = ` package a import "missing" const C1 = foo const C2 = missing.C ` const mainSrc = ` package main import "a" var _ = a.C1 var _ = a.C2 ` makePkg("a", libSrc) makePkg("main", mainSrc) // don't crash when type-checking this package }
func TestMultiFileInitOrder(t *testing.T) { fset := token.NewFileSet() mustParse := func(src string) *ast.File { f, err := parser.ParseFile(fset, "main", src, 0) if err != nil { t.Fatal(err) } return f } fileA := mustParse(`package main; var a = 1`) fileB := mustParse(`package main; var b = 2`) // The initialization order must not depend on the parse // order of the files, only on the presentation order to // the type-checker. for _, test := range []struct { files []*ast.File want string }{ {[]*ast.File{fileA, fileB}, "[a = 1 b = 2]"}, {[]*ast.File{fileB, fileA}, "[b = 2 a = 1]"}, } { var info Info if _, err := new(Config).Check("main", fset, test.files, &info); err != nil { t.Fatal(err) } if got := fmt.Sprint(info.InitOrder); got != test.want { t.Fatalf("got %s; want %s", got, test.want) } } }
func TestExamples(t *testing.T) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleTestFile), parser.ParseComments) if err != nil { t.Fatal(err) } for i, e := range doc.Examples(file) { c := exampleTestCases[i] if e.Name != c.Name { t.Errorf("got Name == %q, want %q", e.Name, c.Name) } if w := c.Play; w != "" { var g string // hah if e.Play == nil { g = "<nil>" } else { var buf bytes.Buffer if err := format.Node(&buf, fset, e.Play); err != nil { t.Fatal(err) } g = buf.String() } if g != w { t.Errorf("%s: got Play == %q, want %q", c.Name, g, w) } } if g, w := e.Output, c.Output; g != w { t.Errorf("%s: got Output == %q, want %q", c.Name, g, w) } } }
func TestFilterDuplicates(t *testing.T) { // parse input fset := token.NewFileSet() file, err := parser.ParseFile(fset, "", input, 0) if err != nil { t.Fatal(err) } // create package files := map[string]*ast.File{"": file} pkg, err := ast.NewPackage(fset, files, nil, nil) if err != nil { t.Fatal(err) } // filter merged := ast.MergePackageFiles(pkg, ast.FilterFuncDuplicates) // pretty-print var buf bytes.Buffer if err := format.Node(&buf, fset, merged); err != nil { t.Fatal(err) } output := buf.String() if output != golden { t.Errorf("incorrect output:\n%s", output) } }
func parse(t *testing.T, name, in string) *ast.File { file, err := parser.ParseFile(fset, name, in, parser.ParseComments) if err != nil { t.Fatalf("%s parse: %v", name, err) } return file }
// Verify that the printer produces a correct program // even if the position information of comments introducing newlines // is incorrect. func TestBadComments(t *testing.T) { const src = ` // first comment - text and position changed by test package p import "fmt" const pi = 3.14 // rough circle var ( x, y, z int = 1, 2, 3 u, v float64 ) func fibo(n int) { if n < 2 { return n /* seed values */ } return fibo(n-1) + fibo(n-2) } ` f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { t.Error(err) // error in test } comment := f.Comments[0].List[0] pos := comment.Pos() if fset.Position(pos).Offset != 1 { t.Error("expected offset 1") // error in test } testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "//-style comment"}) testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment */"}) testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style \n comment */"}) testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment \n\n\n */"}) }
// TestLineComments, using a simple test case, checks that consecutive line // comments are properly terminated with a newline even if the AST position // information is incorrect. // func TestLineComments(t *testing.T) { const src = `// comment 1 // comment 2 // comment 3 package main ` fset := token.NewFileSet() f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { panic(err) // error in test } var buf bytes.Buffer fset = token.NewFileSet() // use the wrong file set Fprint(&buf, fset, f) nlines := 0 for _, ch := range buf.Bytes() { if ch == '\n' { nlines++ } } const expected = 3 if nlines < expected { t.Errorf("got %d, expected %d\n", nlines, expected) t.Errorf("result:\n%s", buf.Bytes()) } }
func ExampleConvertAST() { fset := token.NewFileSet() a, _ := parser.ParseFile(fset, "example.sgo", ` package example type LinkedList struct { Head int // For SGo: ?*LinkedList Tail *LinkedList } `, parser.ParseComments) info := &types.Info{ Defs: map[*ast.Ident]types.Object{}, Uses: map[*ast.Ident]types.Object{}, } cfg := &types.Config{} cfg.Check("", fset, []*ast.File{a}, info) importer.ConvertAST(a, info, nil) printer.Fprint(os.Stdout, fset, a) // Output: // package example // // type LinkedList struct { // Head int // // For SGo: ?*LinkedList // Tail ?*LinkedList // } }
func TestIssue7245(t *testing.T) { src := ` package p func (T) m() (res bool) { return } type T struct{} // receiver type after method declaration ` f, err := parser.ParseFile(fset, "", src, 0) if err != nil { t.Fatal(err) } var conf Config defs := make(map[*ast.Ident]Object) _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Defs: defs}) if err != nil { t.Fatal(err) } m := f.Decls[0].(*ast.FuncDecl) res1 := defs[m.Name].(*Func).Type().(*Signature).Results().At(0) res2 := defs[m.Type.Results.List[0].Names[0]].(*Var) if res1 != res2 { t.Errorf("got %s (%p) != %s (%p)", res1, res2, res1, res2) } }
func loadExportsGoPath(dir string) map[string]bool { exports := make(map[string]bool) buildPkg, err := build.ImportDir(dir, 0) if err != nil { if strings.Contains(err.Error(), "no buildable Go source files in") { return nil } fmt.Fprintf(os.Stderr, "could not import %q: %v\n", dir, err) return nil } fset := token.NewFileSet() for _, files := range [...][]string{buildPkg.GoFiles, buildPkg.CgoFiles} { for _, file := range files { f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0) if err != nil { fmt.Fprintf(os.Stderr, "could not parse %q: %v\n", file, err) continue } for name := range f.Scope.Objects { if ast.IsExported(name) { exports[name] = true } } } } return exports }
// For SGo: func(whence string, files ...NamedFile) ([][]byte, []error) func TranslateFilesFrom(whence string, files ...NamedFile) ([][]byte, []error) { var errs []error fset := token.NewFileSet() cwd, err := os.Getwd() if err != nil { return nil, []error{err} } var parsed []*ast.File var srcs [][]byte for _, named := range files { src, err := ioutil.ReadAll(named.File) if err != nil { errs = append(errs, err) continue } relPath, err := filepath.Rel(cwd, named.Path) if err != nil { relPath = named.Path } file, err := parser.ParseFile(fset, relPath, src, parser.ParseComments) if err != nil { errs = append(errs, err) continue } srcs = append(srcs, src) parsed = append(parsed, file) } // Early typecheck, because fileWithAnnotationComments adds lines and // then type errors are reported in the wrong line. _, typeErrs := typecheck("translate", fset, whence, parsed...) if len(typeErrs) > 0 { errs = append(errs, makeErrList(fset, typeErrs)) return nil, errs } oldFset := fset fset = token.NewFileSet() // fileWithAnnotationComments will reparse. for i, p := range parsed { var err error srcs[i], parsed[i], err = fileWithAnnotationComments(p, fset, oldFset, srcs[i]) if err != nil { errs = append(errs, err) } } if len(errs) > 0 { return nil, errs } info, typeErrs := typecheck("translate", fset, whence, parsed...) if len(typeErrs) > 0 { errs = append(errs, makeErrList(fset, typeErrs)) return nil, errs } return translate(info, srcs, parsed, fset), errs }
// This example illustrates how to remove a variable declaration // in a Go program while maintaining correct comment association // using an ast.CommentMap. func ExampleCommentMap() { // src is the input for which we create the AST that we // are going to manipulate. src := ` // This is the package comment. package main // This comment is associated with the hello constant. const hello = "Hello, World!" // line comment 1 // This comment is associated with the foo variable. var foo = hello // line comment 2 // This comment is associated with the main function. func main() { fmt.Println(hello) // line comment 3 } ` // Create the AST by parsing src. fset := token.NewFileSet() // positions are relative to fset f, err := parser.ParseFile(fset, "src.go", src, parser.ParseComments) if err != nil { panic(err) } // Create an ast.CommentMap from the ast.File's comments. // This helps keeping the association between comments // and AST nodes. cmap := ast.NewCommentMap(fset, f, f.Comments) // Remove the first variable declaration from the list of declarations. f.Decls = removeFirstVarDecl(f.Decls) // Use the comment map to filter comments that don't belong anymore // (the comments associated with the variable declaration), and create // the new comments list. f.Comments = cmap.Filter(f).Comments() // Print the modified AST. var buf bytes.Buffer if err := format.Node(&buf, fset, f); err != nil { panic(err) } fmt.Printf("%s", buf.Bytes()) // output: // // This is the package comment. // package main // // // This comment is associated with the hello constant. // const hello = "Hello, World!" // line comment 1 // // // This comment is associated with the main function. // func main() { // fmt.Println(hello) // line comment 3 // } }
func pkgFor(path, source string, info *Info) (*Package, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, path, source, 0) if err != nil { return nil, err } conf := Config{Importer: importer.Default([]*ast.File{f}), AllowUseUninitializedVars: true, AllowUninitializedExprs: true} return conf.Check(f.Name.Name, fset, []*ast.File{f}, info) }
func makePkg(t *testing.T, src string) (*Package, error) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors) if err != nil { return nil, err } // use the package name as package path conf := Config{Importer: importer.Default([]*ast.File{file})} return conf.Check(file.Name.Name, fset, []*ast.File{file}, nil) }
func parseFunc(filename, functionname string) (fun *ast.FuncDecl, fset *token.FileSet) { fset = token.NewFileSet() if file, err := parser.ParseFile(fset, filename, nil, 0); err == nil { for _, d := range file.Decls { if f, ok := d.(*ast.FuncDecl); ok && f.Name.Name == functionname { fun = f return } } } panic("function not found") }
// This tests that uses of existing vars on the LHS of an assignment // are Uses, not Defs; and also that the (illegal) use of a non-var on // the LHS of an assignment is a Use nonetheless. func TestIssue7827(t *testing.T) { const src = ` package p func _() { const w = 1 // defs w x, y := 2, 3 // defs x, y w, x, z := 4, 5, 6 // uses w, x, defs z; error: cannot assign to w _, _, _ = x, y, z // uses x, y, z } ` const want = `L3 defs func p._() L4 defs const w untyped int L5 defs var x int L5 defs var y int L6 defs var z int L6 uses const w untyped int L6 uses var x int L7 uses var x int L7 uses var y int L7 uses var z int` f, err := parser.ParseFile(fset, "", src, 0) if err != nil { t.Fatal(err) } // don't abort at the first error conf := Config{Error: func(err error) { t.Log(err) }} defs := make(map[*ast.Ident]Object) uses := make(map[*ast.Ident]Object) _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Defs: defs, Uses: uses}) if s := fmt.Sprint(err); !strings.HasSuffix(s, "cannot assign to w") { t.Errorf("Check: unexpected error: %s", s) } var facts []string for id, obj := range defs { if obj != nil { fact := fmt.Sprintf("L%d defs %s", fset.Position(id.Pos()).Line, obj) facts = append(facts, fact) } } for id, obj := range uses { fact := fmt.Sprintf("L%d uses %s", fset.Position(id.Pos()).Line, obj) facts = append(facts, fact) } sort.Strings(facts) got := strings.Join(facts, "\n") if got != want { t.Errorf("Unexpected defs/uses\ngot:\n%s\nwant:\n%s", got, want) } }
// Verify that the printer doesn't crash if the AST contains BadXXX nodes. func TestBadNodes(t *testing.T) { const src = "package p\n(" const res = "package p\nBadDecl\n" f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err == nil { t.Error("expected illegal program") // error in test } var buf bytes.Buffer Fprint(&buf, fset, f) if buf.String() != res { t.Errorf("got %q, expected %q", buf.String(), res) } }
func TestIssue5770(t *testing.T) { src := `package p; type S struct{T}` f, err := parser.ParseFile(fset, "", src, 0) if err != nil { t.Fatal(err) } conf := Config{Importer: importer.Default([]*ast.File{f})} _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) // do not crash want := "undeclared name: T" if err == nil || !strings.Contains(err.Error(), want) { t.Errorf("got: %v; want: %s", err, want) } }
// findInterval parses input and returns the [start, end) positions of // the first occurrence of substr in input. f==nil indicates failure; // an error has already been reported in that case. // func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) { f, err := parser.ParseFile(fset, "<input>", input, 0) if err != nil { t.Errorf("parse error: %s", err) return } i := strings.Index(input, substr) if i < 0 { t.Errorf("%q is not a substring of input", substr) f = nil return } filePos := fset.File(f.Package) return f, filePos.Pos(i), filePos.Pos(i + len(substr)) }
func TestIssue5849(t *testing.T) { src := ` package p var ( s uint _ = uint8(8) _ = uint16(16) << s _ = uint32(32 << s) _ = uint64(64 << s + s) _ = (interface{})("foo") )` f, err := parser.ParseFile(fset, "", src, 0) if err != nil { t.Fatal(err) } var conf Config types := make(map[ast.Expr]TypeAndValue) _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Types: types}) if err != nil { t.Fatal(err) } for x, tv := range types { var want Type switch x := x.(type) { case *ast.BasicLit: switch x.Value { case `8`: want = Typ[Uint8] case `16`: want = Typ[Uint16] case `32`: want = Typ[Uint32] case `64`: want = Typ[Uint] // because of "+ s", s is of type uint case `"foo"`: want = Typ[String] } } if want != nil && !Identical(tv.Type, want) { t.Errorf("got %s; want %s", tv.Type, want) } } }
// testComment verifies that f can be parsed again after printing it // with its first comment set to comment at any possible source offset. func testComment(t *testing.T, f *ast.File, srclen int, comment *ast.Comment) { f.Comments[0].List[0] = comment var buf bytes.Buffer for offs := 0; offs <= srclen; offs++ { buf.Reset() // Printing f should result in a correct program no // matter what the (incorrect) comment position is. if err := Fprint(&buf, fset, f); err != nil { t.Error(err) } if _, err := parser.ParseFile(fset, "", buf.Bytes(), 0); err != nil { t.Fatalf("incorrect program for pos = %d:\n%s", comment.Slash, buf.String()) } // Position information is just an offset. // Move comment one byte down in the source. comment.Slash++ } }
// ExampleMethodSet prints the method sets of various types. func ExampleMethodSet() { // Parse a single source file. const input = ` package temperature import "fmt" type Celsius float64 func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } func (c *Celsius) SetF(f float64) { *c = Celsius(f - 32 / 9 * 5) } ` fset := token.NewFileSet() f, err := parser.ParseFile(fset, "celsius.go", input, 0) if err != nil { log.Fatal(err) } // Type-check a package consisting of this file. // Type information for the imported packages // comes from $GOROOT/pkg/$GOOS_$GOOARCH/fmt.a. conf := types.Config{Importer: importer.Default([]*ast.File{f})} pkg, err := conf.Check("temperature", fset, []*ast.File{f}, nil) if err != nil { log.Fatal(err) } // Print the method sets of Celsius and *Celsius. celsius := pkg.Scope().Lookup("Celsius").Type() for _, t := range []types.Type{celsius, types.NewPointer(celsius)} { fmt.Printf("Method set of %s:\n", t) mset := types.NewMethodSet(t) for i := 0; i < mset.Len(); i++ { fmt.Println(mset.At(i)) } fmt.Println() } // Output: // Method set of temperature.Celsius: // method (temperature.Celsius) String() string // // Method set of *temperature.Celsius: // method (*temperature.Celsius) SetF(f float64) // method (*temperature.Celsius) String() string }
func TestDeclLists(t *testing.T) { for _, src := range decls { file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments) if err != nil { panic(err) // error in test } var buf bytes.Buffer err = Fprint(&buf, fset, file.Decls) // only print declarations if err != nil { panic(err) // error in test } out := buf.String() if out != src { t.Errorf("\ngot : %q\nwant: %q\n", out, src) } } }
func TestStmtLists(t *testing.T) { for _, src := range stmts { file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments) if err != nil { panic(err) // error in test } var buf bytes.Buffer err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List) // only print statements if err != nil { panic(err) // error in test } out := buf.String() if out != src { t.Errorf("\ngot : %q\nwant: %q\n", out, src) } } }
func TestNode(t *testing.T) { src, err := ioutil.ReadFile(testfile) if err != nil { t.Fatal(err) } fset := token.NewFileSet() file, err := parser.ParseFile(fset, testfile, src, parser.ParseComments) if err != nil { t.Fatal(err) } var buf bytes.Buffer if err = Node(&buf, fset, file); err != nil { t.Fatal("Node failed:", err) } diff(t, buf.Bytes(), src) }
func TestImports(t *testing.T) { fset := token.NewFileSet() for _, test := range importsTests { f, err := parser.ParseFile(fset, "test.go", test.in, 0) if err != nil { t.Errorf("%s: %v", test.name, err) continue } var got [][]string for _, group := range Imports(fset, f) { var b []string for _, spec := range group { b = append(b, unquote(spec.Path.Value)) } got = append(got, b) } if !reflect.DeepEqual(got, test.want) { t.Errorf("Imports(%s)=%v, want %v", test.name, got, test.want) } } }
func parseFiles(t *testing.T, filenames []string) ([]*ast.File, []error) { var files []*ast.File var errlist []error for _, filename := range filenames { file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) if file == nil { t.Fatalf("%s: %s", filename, err) } files = append(files, file) if err != nil { if list, _ := err.(scanner.ErrorList); len(list) > 0 { for _, err := range list { errlist = append(errlist, err) } } else { errlist = append(errlist, err) } } } return files, errlist }
// cannot initialize in init because (printer) Fprint launches goroutines. func initialize() { const filename = "testdata/parser.go" src, err := ioutil.ReadFile(filename) if err != nil { log.Fatalf("%s", err) } file, err := parser.ParseFile(fset, filename, src, parser.ParseComments) if err != nil { log.Fatalf("%s", err) } var buf bytes.Buffer testprint(&buf, file) if !bytes.Equal(buf.Bytes(), src) { log.Fatalf("print error: %s not idempotent", filename) } testfile = file }
// This example demonstrates how to inspect the AST of a Go program. func ExampleInspect() { // src is the input for which we want to inspect the AST. src := ` package p const c = 1.0 var X = f(3.14)*2 + c ` // Create the AST by parsing src. fset := token.NewFileSet() // positions are relative to fset f, err := parser.ParseFile(fset, "src.go", src, 0) if err != nil { panic(err) } // Inspect the AST and print all identifiers and literals. ast.Inspect(f, func(n ast.Node) bool { var s string switch x := n.(type) { case *ast.BasicLit: s = x.Value case *ast.Ident: s = x.Name } if s != "" { fmt.Printf("%s:\t%s\n", fset.Position(n.Pos()), s) } return true }) // output: // src.go:2:9: p // src.go:3:7: c // src.go:3:11: 1.0 // src.go:4:5: X // src.go:4:9: f // src.go:4:11: 3.14 // src.go:4:17: 2 // src.go:4:21: c }