// fastQueryPos parses the position string and returns a queryPos. // It parses only a single file and does not run the type checker. func fastQueryPos(ctxt *build.Context, pos string) (*queryPos, error) { filename, startOffset, endOffset, err := parsePos(pos) if err != nil { return nil, err } // Parse the file, opening it the file via the build.Context // so that we observe the effects of the -modified flag. fset := token.NewFileSet() cwd, _ := os.Getwd() f, err := buildutil.ParseFile(fset, ctxt, nil, cwd, filename, parser.Mode(0)) // ParseFile usually returns a partial file along with an error. // Only fail if there is no file. if f == nil { return nil, err } if !f.Pos().IsValid() { return nil, fmt.Errorf("%s is not a Go source file", filename) } start, end, err := fileOffsetToPos(fset.File(f.Pos()), startOffset, endOffset) if err != nil { return nil, err } path, exact := astutil.PathEnclosingInterval(f, start, end) if path == nil { return nil, fmt.Errorf("no syntax here") } return &queryPos{fset, start, end, path, exact, nil}, nil }
// findPackageMember returns the type and position of the declaration of // pkg.member by loading and parsing the files of that package. // srcdir is the directory in which the import appears. func findPackageMember(ctxt *build.Context, fset *token.FileSet, srcdir, pkg, member string) (token.Token, token.Pos, error) { bp, err := ctxt.Import(pkg, srcdir, 0) if err != nil { return 0, token.NoPos, err // no files for package } // TODO(adonovan): opt: parallelize. for _, fname := range bp.GoFiles { filename := filepath.Join(bp.Dir, fname) // Parse the file, opening it the file via the build.Context // so that we observe the effects of the -modified flag. f, _ := buildutil.ParseFile(fset, ctxt, nil, ".", filename, parser.Mode(0)) if f == nil { continue } // Find a package-level decl called 'member'. for _, decl := range f.Decls { switch decl := decl.(type) { case *ast.GenDecl: for _, spec := range decl.Specs { switch spec := spec.(type) { case *ast.ValueSpec: // const or var for _, id := range spec.Names { if id.Name == member { return decl.Tok, id.Pos(), nil } } case *ast.TypeSpec: if spec.Name.Name == member { return token.TYPE, spec.Name.Pos(), nil } case *ast.AliasSpec: if spec.Name.Name == member { return decl.Tok, spec.Name.Pos(), nil } } } case *ast.FuncDecl: if decl.Recv == nil && decl.Name.Name == member { return token.FUNC, decl.Name.Pos(), nil } } } } return 0, token.NoPos, fmt.Errorf("couldn't find declaration of %s in %q", member, pkg) }
// parseOffsetFlag interprets the "-offset" flag value as a renaming specification. func parseOffsetFlag(ctxt *build.Context, offsetFlag string) (*spec, error) { var spec spec // Validate -offset, e.g. file.go:#123 parts := strings.Split(offsetFlag, ":#") if len(parts) != 2 { return nil, fmt.Errorf("-offset %q: invalid offset specification", offsetFlag) } spec.filename = parts[0] if !buildutil.FileExists(ctxt, spec.filename) { return nil, fmt.Errorf("no such file: %s", spec.filename) } bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename) if err != nil { return nil, err } spec.pkg = bp.ImportPath for _, r := range parts[1] { if !isDigit(r) { return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag) } } spec.offset, err = strconv.Atoi(parts[1]) if err != nil { return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag) } // Parse the file and check there's an identifier at that offset. fset := token.NewFileSet() f, err := buildutil.ParseFile(fset, ctxt, nil, wd, spec.filename, parser.ParseComments) if err != nil { return nil, fmt.Errorf("-offset %q: cannot parse file: %s", offsetFlag, err) } id := identAtOffset(fset, f, spec.offset) if id == nil { return nil, fmt.Errorf("-offset %q: no identifier at this position", offsetFlag) } spec.fromName = id.Name return &spec, nil }