1 // Copyright 2019 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
17 "golang.org/x/tools/internal/event"
18 "golang.org/x/tools/internal/lsp/protocol"
19 errors "golang.org/x/xerrors"
22 type HoverInformation struct {
23 // Signature is the symbol's signature.
24 Signature string `json:"signature"`
26 // SingleLine is a single line describing the symbol.
27 // This is recommended only for use in clients that show a single line for hover.
28 SingleLine string `json:"singleLine"`
30 // Synopsis is a single sentence synopsis of the symbol's documentation.
31 Synopsis string `json:"synopsis"`
33 // FullDocumentation is the symbol's full documentation.
34 FullDocumentation string `json:"fullDocumentation"`
36 // ImportPath is the import path for the package containing the given symbol.
39 // Link is the pkg.go.dev anchor for the given symbol.
40 // For example, "go/ast#Node".
41 Link string `json:"link"`
43 // SymbolName is the types.Object.Name for the given symbol.
47 comment *ast.CommentGroup
49 // isTypeName reports whether the identifier is a type name. In such cases,
50 // the hover has the prefix "type ".
54 func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
55 ident, err := Identifier(ctx, snapshot, fh, position)
59 h, err := HoverIdentifier(ctx, ident)
63 rng, err := ident.Range()
67 // See golang/go#36998: don't link to modules matching GOPRIVATE.
68 if snapshot.View().IsGoPrivatePath(h.ImportPath) {
71 hover, err := FormatHover(h, snapshot.View().Options())
75 return &protocol.Hover{
76 Contents: protocol.MarkupContent{
77 Kind: snapshot.View().Options().PreferredContentFormat,
84 func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverInformation, error) {
85 ctx, done := event.Start(ctx, "source.Hover")
88 fset := i.Snapshot.FileSet()
89 h, err := HoverInfo(ctx, i.pkg, i.Declaration.obj, i.Declaration.node)
93 // Determine the symbol's signature.
94 switch x := h.source.(type) {
97 if err := format.Node(&b, fset, x); err != nil {
100 h.Signature = b.String()
102 h.Signature = "type " + h.Signature
105 // If the variable is implicitly declared in a type switch, we need to
106 // manually generate its object string.
107 if typ := i.Declaration.typeSwitchImplicit; typ != nil {
108 if v, ok := x.(*types.Var); ok {
109 h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(typ, i.qf))
113 h.Signature = objectString(x, i.qf)
115 if obj := i.Declaration.obj; obj != nil {
116 h.SingleLine = objectString(obj, i.qf)
118 h.ImportPath, h.Link, h.SymbolName = pathLinkAndSymbolName(i)
123 func pathLinkAndSymbolName(i *IdentifierInfo) (string, string, string) {
124 obj := i.Declaration.obj
128 switch obj := obj.(type) {
130 path := obj.Imported().Path()
132 if mod, version, ok := moduleAtVersion(path, i); ok {
133 link = strings.Replace(path, mod, mod+"@"+version, 1)
135 return path, link, obj.Name()
137 return "builtin", fmt.Sprintf("builtin#%s", obj.Name()), obj.Name()
139 // Check if the identifier is test-only (and is therefore not part of a
140 // package's API). This is true if the request originated in a test package,
141 // and if the declaration is also found in the same test package.
142 if i.pkg != nil && obj.Pkg() != nil && i.pkg.ForTest() != "" {
143 if _, err := i.pkg.File(i.Declaration.MappedRange[0].URI()); err == nil {
147 // Don't return links for other unexported types.
152 switch obj := obj.(type) {
154 // If the object is a field, and we have an associated selector
155 // composite literal, or struct, we can determine the link.
157 if named, ok := i.enclosing.(*types.Named); ok {
158 rTypeName = named.Obj().Name()
162 typ, ok := obj.Type().(*types.Signature)
166 if r := typ.Recv(); r != nil {
167 switch rtyp := Deref(r.Type()).(type) {
171 // If we have an unexported type, see if the enclosing type is
172 // exported (we may have an interface or struct we can link
173 // to). If not, don't show any link.
174 if !rtyp.Obj().Exported() {
175 if named, ok := i.enclosing.(*types.Named); ok && named.Obj().Exported() {
176 rTypeName = named.Obj().Name()
181 rTypeName = rtyp.Obj().Name()
186 path := obj.Pkg().Path()
188 if mod, version, ok := moduleAtVersion(path, i); ok {
189 link = strings.Replace(path, mod, mod+"@"+version, 1)
192 link = fmt.Sprintf("%s#%s.%s", link, rTypeName, obj.Name())
193 symbol := fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), rTypeName, obj.Name())
194 return path, link, symbol
196 // For most cases, the link is "package/path#symbol".
197 link = fmt.Sprintf("%s#%s", link, obj.Name())
198 symbolName := fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name())
199 return path, link, symbolName
202 func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) {
203 if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" {
206 impPkg, err := i.pkg.GetImport(path)
210 if impPkg.Version() == nil {
213 version, modpath := impPkg.Version().Version, impPkg.Version().Path
214 if modpath == "" || version == "" {
217 return modpath, version, true
220 // objectString is a wrapper around the types.ObjectString function.
221 // It handles adding more information to the object string.
222 func objectString(obj types.Object, qf types.Qualifier) string {
223 str := types.ObjectString(obj, qf)
224 switch obj := obj.(type) {
226 str = fmt.Sprintf("%s = %s", str, obj.Val())
231 // HoverInfo returns a HoverInformation struct for an ast node and its type
233 func HoverInfo(ctx context.Context, pkg Package, obj types.Object, node ast.Node) (*HoverInformation, error) {
234 var info *HoverInformation
236 switch node := node.(type) {
238 // The package declaration.
239 for _, f := range pkg.GetSyntax() {
241 info = &HoverInformation{comment: f.Doc}
244 case *ast.ImportSpec:
245 // Try to find the package documentation for an imported package.
246 if pkgName, ok := obj.(*types.PkgName); ok {
247 imp, err := pkg.GetImport(pkgName.Imported().Path())
251 // Assume that only one file will contain package documentation,
252 // so pick the first file that has a doc comment.
253 for _, file := range imp.GetSyntax() {
255 info = &HoverInformation{source: obj, comment: file.Doc}
260 info = &HoverInformation{source: node}
262 switch obj := obj.(type) {
263 case *types.TypeName, *types.Var, *types.Const, *types.Func:
265 info, err = formatGenDecl(node, obj, obj.Type())
269 _, info.isType = obj.(*types.TypeName)
272 if obj.Parent() == types.Universe {
273 if obj.Name() == "error" {
274 info = &HoverInformation{source: node}
276 info = &HoverInformation{source: node.Name} // comments not needed for builtins
282 info = &HoverInformation{source: obj, comment: node.Doc}
284 info = &HoverInformation{source: node.Type, comment: node.Doc}
289 info = &HoverInformation{source: obj}
292 if info.comment != nil {
293 info.FullDocumentation = info.comment.Text()
294 info.Synopsis = doc.Synopsis(info.FullDocumentation)
300 func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*HoverInformation, error) {
301 if _, ok := typ.(*types.Named); ok {
302 switch typ.Underlying().(type) {
303 case *types.Interface, *types.Struct:
304 return formatGenDecl(node, obj, typ.Underlying())
308 for _, s := range node.Specs {
309 if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() {
315 return nil, errors.Errorf("no spec for node %v at position %v", node, obj.Pos())
318 // If we have a field or method.
320 case *types.Var, *types.Const, *types.Func:
321 return formatVar(spec, obj, node), nil
324 switch spec := spec.(type) {
326 if len(node.Specs) > 1 {
327 // If multiple types are declared in the same block.
328 return &HoverInformation{source: spec.Type, comment: spec.Doc}, nil
330 return &HoverInformation{source: spec, comment: node.Doc}, nil
333 return &HoverInformation{source: spec, comment: spec.Doc}, nil
334 case *ast.ImportSpec:
335 return &HoverInformation{source: spec, comment: spec.Doc}, nil
337 return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec)
340 func formatVar(node ast.Spec, obj types.Object, decl *ast.GenDecl) *HoverInformation {
341 var fieldList *ast.FieldList
342 switch spec := node.(type) {
344 switch t := spec.Type.(type) {
345 case *ast.StructType:
347 case *ast.InterfaceType:
348 fieldList = t.Methods
356 comment = spec.Comment
358 return &HoverInformation{source: obj, comment: comment}
360 // If we have a struct or interface declaration,
361 // we need to match the object to the corresponding field or method.
362 if fieldList != nil {
363 for i := 0; i < len(fieldList.List); i++ {
364 field := fieldList.List[i]
365 if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
366 if field.Doc.Text() != "" {
367 return &HoverInformation{source: obj, comment: field.Doc}
369 return &HoverInformation{source: obj, comment: field.Comment}
373 return &HoverInformation{source: obj, comment: decl.Doc}
376 func FormatHover(h *HoverInformation, options *Options) (string, error) {
377 signature := h.Signature
378 if signature != "" && options.PreferredContentFormat == protocol.Markdown {
379 signature = fmt.Sprintf("```go\n%s\n```", signature)
382 switch options.HoverKind {
384 return h.SingleLine, nil
385 case NoDocumentation:
386 return signature, nil
388 b, err := json.Marshal(h)
392 return string(b), nil
394 link := formatLink(h, options)
395 switch options.HoverKind {
396 case SynopsisDocumentation:
397 doc := formatDoc(h.Synopsis, options)
398 return formatHover(options, signature, link, doc), nil
399 case FullDocumentation:
400 doc := formatDoc(h.FullDocumentation, options)
401 return formatHover(options, signature, link, doc), nil
403 return "", errors.Errorf("no hover for %v", h.source)
406 func formatLink(h *HoverInformation, options *Options) string {
407 if !options.LinksInHover || options.LinkTarget == "" || h.Link == "" {
410 plainLink := fmt.Sprintf("https://%s/%s", options.LinkTarget, h.Link)
411 switch options.PreferredContentFormat {
412 case protocol.Markdown:
413 return fmt.Sprintf("[`%s` on %s](%s)", h.SymbolName, options.LinkTarget, plainLink)
414 case protocol.PlainText:
421 func formatDoc(doc string, options *Options) string {
422 if options.PreferredContentFormat == protocol.Markdown {
423 return CommentToMarkdown(doc)
428 func formatHover(options *Options, x ...string) string {
429 var b strings.Builder
430 for i, el := range x {
434 // Don't write out final newline.
438 // If any elements of the remainder of the list are non-empty,
440 if anyNonEmpty(x[i+1:]) {
441 if options.PreferredContentFormat == protocol.Markdown {
442 b.WriteString("\n\n")
452 func anyNonEmpty(x []string) bool {
453 for _, el := range x {