go/internal/lsp/source/inlay_hint.go

138 lines
3.4 KiB
Go

// Copyright 2022 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 source
import (
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/lsppos"
"golang.org/x/tools/internal/lsp/protocol"
)
const (
maxLabelLength = 28
)
func InlayHint(ctx context.Context, snapshot Snapshot, fh FileHandle, _ protocol.Range) ([]protocol.InlayHint, error) {
ctx, done := event.Start(ctx, "source.InlayHint")
defer done()
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
if err != nil {
return nil, fmt.Errorf("getting file for InlayHint: %w", err)
}
tmap := lsppos.NewTokenMapper(pgf.Src, pgf.Tok)
info := pkg.GetTypesInfo()
q := Qualifier(pgf.File, pkg.GetTypes(), info)
var hints []protocol.InlayHint
ast.Inspect(pgf.File, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.CallExpr:
hints = append(hints, parameterNames(n, tmap, info)...)
case *ast.AssignStmt:
hints = append(hints, assignVariableTypes(n, tmap, info, &q)...)
case *ast.RangeStmt:
hints = append(hints, rangeVariableTypes(n, tmap, info, &q)...)
}
return true
})
return hints, nil
}
func parameterNames(node *ast.CallExpr, tmap *lsppos.TokenMapper, info *types.Info) []protocol.InlayHint {
signature, ok := info.TypeOf(node.Fun).(*types.Signature)
if !ok {
return nil
}
var hints []protocol.InlayHint
for i, v := range node.Args {
start, ok := tmap.Position(v.Pos())
if !ok {
continue
}
params := signature.Params()
// When a function has variadic params, we skip args after
// params.Len().
if i > params.Len()-1 {
break
}
value := params.At(i).Name()
// param.Name is empty for built-ins like append
if value == "" {
continue
}
if signature.Variadic() && i == params.Len()-1 {
value = value + "..."
}
hints = append(hints, protocol.InlayHint{
Position: &start,
Label: buildLabel(value + ":"),
Kind: protocol.Parameter,
PaddingRight: true,
})
}
return hints
}
func assignVariableTypes(node *ast.AssignStmt, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint {
if node.Tok != token.DEFINE {
return nil
}
var hints []protocol.InlayHint
for _, v := range node.Lhs {
if h := variableType(v, tmap, info, q); h != nil {
hints = append(hints, *h)
}
}
return hints
}
func rangeVariableTypes(node *ast.RangeStmt, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint {
var hints []protocol.InlayHint
if h := variableType(node.Key, tmap, info, q); h != nil {
hints = append(hints, *h)
}
if h := variableType(node.Value, tmap, info, q); h != nil {
hints = append(hints, *h)
}
return hints
}
func variableType(e ast.Expr, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) *protocol.InlayHint {
typ := info.TypeOf(e)
if typ == nil {
return nil
}
end, ok := tmap.Position(e.End())
if !ok {
return nil
}
return &protocol.InlayHint{
Position: &end,
Label: buildLabel(types.TypeString(typ, *q)),
Kind: protocol.Type,
PaddingLeft: true,
}
}
func buildLabel(s string) []protocol.InlayHintLabelPart {
label := protocol.InlayHintLabelPart{
Value: s,
}
if len(s) > maxLabelLength {
label.Value = s[:maxLabelLength] + "..."
label.Tooltip = s
}
return []protocol.InlayHintLabelPart{label}
}