mirror of https://github.com/golang/go.git
962 lines
27 KiB
Go
962 lines
27 KiB
Go
// Copyright 2020 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 lsp
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"log"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/tools/internal/event"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/safetoken"
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
"golang.org/x/tools/internal/lsp/template"
|
|
"golang.org/x/tools/internal/typeparams"
|
|
)
|
|
|
|
// The LSP says that errors for the semantic token requests should only be returned
|
|
// for exceptions (a word not otherwise defined). This code treats a too-large file
|
|
// as an exception. On parse errors, the code does what it can.
|
|
|
|
// reject full semantic token requests for large files
|
|
const maxFullFileSize int = 100000
|
|
|
|
// to control comprehensive logging of decisions (gopls semtok foo.go > /dev/null shows log output)
|
|
// semDebug should NEVER be true in checked-in code
|
|
const semDebug = false
|
|
|
|
func (s *Server) semanticTokensFull(ctx context.Context, p *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) {
|
|
ret, err := s.computeSemanticTokens(ctx, p.TextDocument, nil)
|
|
return ret, err
|
|
}
|
|
|
|
func (s *Server) semanticTokensFullDelta(ctx context.Context, p *protocol.SemanticTokensDeltaParams) (interface{}, error) {
|
|
return nil, fmt.Errorf("implement SemanticTokensFullDelta")
|
|
}
|
|
|
|
func (s *Server) semanticTokensRange(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) {
|
|
ret, err := s.computeSemanticTokens(ctx, p.TextDocument, &p.Range)
|
|
return ret, err
|
|
}
|
|
|
|
func (s *Server) semanticTokensRefresh(ctx context.Context) error {
|
|
// in the code, but not in the protocol spec
|
|
return fmt.Errorf("implement SemanticTokensRefresh")
|
|
}
|
|
|
|
func (s *Server) computeSemanticTokens(ctx context.Context, td protocol.TextDocumentIdentifier, rng *protocol.Range) (*protocol.SemanticTokens, error) {
|
|
ans := protocol.SemanticTokens{
|
|
Data: []uint32{},
|
|
}
|
|
snapshot, fh, ok, release, err := s.beginFileRequest(ctx, td.URI, source.UnknownKind)
|
|
defer release()
|
|
if !ok {
|
|
return nil, err
|
|
}
|
|
vv := snapshot.View()
|
|
if !vv.Options().SemanticTokens {
|
|
// return an error, so if the option changes
|
|
// the client won't remember the wrong answer
|
|
return nil, fmt.Errorf("semantictokens are disabled")
|
|
}
|
|
kind := snapshot.View().FileKind(fh)
|
|
if kind == source.Tmpl {
|
|
// this is a little cumbersome to avoid both exporting 'encoded' and its methods
|
|
// and to avoid import cycles
|
|
e := &encoded{
|
|
ctx: ctx,
|
|
rng: rng,
|
|
tokTypes: s.session.Options().SemanticTypes,
|
|
tokMods: s.session.Options().SemanticMods,
|
|
}
|
|
add := func(line, start uint32, len uint32) {
|
|
e.add(line, start, len, tokMacro, nil)
|
|
}
|
|
data := func() []uint32 {
|
|
return e.Data()
|
|
}
|
|
return template.SemanticTokens(ctx, snapshot, fh.URI(), add, data)
|
|
}
|
|
if kind != source.Go {
|
|
return nil, nil
|
|
}
|
|
pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckFull, source.WidestPackage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pgf, err := pkg.File(fh.URI())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// ignore pgf.ParseErr. Do what we can.
|
|
if rng == nil && len(pgf.Src) > maxFullFileSize {
|
|
err := fmt.Errorf("semantic tokens: file %s too large for full (%d>%d)",
|
|
fh.URI().Filename(), len(pgf.Src), maxFullFileSize)
|
|
return nil, err
|
|
}
|
|
e := &encoded{
|
|
ctx: ctx,
|
|
pgf: pgf,
|
|
rng: rng,
|
|
ti: pkg.GetTypesInfo(),
|
|
pkg: pkg,
|
|
fset: snapshot.FileSet(),
|
|
tokTypes: s.session.Options().SemanticTypes,
|
|
tokMods: s.session.Options().SemanticMods,
|
|
}
|
|
if err := e.init(); err != nil {
|
|
// e.init should never return an error, unless there's some
|
|
// seemingly impossible race condition
|
|
return nil, err
|
|
}
|
|
e.semantics()
|
|
ans.Data = e.Data()
|
|
// For delta requests, but we've never seen any.
|
|
ans.ResultID = fmt.Sprintf("%v", time.Now())
|
|
return &ans, nil
|
|
}
|
|
|
|
func (e *encoded) semantics() {
|
|
f := e.pgf.File
|
|
// may not be in range, but harmless
|
|
e.token(f.Package, len("package"), tokKeyword, nil)
|
|
e.token(f.Name.NamePos, len(f.Name.Name), tokNamespace, nil)
|
|
inspect := func(n ast.Node) bool {
|
|
return e.inspector(n)
|
|
}
|
|
for _, d := range f.Decls {
|
|
// only look at the decls that overlap the range
|
|
start, end := d.Pos(), d.End()
|
|
if end <= e.start || start >= e.end {
|
|
continue
|
|
}
|
|
ast.Inspect(d, inspect)
|
|
}
|
|
for _, cg := range f.Comments {
|
|
for _, c := range cg.List {
|
|
if !strings.Contains(c.Text, "\n") {
|
|
e.token(c.Pos(), len(c.Text), tokComment, nil)
|
|
continue
|
|
}
|
|
e.multiline(c.Pos(), c.End(), c.Text, tokComment)
|
|
}
|
|
}
|
|
}
|
|
|
|
type tokenType string
|
|
|
|
const (
|
|
tokNamespace tokenType = "namespace"
|
|
tokType tokenType = "type"
|
|
tokInterface tokenType = "interface"
|
|
tokTypeParam tokenType = "typeParameter"
|
|
tokParameter tokenType = "parameter"
|
|
tokVariable tokenType = "variable"
|
|
tokMethod tokenType = "method"
|
|
tokFunction tokenType = "function"
|
|
tokKeyword tokenType = "keyword"
|
|
tokComment tokenType = "comment"
|
|
tokString tokenType = "string"
|
|
tokNumber tokenType = "number"
|
|
tokOperator tokenType = "operator"
|
|
|
|
tokMacro tokenType = "macro" // for templates
|
|
)
|
|
|
|
func (e *encoded) token(start token.Pos, leng int, typ tokenType, mods []string) {
|
|
|
|
if !start.IsValid() {
|
|
// This is not worth reporting
|
|
return
|
|
}
|
|
if start >= e.end || start+token.Pos(leng) <= e.start {
|
|
return
|
|
}
|
|
// want a line and column from start (in LSP coordinates)
|
|
// [//line directives should be ignored]
|
|
rng := source.NewMappedRange(e.fset, e.pgf.Mapper, start, start+token.Pos(leng))
|
|
lspRange, err := rng.Range()
|
|
if err != nil {
|
|
// possibly a //line directive. TODO(pjw): fix this somehow
|
|
// "column mapper is for file...instead of..."
|
|
// "line is beyond end of file..."
|
|
// see line 116 of internal/span/token.go which uses Position not PositionFor
|
|
// (it is too verbose to print the error on every token. some other RPC will fail)
|
|
// event.Error(e.ctx, "failed to convert to range", err)
|
|
return
|
|
}
|
|
if lspRange.End.Line != lspRange.Start.Line {
|
|
// this happens if users are typing at the end of the file, but report nothing
|
|
return
|
|
}
|
|
// token is all on one line
|
|
length := lspRange.End.Character - lspRange.Start.Character
|
|
e.add(lspRange.Start.Line, lspRange.Start.Character, length, typ, mods)
|
|
}
|
|
|
|
func (e *encoded) add(line, start uint32, len uint32, tok tokenType, mod []string) {
|
|
x := semItem{line, start, len, tok, mod}
|
|
e.items = append(e.items, x)
|
|
}
|
|
|
|
// semItem represents a token found walking the parse tree
|
|
type semItem struct {
|
|
line, start uint32
|
|
len uint32
|
|
typeStr tokenType
|
|
mods []string
|
|
}
|
|
|
|
type encoded struct {
|
|
// the generated data
|
|
items []semItem
|
|
|
|
ctx context.Context
|
|
tokTypes, tokMods []string
|
|
pgf *source.ParsedGoFile
|
|
rng *protocol.Range
|
|
ti *types.Info
|
|
pkg source.Package
|
|
fset *token.FileSet
|
|
// allowed starting and ending token.Pos, set by init
|
|
// used to avoid looking at declarations not in range
|
|
start, end token.Pos
|
|
// path from the root of the parse tree, used for debugging
|
|
stack []ast.Node
|
|
}
|
|
|
|
// convert the stack to a string, for debugging
|
|
func (e *encoded) strStack() string {
|
|
msg := []string{"["}
|
|
for i := len(e.stack) - 1; i >= 0; i-- {
|
|
s := e.stack[i]
|
|
msg = append(msg, fmt.Sprintf("%T", s)[5:])
|
|
}
|
|
if len(e.stack) > 0 {
|
|
loc := e.stack[len(e.stack)-1].Pos()
|
|
if !safetoken.InRange(e.pgf.Tok, loc) {
|
|
msg = append(msg, fmt.Sprintf("invalid position %v for %s", loc, e.pgf.URI))
|
|
} else if safetoken.InRange(e.pgf.Tok, loc) {
|
|
add := e.pgf.Tok.PositionFor(loc, false)
|
|
nm := filepath.Base(add.Filename)
|
|
msg = append(msg, fmt.Sprintf("(%s:%d,col:%d)", nm, add.Line, add.Column))
|
|
} else {
|
|
msg = append(msg, fmt.Sprintf("(loc %d out of range)", loc))
|
|
}
|
|
}
|
|
msg = append(msg, "]")
|
|
return strings.Join(msg, " ")
|
|
}
|
|
|
|
// find the line in the source
|
|
func (e *encoded) srcLine(x ast.Node) string {
|
|
file := e.pgf.Tok
|
|
line := file.Line(x.Pos())
|
|
start, err := safetoken.Offset(file, file.LineStart(line))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
end := start
|
|
for ; end < len(e.pgf.Src) && e.pgf.Src[end] != '\n'; end++ {
|
|
|
|
}
|
|
ans := e.pgf.Src[start:end]
|
|
return string(ans)
|
|
}
|
|
|
|
func (e *encoded) inspector(n ast.Node) bool {
|
|
pop := func() {
|
|
e.stack = e.stack[:len(e.stack)-1]
|
|
}
|
|
if n == nil {
|
|
pop()
|
|
return true
|
|
}
|
|
e.stack = append(e.stack, n)
|
|
switch x := n.(type) {
|
|
case *ast.ArrayType:
|
|
case *ast.AssignStmt:
|
|
e.token(x.TokPos, len(x.Tok.String()), tokOperator, nil)
|
|
case *ast.BasicLit:
|
|
if strings.Contains(x.Value, "\n") {
|
|
// has to be a string
|
|
e.multiline(x.Pos(), x.End(), x.Value, tokString)
|
|
break
|
|
}
|
|
ln := len(x.Value)
|
|
what := tokNumber
|
|
if x.Kind == token.STRING {
|
|
what = tokString
|
|
if _, ok := e.stack[len(e.stack)-2].(*ast.Field); ok {
|
|
// struct tags (this is probably pointless, as the
|
|
// TextMate grammar will treat all the other comments the same)
|
|
what = tokComment
|
|
}
|
|
}
|
|
e.token(x.Pos(), ln, what, nil)
|
|
case *ast.BinaryExpr:
|
|
e.token(x.OpPos, len(x.Op.String()), tokOperator, nil)
|
|
case *ast.BlockStmt:
|
|
case *ast.BranchStmt:
|
|
e.token(x.TokPos, len(x.Tok.String()), tokKeyword, nil)
|
|
// There's no semantic encoding for labels
|
|
case *ast.CallExpr:
|
|
if x.Ellipsis != token.NoPos {
|
|
e.token(x.Ellipsis, len("..."), tokOperator, nil)
|
|
}
|
|
case *ast.CaseClause:
|
|
iam := "case"
|
|
if x.List == nil {
|
|
iam = "default"
|
|
}
|
|
e.token(x.Case, len(iam), tokKeyword, nil)
|
|
case *ast.ChanType:
|
|
// chan | chan <- | <- chan
|
|
switch {
|
|
case x.Arrow == token.NoPos:
|
|
e.token(x.Begin, len("chan"), tokKeyword, nil)
|
|
case x.Arrow == x.Begin:
|
|
e.token(x.Arrow, 2, tokOperator, nil)
|
|
pos := e.findKeyword("chan", x.Begin+2, x.Value.Pos())
|
|
e.token(pos, len("chan"), tokKeyword, nil)
|
|
case x.Arrow != x.Begin:
|
|
e.token(x.Begin, len("chan"), tokKeyword, nil)
|
|
e.token(x.Arrow, 2, tokOperator, nil)
|
|
}
|
|
case *ast.CommClause:
|
|
iam := len("case")
|
|
if x.Comm == nil {
|
|
iam = len("default")
|
|
}
|
|
e.token(x.Case, iam, tokKeyword, nil)
|
|
case *ast.CompositeLit:
|
|
case *ast.DeclStmt:
|
|
case *ast.DeferStmt:
|
|
e.token(x.Defer, len("defer"), tokKeyword, nil)
|
|
case *ast.Ellipsis:
|
|
e.token(x.Ellipsis, len("..."), tokOperator, nil)
|
|
case *ast.EmptyStmt:
|
|
case *ast.ExprStmt:
|
|
case *ast.Field:
|
|
case *ast.FieldList:
|
|
case *ast.ForStmt:
|
|
e.token(x.For, len("for"), tokKeyword, nil)
|
|
case *ast.FuncDecl:
|
|
case *ast.FuncLit:
|
|
case *ast.FuncType:
|
|
if x.Func != token.NoPos {
|
|
e.token(x.Func, len("func"), tokKeyword, nil)
|
|
}
|
|
case *ast.GenDecl:
|
|
e.token(x.TokPos, len(x.Tok.String()), tokKeyword, nil)
|
|
case *ast.GoStmt:
|
|
e.token(x.Go, len("go"), tokKeyword, nil)
|
|
case *ast.Ident:
|
|
e.ident(x)
|
|
case *ast.IfStmt:
|
|
e.token(x.If, len("if"), tokKeyword, nil)
|
|
if x.Else != nil {
|
|
// x.Body.End() or x.Body.End()+1, not that it matters
|
|
pos := e.findKeyword("else", x.Body.End(), x.Else.Pos())
|
|
e.token(pos, len("else"), tokKeyword, nil)
|
|
}
|
|
case *ast.ImportSpec:
|
|
e.importSpec(x)
|
|
pop()
|
|
return false
|
|
case *ast.IncDecStmt:
|
|
e.token(x.TokPos, len(x.Tok.String()), tokOperator, nil)
|
|
case *ast.IndexExpr:
|
|
case *typeparams.IndexListExpr: // accommodate generics
|
|
case *ast.InterfaceType:
|
|
e.token(x.Interface, len("interface"), tokKeyword, nil)
|
|
case *ast.KeyValueExpr:
|
|
case *ast.LabeledStmt:
|
|
case *ast.MapType:
|
|
e.token(x.Map, len("map"), tokKeyword, nil)
|
|
case *ast.ParenExpr:
|
|
case *ast.RangeStmt:
|
|
e.token(x.For, len("for"), tokKeyword, nil)
|
|
// x.TokPos == token.NoPos is legal (for range foo {})
|
|
offset := x.TokPos
|
|
if offset == token.NoPos {
|
|
offset = x.For
|
|
}
|
|
pos := e.findKeyword("range", offset, x.X.Pos())
|
|
e.token(pos, len("range"), tokKeyword, nil)
|
|
case *ast.ReturnStmt:
|
|
e.token(x.Return, len("return"), tokKeyword, nil)
|
|
case *ast.SelectStmt:
|
|
e.token(x.Select, len("select"), tokKeyword, nil)
|
|
case *ast.SelectorExpr:
|
|
case *ast.SendStmt:
|
|
e.token(x.Arrow, len("<-"), tokOperator, nil)
|
|
case *ast.SliceExpr:
|
|
case *ast.StarExpr:
|
|
e.token(x.Star, len("*"), tokOperator, nil)
|
|
case *ast.StructType:
|
|
e.token(x.Struct, len("struct"), tokKeyword, nil)
|
|
case *ast.SwitchStmt:
|
|
e.token(x.Switch, len("switch"), tokKeyword, nil)
|
|
case *ast.TypeAssertExpr:
|
|
if x.Type == nil {
|
|
pos := e.findKeyword("type", x.Lparen, x.Rparen)
|
|
e.token(pos, len("type"), tokKeyword, nil)
|
|
}
|
|
case *ast.TypeSpec:
|
|
case *ast.TypeSwitchStmt:
|
|
e.token(x.Switch, len("switch"), tokKeyword, nil)
|
|
case *ast.UnaryExpr:
|
|
e.token(x.OpPos, len(x.Op.String()), tokOperator, nil)
|
|
case *ast.ValueSpec:
|
|
// things only seen with parsing or type errors, so ignore them
|
|
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
|
|
return true
|
|
// not going to see these
|
|
case *ast.File, *ast.Package:
|
|
e.unexpected(fmt.Sprintf("implement %T %s", x, e.pgf.Tok.PositionFor(x.Pos(), false)))
|
|
// other things we knowingly ignore
|
|
case *ast.Comment, *ast.CommentGroup:
|
|
pop()
|
|
return false
|
|
default:
|
|
e.unexpected(fmt.Sprintf("failed to implement %T", x))
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (e *encoded) ident(x *ast.Ident) {
|
|
if e.ti == nil {
|
|
what, mods := e.unkIdent(x)
|
|
if what != "" {
|
|
e.token(x.Pos(), len(x.String()), what, mods)
|
|
}
|
|
if semDebug {
|
|
log.Printf(" nil %s/nil/nil %q %v %s", x.String(), what, mods, e.strStack())
|
|
}
|
|
return
|
|
}
|
|
def := e.ti.Defs[x]
|
|
if def != nil {
|
|
what, mods := e.definitionFor(x, def)
|
|
if what != "" {
|
|
e.token(x.Pos(), len(x.String()), what, mods)
|
|
}
|
|
if semDebug {
|
|
log.Printf(" for %s/%T/%T got %s %v (%s)", x.String(), def, def.Type(), what, mods, e.strStack())
|
|
}
|
|
return
|
|
}
|
|
use := e.ti.Uses[x]
|
|
tok := func(pos token.Pos, lng int, tok tokenType, mods []string) {
|
|
e.token(pos, lng, tok, mods)
|
|
q := "nil"
|
|
if use != nil {
|
|
q = fmt.Sprintf("%T", use.Type())
|
|
}
|
|
if semDebug {
|
|
log.Printf(" use %s/%T/%s got %s %v (%s)", x.String(), use, q, tok, mods, e.strStack())
|
|
}
|
|
}
|
|
|
|
switch y := use.(type) {
|
|
case nil:
|
|
what, mods := e.unkIdent(x)
|
|
if what != "" {
|
|
tok(x.Pos(), len(x.String()), what, mods)
|
|
} else if semDebug {
|
|
// tok() wasn't called, so didn't log
|
|
log.Printf(" nil %s/%T/nil %q %v (%s)", x.String(), use, what, mods, e.strStack())
|
|
}
|
|
return
|
|
case *types.Builtin:
|
|
tok(x.NamePos, len(x.Name), tokFunction, []string{"defaultLibrary"})
|
|
case *types.Const:
|
|
mods := []string{"readonly"}
|
|
tt := y.Type()
|
|
if _, ok := tt.(*types.Basic); ok {
|
|
tok(x.Pos(), len(x.String()), tokVariable, mods)
|
|
break
|
|
}
|
|
if ttx, ok := tt.(*types.Named); ok {
|
|
if x.String() == "iota" {
|
|
e.unexpected(fmt.Sprintf("iota:%T", ttx))
|
|
}
|
|
if _, ok := ttx.Underlying().(*types.Basic); ok {
|
|
tok(x.Pos(), len(x.String()), tokVariable, mods)
|
|
break
|
|
}
|
|
e.unexpected(fmt.Sprintf("%q/%T", x.String(), tt))
|
|
}
|
|
// can this happen? Don't think so
|
|
e.unexpected(fmt.Sprintf("%s %T %#v", x.String(), tt, tt))
|
|
case *types.Func:
|
|
tok(x.Pos(), len(x.Name), tokFunction, nil)
|
|
case *types.Label:
|
|
// nothing to map it to
|
|
case *types.Nil:
|
|
// nil is a predeclared identifier
|
|
tok(x.Pos(), len("nil"), tokVariable, []string{"readonly", "defaultLibrary"})
|
|
case *types.PkgName:
|
|
tok(x.Pos(), len(x.Name), tokNamespace, nil)
|
|
case *types.TypeName: // could be a tokTpeParam
|
|
var mods []string
|
|
if _, ok := y.Type().(*types.Basic); ok {
|
|
mods = []string{"defaultLibrary"}
|
|
} else if _, ok := y.Type().(*typeparams.TypeParam); ok {
|
|
tok(x.Pos(), len(x.String()), tokTypeParam, mods)
|
|
break
|
|
}
|
|
tok(x.Pos(), len(x.String()), tokType, mods)
|
|
case *types.Var:
|
|
if isSignature(y) {
|
|
tok(x.Pos(), len(x.Name), tokFunction, nil)
|
|
} else if _, ok := y.Type().(*typeparams.TypeParam); ok {
|
|
tok(x.Pos(), len(x.Name), tokTypeParam, nil)
|
|
} else {
|
|
tok(x.Pos(), len(x.Name), tokVariable, nil)
|
|
}
|
|
default:
|
|
// can't happen
|
|
if use == nil {
|
|
msg := fmt.Sprintf("%#v/%#v %#v %#v", x, x.Obj, e.ti.Defs[x], e.ti.Uses[x])
|
|
e.unexpected(msg)
|
|
}
|
|
if use.Type() != nil {
|
|
e.unexpected(fmt.Sprintf("%s %T/%T,%#v", x.String(), use, use.Type(), use))
|
|
} else {
|
|
e.unexpected(fmt.Sprintf("%s %T", x.String(), use))
|
|
}
|
|
}
|
|
}
|
|
|
|
func isSignature(use types.Object) bool {
|
|
if true {
|
|
return false //PJW: fix after generics seem ok
|
|
}
|
|
if _, ok := use.(*types.Var); !ok {
|
|
return false
|
|
}
|
|
v := use.Type()
|
|
if v == nil {
|
|
return false
|
|
}
|
|
if _, ok := v.(*types.Signature); ok {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// both e.ti.Defs and e.ti.Uses are nil. use the parse stack.
|
|
// a lot of these only happen when the package doesn't compile
|
|
// but in that case it is all best-effort from the parse tree
|
|
func (e *encoded) unkIdent(x *ast.Ident) (tokenType, []string) {
|
|
def := []string{"definition"}
|
|
n := len(e.stack) - 2 // parent of Ident
|
|
if n < 0 {
|
|
e.unexpected("no stack?")
|
|
return "", nil
|
|
}
|
|
switch nd := e.stack[n].(type) {
|
|
case *ast.BinaryExpr, *ast.UnaryExpr, *ast.ParenExpr, *ast.StarExpr,
|
|
*ast.IncDecStmt, *ast.SliceExpr, *ast.ExprStmt, *ast.IndexExpr,
|
|
*ast.ReturnStmt, *ast.ChanType, *ast.SendStmt,
|
|
*ast.ForStmt, // possibly incomplete
|
|
*ast.IfStmt, /* condition */
|
|
*ast.KeyValueExpr: // either key or value
|
|
return tokVariable, nil
|
|
case *typeparams.IndexListExpr: // generic?
|
|
return tokVariable, nil
|
|
case *ast.Ellipsis:
|
|
return tokType, nil
|
|
case *ast.CaseClause:
|
|
if n-2 >= 0 {
|
|
if _, ok := e.stack[n-2].(*ast.TypeSwitchStmt); ok {
|
|
return tokType, nil
|
|
}
|
|
}
|
|
return tokVariable, nil
|
|
case *ast.ArrayType:
|
|
if x == nd.Len {
|
|
// or maybe a Type Param, but we can't just from the parse tree
|
|
return tokVariable, nil
|
|
} else {
|
|
return tokType, nil
|
|
}
|
|
case *ast.MapType:
|
|
return tokType, nil
|
|
case *ast.CallExpr:
|
|
if x == nd.Fun {
|
|
return tokFunction, nil
|
|
}
|
|
return tokVariable, nil
|
|
case *ast.SwitchStmt:
|
|
return tokVariable, nil
|
|
case *ast.TypeAssertExpr:
|
|
if x == nd.X {
|
|
return tokVariable, nil
|
|
} else if x == nd.Type {
|
|
return tokType, nil
|
|
}
|
|
case *ast.ValueSpec:
|
|
for _, p := range nd.Names {
|
|
if p == x {
|
|
return tokVariable, def
|
|
}
|
|
}
|
|
for _, p := range nd.Values {
|
|
if p == x {
|
|
return tokVariable, nil
|
|
}
|
|
}
|
|
return tokType, nil
|
|
case *ast.SelectorExpr: // e.ti.Selections[nd] is nil, so no help
|
|
if n-1 >= 0 {
|
|
if ce, ok := e.stack[n-1].(*ast.CallExpr); ok {
|
|
// ... CallExpr SelectorExpr Ident (_.x())
|
|
if ce.Fun == nd && nd.Sel == x {
|
|
return tokFunction, nil
|
|
}
|
|
}
|
|
}
|
|
return tokVariable, nil
|
|
case *ast.AssignStmt:
|
|
for _, p := range nd.Lhs {
|
|
// x := ..., or x = ...
|
|
if p == x {
|
|
if nd.Tok != token.DEFINE {
|
|
def = nil
|
|
}
|
|
return tokVariable, def
|
|
}
|
|
}
|
|
// RHS, = x
|
|
return tokVariable, nil
|
|
case *ast.TypeSpec: // it's a type if it is either the Name or the Type
|
|
if x == nd.Type {
|
|
def = nil
|
|
}
|
|
return tokType, def
|
|
case *ast.Field:
|
|
// ident could be type in a field, or a method in an interface type, or a variable
|
|
if x == nd.Type {
|
|
return tokType, nil
|
|
}
|
|
if n-2 >= 0 {
|
|
_, okit := e.stack[n-2].(*ast.InterfaceType)
|
|
_, okfl := e.stack[n-1].(*ast.FieldList)
|
|
if okit && okfl {
|
|
return tokMethod, def
|
|
}
|
|
}
|
|
return tokVariable, nil
|
|
case *ast.LabeledStmt, *ast.BranchStmt:
|
|
// nothing to report
|
|
case *ast.CompositeLit:
|
|
if nd.Type == x {
|
|
return tokType, nil
|
|
}
|
|
return tokVariable, nil
|
|
case *ast.RangeStmt:
|
|
if nd.Tok != token.DEFINE {
|
|
def = nil
|
|
}
|
|
return tokVariable, def
|
|
case *ast.FuncDecl:
|
|
return tokFunction, def
|
|
default:
|
|
msg := fmt.Sprintf("%T undexpected: %s %s%q", nd, x.Name, e.strStack(), e.srcLine(x))
|
|
e.unexpected(msg)
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func isDeprecated(n *ast.CommentGroup) bool {
|
|
if n == nil {
|
|
return false
|
|
}
|
|
for _, c := range n.List {
|
|
if strings.HasPrefix(c.Text, "// Deprecated") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (e *encoded) definitionFor(x *ast.Ident, def types.Object) (tokenType, []string) {
|
|
// PJW: def == types.Label? probably a nothing
|
|
// PJW: look into replaceing these syntactic tests with types more generally
|
|
mods := []string{"definition"}
|
|
for i := len(e.stack) - 1; i >= 0; i-- {
|
|
s := e.stack[i]
|
|
switch y := s.(type) {
|
|
case *ast.AssignStmt, *ast.RangeStmt:
|
|
if x.Name == "_" {
|
|
return "", nil // not really a variable
|
|
}
|
|
return tokVariable, mods
|
|
case *ast.GenDecl:
|
|
if isDeprecated(y.Doc) {
|
|
mods = append(mods, "deprecated")
|
|
}
|
|
if y.Tok == token.CONST {
|
|
mods = append(mods, "readonly")
|
|
}
|
|
return tokVariable, mods
|
|
case *ast.FuncDecl:
|
|
// If x is immediately under a FuncDecl, it is a function or method
|
|
if i == len(e.stack)-2 {
|
|
if isDeprecated(y.Doc) {
|
|
mods = append(mods, "deprecated")
|
|
}
|
|
if y.Recv != nil {
|
|
return tokMethod, mods
|
|
}
|
|
return tokFunction, mods
|
|
}
|
|
// if x < ... < FieldList < FuncDecl, this is the receiver, a variable
|
|
if _, ok := e.stack[i+1].(*ast.FieldList); ok {
|
|
return tokVariable, nil
|
|
}
|
|
// if x < ... < FieldList < FuncType < FuncDecl, this is a param
|
|
return tokParameter, mods
|
|
case *ast.FuncType:
|
|
return tokParameter, mods
|
|
case *ast.InterfaceType:
|
|
return tokMethod, mods
|
|
case *ast.TypeSpec:
|
|
// GenDecl/Typespec/FuncType/FieldList/Field/Ident
|
|
// (type A func(b uint64)) (err error)
|
|
// b and err should not be tokType, but tokVaraible
|
|
// and in GenDecl/TpeSpec/StructType/FieldList/Field/Ident
|
|
// (type A struct{b uint64}
|
|
// but on type B struct{C}), C is a type, but is not being defined.
|
|
// GenDecl/TypeSpec/FieldList/Field/Ident is a typeParam
|
|
if _, ok := e.stack[i+1].(*ast.FieldList); ok {
|
|
return tokTypeParam, mods
|
|
}
|
|
fldm := e.stack[len(e.stack)-2]
|
|
if fld, ok := fldm.(*ast.Field); ok {
|
|
// if len(fld.names) == 0 this is a tokType, being used
|
|
if len(fld.Names) == 0 {
|
|
return tokType, nil
|
|
}
|
|
return tokVariable, mods
|
|
}
|
|
return tokType, mods
|
|
}
|
|
}
|
|
// can't happen
|
|
msg := fmt.Sprintf("failed to find the decl for %s", e.pgf.Tok.PositionFor(x.Pos(), false))
|
|
e.unexpected(msg)
|
|
return "", []string{""}
|
|
}
|
|
|
|
func (e *encoded) multiline(start, end token.Pos, val string, tok tokenType) {
|
|
f := e.fset.File(start)
|
|
// the hard part is finding the lengths of lines. include the \n
|
|
leng := func(line int) int {
|
|
n := f.LineStart(line)
|
|
if line >= f.LineCount() {
|
|
return f.Size() - int(n)
|
|
}
|
|
return int(f.LineStart(line+1) - n)
|
|
}
|
|
spos := e.fset.PositionFor(start, false)
|
|
epos := e.fset.PositionFor(end, false)
|
|
sline := spos.Line
|
|
eline := epos.Line
|
|
// first line is from spos.Column to end
|
|
e.token(start, leng(sline)-spos.Column, tok, nil) // leng(sline)-1 - (spos.Column-1)
|
|
for i := sline + 1; i < eline; i++ {
|
|
// intermediate lines are from 1 to end
|
|
e.token(f.LineStart(i), leng(i)-1, tok, nil) // avoid the newline
|
|
}
|
|
// last line is from 1 to epos.Column
|
|
e.token(f.LineStart(eline), epos.Column-1, tok, nil) // columns are 1-based
|
|
}
|
|
|
|
// findKeyword finds a keyword rather than guessing its location
|
|
func (e *encoded) findKeyword(keyword string, start, end token.Pos) token.Pos {
|
|
offset := int(start) - e.pgf.Tok.Base()
|
|
last := int(end) - e.pgf.Tok.Base()
|
|
buf := e.pgf.Src
|
|
idx := bytes.Index(buf[offset:last], []byte(keyword))
|
|
if idx != -1 {
|
|
return start + token.Pos(idx)
|
|
}
|
|
//(in unparsable programs: type _ <-<-chan int)
|
|
e.unexpected(fmt.Sprintf("not found:%s %v", keyword, e.fset.PositionFor(start, false)))
|
|
return token.NoPos
|
|
}
|
|
|
|
func (e *encoded) init() error {
|
|
e.start = token.Pos(e.pgf.Tok.Base())
|
|
e.end = e.start + token.Pos(e.pgf.Tok.Size())
|
|
if e.rng == nil {
|
|
return nil
|
|
}
|
|
span, err := e.pgf.Mapper.RangeSpan(*e.rng)
|
|
if err != nil {
|
|
return fmt.Errorf("range span (%w) error for %s", err, e.pgf.File.Name)
|
|
}
|
|
e.end = e.start + token.Pos(span.End().Offset())
|
|
e.start += token.Pos(span.Start().Offset())
|
|
return nil
|
|
}
|
|
|
|
func (e *encoded) Data() []uint32 {
|
|
// binary operators, at least, will be out of order
|
|
sort.Slice(e.items, func(i, j int) bool {
|
|
if e.items[i].line != e.items[j].line {
|
|
return e.items[i].line < e.items[j].line
|
|
}
|
|
return e.items[i].start < e.items[j].start
|
|
})
|
|
typeMap, modMap := e.maps()
|
|
// each semantic token needs five values
|
|
// (see Integer Encoding for Tokens in the LSP spec)
|
|
x := make([]uint32, 5*len(e.items))
|
|
var j int
|
|
var last semItem
|
|
for i := 0; i < len(e.items); i++ {
|
|
typ, ok := typeMap[e.items[i].typeStr]
|
|
if !ok {
|
|
continue // client doesn't want typeStr
|
|
}
|
|
if j == 0 {
|
|
x[0] = e.items[0].line
|
|
} else {
|
|
x[j] = e.items[i].line - last.line
|
|
}
|
|
x[j+1] = e.items[i].start
|
|
if j > 0 && x[j] == 0 {
|
|
x[j+1] = e.items[i].start - last.start
|
|
}
|
|
x[j+2] = e.items[i].len
|
|
x[j+3] = uint32(typ)
|
|
mask := 0
|
|
for _, s := range e.items[i].mods {
|
|
// modMap[s] is 0 if the client doesn't want this modifier
|
|
mask |= modMap[s]
|
|
}
|
|
x[j+4] = uint32(mask)
|
|
j += 5
|
|
last = e.items[i]
|
|
}
|
|
return x[:j]
|
|
}
|
|
|
|
func (e *encoded) importSpec(d *ast.ImportSpec) {
|
|
// a local package name or the last component of the Path
|
|
if d.Name != nil {
|
|
nm := d.Name.String()
|
|
if nm != "_" && nm != "." {
|
|
e.token(d.Name.Pos(), len(nm), tokNamespace, nil)
|
|
}
|
|
return // don't mark anything for . or _
|
|
}
|
|
val := d.Path.Value
|
|
if len(val) < 2 || val[0] != '"' || val[len(val)-1] != '"' {
|
|
// avoid panics on imports without a properly quoted string
|
|
return
|
|
}
|
|
nm := val[1 : len(val)-1] // remove surrounding "s
|
|
// Import strings are implementation defined. Try to match with parse information.
|
|
x, err := e.pkg.GetImport(nm)
|
|
if err != nil {
|
|
// unexpected, but impact is that maybe some import is not colored
|
|
return
|
|
}
|
|
// expect that nm is x.PkgPath and that x.Name() is a component of it
|
|
if x.PkgPath() != nm {
|
|
// don't know how or what to color (if this can happen at all)
|
|
return
|
|
}
|
|
// this is not a precise test: imagine "github.com/nasty/v/v2"
|
|
j := strings.LastIndex(nm, x.Name())
|
|
if j == -1 {
|
|
// name doesn't show up, for whatever reason, so nothing to report
|
|
return
|
|
}
|
|
start := d.Path.Pos() + 1 + token.Pos(j) // skip the initial quote
|
|
e.token(start, len(x.Name()), tokNamespace, nil)
|
|
}
|
|
|
|
// log unexpected state
|
|
func (e *encoded) unexpected(msg string) {
|
|
if semDebug {
|
|
panic(msg)
|
|
}
|
|
event.Error(e.ctx, e.strStack(), errors.New(msg))
|
|
}
|
|
|
|
// SemType returns a string equivalent of the type, for gopls semtok
|
|
func SemType(n int) string {
|
|
tokTypes := SemanticTypes()
|
|
tokMods := SemanticModifiers()
|
|
if n >= 0 && n < len(tokTypes) {
|
|
return tokTypes[n]
|
|
}
|
|
return fmt.Sprintf("?%d[%d,%d]?", n, len(tokTypes), len(tokMods))
|
|
}
|
|
|
|
// SemMods returns the []string equivalent of the mods, for gopls semtok.
|
|
func SemMods(n int) []string {
|
|
tokMods := SemanticModifiers()
|
|
mods := []string{}
|
|
for i := 0; i < len(tokMods); i++ {
|
|
if (n & (1 << uint(i))) != 0 {
|
|
mods = append(mods, tokMods[i])
|
|
}
|
|
}
|
|
return mods
|
|
}
|
|
|
|
func (e *encoded) maps() (map[tokenType]int, map[string]int) {
|
|
tmap := make(map[tokenType]int)
|
|
mmap := make(map[string]int)
|
|
for i, t := range e.tokTypes {
|
|
tmap[tokenType(t)] = i
|
|
}
|
|
for i, m := range e.tokMods {
|
|
mmap[m] = 1 << uint(i) // go 1.12 compatibility
|
|
}
|
|
return tmap, mmap
|
|
}
|
|
|
|
// SemanticTypes to use in case there is no client, as in the command line, or tests
|
|
func SemanticTypes() []string {
|
|
return semanticTypes[:]
|
|
}
|
|
|
|
// SemanticModifiers to use in case there is no client.
|
|
func SemanticModifiers() []string {
|
|
return semanticModifiers[:]
|
|
}
|
|
|
|
var (
|
|
semanticTypes = [...]string{
|
|
"namespace", "type", "class", "enum", "interface",
|
|
"struct", "typeParameter", "parameter", "variable", "property", "enumMember",
|
|
"event", "function", "method", "macro", "keyword", "modifier", "comment",
|
|
"string", "number", "regexp", "operator",
|
|
}
|
|
semanticModifiers = [...]string{
|
|
"declaration", "definition", "readonly", "static",
|
|
"deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary",
|
|
}
|
|
)
|