// ParseQueryPos parses the source query position pos and returns the // AST node of the loaded program lprog that it identifies. // If needExact, it must identify a single AST subtree; // this is appropriate for queries that allow fairly arbitrary syntax, // e.g. "describe". // func parseQueryPos(lprog *loader.Program, pos string, needExact bool) (*queryPos, error) { filename, startOffset, endOffset, err := parsePos(pos) if err != nil { return nil, err } // Find the named file among those in the loaded program. var file *token.File lprog.Fset.Iterate(func(f *token.File) bool { if sameFile(filename, f.Name()) { file = f return false // done } return true // continue }) if file == nil { return nil, fmt.Errorf("file %s not found in loaded program", filename) } start, end, err := fileOffsetToPos(file, startOffset, endOffset) if err != nil { return nil, err } info, path, exact := lprog.PathEnclosingInterval(start, end) if path == nil { return nil, fmt.Errorf("no syntax here") } if needExact && !exact { return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) } return &queryPos{lprog.Fset, start, end, path, exact, info}, nil }
func getDeclareStructOrInterface(prog *loader.Program, v *types.Var) string { // From x/tools/refactor/rename/check.go(checkStructField)#L288 // go/types offers no easy way to get from a field (or interface // method) to its declaring struct (or interface), so we must // ascend the AST. _, path, _ := prog.PathEnclosingInterval(v.Pos(), v.Pos()) // path matches this pattern: // [Ident SelectorExpr? StarExpr? Field FieldList StructType ParenExpr* ... File] // Ascend to FieldList. var i int for { if _, ok := path[i].(*ast.FieldList); ok { break } i++ } i++ _ = path[i].(*ast.StructType) i++ for { if _, ok := path[i].(*ast.ParenExpr); !ok { break } i++ } if spec, ok := path[i].(*ast.TypeSpec); ok { return spec.Name.String() } return "" }
// ParseQueryPos parses the source query position pos. // If needExact, it must identify a single AST subtree; // this is appropriate for queries that allow fairly arbitrary syntax, // e.g. "describe". // func ParseQueryPos(iprog *loader.Program, posFlag string, needExact bool) (*QueryPos, error) { filename, startOffset, endOffset, err := parsePosFlag(posFlag) if err != nil { return nil, err } start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset) if err != nil { return nil, err } info, path, exact := iprog.PathEnclosingInterval(start, end) if path == nil { return nil, fmt.Errorf("no syntax here") } if needExact && !exact { return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) } return &QueryPos{iprog.Fset, start, end, path, exact, info}, nil }
// DocForPos attempts to get the documentation for an item given a filename and byte offset. func DocForPos(ctxt *build.Context, lprog *loader.Program, filename string, offset int64) (*Doc, error) { tokFile := FileFromProgram(lprog, filename) if tokFile == nil { return nil, fmt.Errorf("gogetdoc: couldn't find %s in program", filename) } offPos := tokFile.Pos(int(offset)) pkgInfo, nodes, _ := lprog.PathEnclosingInterval(offPos, offPos) for _, node := range nodes { switch i := node.(type) { case *ast.ImportSpec: abs, err := filepath.Abs(filename) if err != nil { return nil, err } return PackageDoc(ctxt, lprog.Fset, filepath.Dir(abs), ImportPath(i)) case *ast.Ident: // if we can't find the object denoted by the identifier, keep searching) if obj := pkgInfo.ObjectOf(i); obj == nil { continue } return IdentDoc(ctxt, i, pkgInfo, lprog) case *ast.File: if i.Doc != nil { return &Doc{ Doc: i.Doc.Text(), }, nil } for _, f := range pkgInfo.Files { if f.Doc != nil { return &Doc{ Doc: f.Doc.Text(), }, nil } } } } return nil, errors.New("gogetdoc: no documentation found") }
func findFromObjectsInFile(iprog *loader.Program, spec *spec) ([]types.Object, error) { var fromObjects []types.Object for _, info := range iprog.AllPackages { // restrict to specified filename // NB: under certain proprietary build systems, a given // filename may appear in multiple packages. for _, f := range info.Files { thisFile := iprog.Fset.File(f.Pos()) if !sameFile(thisFile.Name(), spec.filename) { continue } // This package contains the query file. if spec.offset != 0 { // Search for a specific ident by file/offset. id := identAtOffset(iprog.Fset, f, spec.offset) if id == nil { // can't happen? return nil, fmt.Errorf("identifier not found") } obj := info.Uses[id] if obj == nil { obj = info.Defs[id] if obj == nil { // Ident without Object. // Package clause? pos := thisFile.Pos(spec.offset) _, path, _ := iprog.PathEnclosingInterval(pos, pos) if len(path) == 2 { // [Ident File] // TODO(adonovan): support this case. return nil, fmt.Errorf("cannot rename %q: renaming package clauses is not yet supported", path[1].(*ast.File).Name.Name) } // Implicit y in "switch y := x.(type) {"? if obj := typeSwitchVar(&info.Info, path); obj != nil { return []types.Object{obj}, nil } // Probably a type error. return nil, fmt.Errorf("cannot find object for %q", id.Name) } } if obj.Pkg() == nil { return nil, fmt.Errorf("cannot rename predeclared identifiers (%s)", obj) } fromObjects = append(fromObjects, obj) } else { // do a package-wide query objects, err := findObjects(info, spec) if err != nil { return nil, err } // filter results: only objects defined in thisFile var filtered []types.Object for _, obj := range objects { if iprog.Fset.File(obj.Pos()) == thisFile { filtered = append(filtered, obj) } } if len(filtered) == 0 { return nil, fmt.Errorf("no object %q declared in file %s", spec.fromName, spec.filename) } else if len(filtered) > 1 { return nil, ambiguityError(iprog.Fset, filtered) } fromObjects = append(fromObjects, filtered[0]) } break } } if len(fromObjects) == 0 { // can't happen? return nil, fmt.Errorf("file %s was not part of the loaded program", spec.filename) } return fromObjects, nil }
// IdentDoc attempts to get the documentation for a *ast.Ident. func IdentDoc(ctxt *build.Context, id *ast.Ident, info *loader.PackageInfo, prog *loader.Program) (*Doc, error) { // get definition of identifier obj := info.ObjectOf(id) var pos string if p := obj.Pos(); p.IsValid() { pos = prog.Fset.Position(p).String() } pkgPath := "" if obj.Pkg() != nil { pkgPath = obj.Pkg().Path() } // handle packages imported under a different name if p, ok := obj.(*types.PkgName); ok { return PackageDoc(ctxt, prog.Fset, "", p.Imported().Path()) // SRCDIR TODO TODO } _, nodes, _ := prog.PathEnclosingInterval(obj.Pos(), obj.Pos()) if len(nodes) == 0 { // special case - builtins doc, decl := findInBuiltin(obj.Name(), obj, prog) if doc != "" { return &Doc{ Import: "builtin", Name: obj.Name(), Doc: doc, Decl: decl, Pos: pos, }, nil } return nil, fmt.Errorf("No documentation found for %s", obj.Name()) } var doc *Doc for _, node := range nodes { switch node.(type) { case *ast.FuncDecl, *ast.GenDecl, *ast.Field: default: continue } doc = &Doc{ Import: stripVendorFromImportPath(pkgPath), Name: obj.Name(), Decl: formatNode(node, obj, prog), Pos: pos, } break } if doc == nil { // This shouldn't happen return nil, fmt.Errorf("No documentation found for %s", obj.Name()) } for i, node := range nodes { //fmt.Printf("for %s: found %T\n%#v\n", id.Name, node, node) switch n := node.(type) { case *ast.FuncDecl: doc.Doc = n.Doc.Text() return doc, nil case *ast.GenDecl: constValue := "" if c, ok := obj.(*types.Const); ok { constValue = c.Val().ExactString() } if len(n.Specs) > 0 { switch n.Specs[0].(type) { case *ast.TypeSpec: spec := findTypeSpec(n, nodes[i-1].Pos()) if spec.Doc != nil { doc.Doc = spec.Doc.Text() } else if spec.Comment != nil { doc.Doc = spec.Comment.Text() } case *ast.ValueSpec: spec := findVarSpec(n, nodes[i-1].Pos()) if spec.Doc != nil { doc.Doc = spec.Doc.Text() } else if spec.Comment != nil { doc.Doc = spec.Comment.Text() } } } // if we didn't find doc for the spec, check the overall GenDecl if doc.Doc == "" && n.Doc != nil { doc.Doc = n.Doc.Text() } if constValue != "" { doc.Doc += fmt.Sprintf("\nConstant Value: %s", constValue) } return doc, nil case *ast.Field: // check the doc first, if not present, then look for a comment if n.Doc != nil { doc.Doc = n.Doc.Text() return doc, nil } else if n.Comment != nil { doc.Doc = n.Comment.Text() return doc, nil } } } return doc, nil }
// Returns true if unused func checkObj(expr *ast.Ident, object types.Object, prog *loader.Program, ssaprog *ssa.Program, fset *token.FileSet) (unused bool) { if _, ok := object.(*types.Var); !ok { if debug { fmt.Println("Skipping object", object) } return false } pkg, node, _ := prog.PathEnclosingInterval(expr.Pos(), expr.End()) spkg := ssaprog.Package(pkg.Pkg) f := ssa.EnclosingFunction(spkg, node) if f == nil { if debug { fmt.Printf("Unknown function %v %v %v %v\n", fset.Position(expr.Pos()), object, pkg, prog) } return false } value, _ := f.ValueForExpr(expr) // Unwrap unops and grab the value inside if v, ok := value.(*ssa.UnOp); ok { if debug { fmt.Println("Unwrapping unop") } value = v.X } if debug { fmt.Printf("%v %v: %v %#v\n", fset.Position(expr.Pos()), expr, object, value) } if _, ok := value.(*ssa.Global); ok { if debug { fmt.Printf(" is global\n") } return false } if value == nil { if debug { fmt.Println("Value is nil", object) } return false } refs := value.Referrers() if refs == nil { if debug { fmt.Println("Referrers is nil", object) } return false } if debug { fmt.Printf(" (refs) %v\n", refs) } hasRef := false for _, r := range *refs { _, ok := r.(*ssa.DebugRef) hasRef = hasRef || !ok if debug && !ok { fmt.Printf("%v %v: %v %v\n", fset.Position(expr.Pos()), expr, object, r) } } if !hasRef { unused = true } return unused }