blob: 861de8386bfd6cf94e05ae1deb4c76961f342a9d [file] [log] [blame]
// Copyright 2019 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 cmd
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"go/types"
"os"
guru "golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/internal/lsp/cache"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/tool"
)
// A Definition is the result of a 'definition' query.
type Definition struct {
Location Location `json:"location"` // location of the definition
Description string `json:"description"` // description of the denoted object
}
// This constant is printed in the help, and then used in a test to verify the
// help is still valid.
// It should be the byte offset in this file of the "Set" in "flag.FlagSet" from
// the DetailedHelp method below.
const exampleOffset = 1277
// definition implements the definition noun for the query command.
type definition struct {
query *query
}
func (d *definition) Name() string { return "definition" }
func (d *definition) Usage() string { return "<position>" }
func (d *definition) ShortHelp() string { return "show declaration of selected identifier" }
func (d *definition) DetailedHelp(f *flag.FlagSet) {
fmt.Fprintf(f.Output(), `
Example: show the definition of the identifier at syntax at offset %[1]v in this file (flag.FlagSet):
$ gopls definition internal/lsp/cmd/definition.go:#%[1]v
gopls definition flags are:
`, exampleOffset)
f.PrintDefaults()
}
// Run performs the definition query as specified by args and prints the
// results to stdout.
func (d *definition) Run(ctx context.Context, args ...string) error {
if len(args) != 1 {
return tool.CommandLineErrorf("definition expects 1 argument")
}
view := cache.NewView(&d.query.app.Config)
from, err := parseLocation(args[0])
if err != nil {
return err
}
f, err := view.GetFile(ctx, source.ToURI(from.Filename))
if err != nil {
return err
}
tok, err := f.GetToken()
if err != nil {
return err
}
pos := tok.Pos(from.Start.Offset)
ident, err := source.Identifier(ctx, view, f, pos)
if err != nil {
return err
}
if ident == nil {
return fmt.Errorf("not an identifier")
}
var result interface{}
switch d.query.Emulate {
case "":
result, err = buildDefinition(view, ident)
case emulateGuru:
result, err = buildGuruDefinition(view, ident)
default:
return fmt.Errorf("unknown emulation for definition: %s", d.query.Emulate)
}
if err != nil {
return err
}
if d.query.JSON {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "\t")
return enc.Encode(result)
}
switch d := result.(type) {
case *Definition:
fmt.Printf("%v: defined here as %s", d.Location, d.Description)
case *guru.Definition:
fmt.Printf("%s: defined here as %s", d.ObjPos, d.Desc)
default:
return fmt.Errorf("no printer for type %T", result)
}
return nil
}
func buildDefinition(view source.View, ident *source.IdentifierInfo) (*Definition, error) {
content, err := ident.Hover(nil)
if err != nil {
return nil, err
}
return &Definition{
Location: newLocation(view.FileSet(), ident.Declaration.Range),
Description: content,
}, nil
}
func buildGuruDefinition(view source.View, ident *source.IdentifierInfo) (*guru.Definition, error) {
loc := newLocation(view.FileSet(), ident.Declaration.Range)
pkg, err := ident.File.GetPackage()
if err != nil {
return nil, err
}
// guru does not support ranges
loc.End = loc.Start
// Behavior that attempts to match the expected output for guru. For an example
// of the format, see the associated definition tests.
buf := &bytes.Buffer{}
q := types.RelativeTo(pkg.Types)
qualifyName := ident.Declaration.Object.Pkg() != pkg.Types
name := ident.Name
var suffix interface{}
switch obj := ident.Declaration.Object.(type) {
case *types.TypeName:
fmt.Fprint(buf, "type")
case *types.Var:
if obj.IsField() {
qualifyName = false
fmt.Fprint(buf, "field")
suffix = obj.Type()
} else {
fmt.Fprint(buf, "var")
}
case *types.Func:
fmt.Fprint(buf, "func")
typ := obj.Type()
if obj.Type() != nil {
if sig, ok := typ.(*types.Signature); ok {
buf := &bytes.Buffer{}
if recv := sig.Recv(); recv != nil {
if named, ok := recv.Type().(*types.Named); ok {
fmt.Fprintf(buf, "(%s).%s", named.Obj().Name(), name)
}
}
if buf.Len() == 0 {
buf.WriteString(name)
}
types.WriteSignature(buf, sig, q)
name = buf.String()
}
}
default:
fmt.Fprintf(buf, "unknown [%T]", obj)
}
fmt.Fprint(buf, " ")
if qualifyName {
fmt.Fprintf(buf, "%s.", ident.Declaration.Object.Pkg().Path())
}
fmt.Fprint(buf, name)
if suffix != nil {
fmt.Fprint(buf, " ")
fmt.Fprint(buf, suffix)
}
return &guru.Definition{
ObjPos: fmt.Sprint(loc),
Desc: buf.String(),
}, nil
}