internal/lsp/source: move completion to its own package

Completion is slowly becoming a large part of internal/lsp/source and it
makes sense to move to its own seperate package inside source to make
future refactors easier. As a part of this change, any unexported
members from source required by completion are now exported. Util
functions only required by completion are moved from
internal/lsp/source/util.go to internal/lsp/source/completion/util.go.

Change-Id: I6b7405ec598c910545e649bb0e6aa02ffa653b38
Reviewed-on: https://go-review.googlesource.com/c/tools/+/253178
Run-TryBot: Danish Dua <danishdua@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Danish Dua 2020-09-04 15:37:47 -04:00
parent ea3a2cdbfb
commit acefd226e2
39 changed files with 594 additions and 538 deletions

View File

@ -13,6 +13,7 @@ import (
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/source/completion"
"golang.org/x/tools/internal/span"
)
@ -22,11 +23,11 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara
if !ok {
return nil, err
}
var candidates []source.CompletionItem
var surrounding *source.Selection
var candidates []completion.CompletionItem
var surrounding *completion.Selection
switch fh.Kind() {
case source.Go:
candidates, surrounding, err = source.Completion(ctx, snapshot, fh, params.Position, params.Context.TriggerCharacter)
candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, params.Position, params.Context.TriggerCharacter)
case source.Mod:
candidates, surrounding = nil, nil
}
@ -103,7 +104,7 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara
}, nil
}
func toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
func toProtocolCompletionItems(candidates []completion.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
var (
items = make([]protocol.CompletionItem, 0, len(candidates))
numDeepCompletionsSeen int
@ -115,7 +116,7 @@ func toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.
if !options.DeepCompletion {
continue
}
if numDeepCompletionsSeen >= source.MaxDeepCompletions {
if numDeepCompletionsSeen >= completion.MaxDeepCompletions {
continue
}
numDeepCompletionsSeen++

View File

@ -152,7 +152,7 @@ outer:
nameStart, nameEnd = funcLit.Type.Func, funcLit.Type.Params.Pos()
kind = protocol.Function
}
rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, nameStart, nameEnd).Range()
rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, nameStart, nameEnd).Range()
if err != nil {
return protocol.CallHierarchyItem{}, err
}
@ -229,7 +229,7 @@ func collectCallExpressions(fset *token.FileSet, mapper *protocol.ColumnMapper,
callRanges := []protocol.Range{}
for _, call := range callPositions {
callRange, err := newMappedRange(fset, mapper, call.start, call.end).Range()
callRange, err := NewMappedRange(fset, mapper, call.start, call.end).Range()
if err != nil {
return nil, err
}

View File

@ -40,7 +40,7 @@ func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]p
if !strings.HasSuffix(fh.URI().Filename(), "_test.go") {
return nil, nil
}
pkg, pgf, err := getParsedFile(ctx, snapshot, fh, WidestPackage)
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
if err != nil {
return nil, err
}
@ -54,7 +54,7 @@ func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]p
if benchmarkRe.MatchString(fn.Name.Name) {
benchFns = append(benchFns, fn.Name.Name)
}
rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, d.Pos(), d.Pos()).Range()
rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, d.Pos(), d.Pos()).Range()
if err != nil {
return nil, err
}
@ -90,7 +90,7 @@ func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]p
}
}
// add a code lens to the top of the file which runs all benchmarks in the file
rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
if err != nil {
return nil, err
}
@ -158,7 +158,7 @@ func goGenerateCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) (
if !strings.HasPrefix(l.Text, ggDirective) {
continue
}
rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, l.Pos(), l.Pos()+token.Pos(len(ggDirective))).Range()
rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, l.Pos(), l.Pos()+token.Pos(len(ggDirective))).Range()
if err != nil {
return nil, err
}
@ -209,7 +209,7 @@ func regenerateCgoLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([
if c == nil {
return nil, nil
}
rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, c.Pos(), c.EndPos).Range()
rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, c.Pos(), c.EndPos).Range()
if err != nil {
return nil, err
}
@ -230,11 +230,11 @@ func regenerateCgoLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([
}
func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
_, pgf, err := getParsedFile(ctx, snapshot, fh, WidestPackage)
_, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
if err != nil {
return nil, err
}
rng, err := newMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
rng, err := NewMappedRange(snapshot.FileSet(), pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
if err != nil {
return nil, err
}

View File

@ -208,7 +208,7 @@ func (c *Command) SuggestedFix(ctx context.Context, snapshot Snapshot, fh Versio
// getAllSuggestedFixInputs is a helper function to collect all possible needed
// inputs for an AppliesFunc or SuggestedFixFunc.
func getAllSuggestedFixInputs(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) (*token.FileSet, span.Range, []byte, *ast.File, *protocol.ColumnMapper, *types.Package, *types.Info, error) {
pkg, pgf, err := getParsedFile(ctx, snapshot, fh, NarrowestPackage)
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
if err != nil {
return nil, span.Range{}, nil, nil, nil, nil, nil, errors.Errorf("getting file for Identifier: %w", err)
}

View File

@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
// Package completion provides core functionality for code completion in Go
// editors and tools.
package completion
import (
"context"
@ -25,6 +27,7 @@ import (
"golang.org/x/tools/internal/lsp/fuzzy"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/snippet"
"golang.org/x/tools/internal/lsp/source"
errors "golang.org/x/xerrors"
)
@ -84,6 +87,18 @@ type CompletionItem struct {
obj types.Object
}
// completionOptions holds completion specific configuration.
type completionOptions struct {
deepCompletion bool
unimported bool
documentation bool
fullDocumentation bool
placeholders bool
literal bool
matcher source.Matcher
budget time.Duration
}
// Snippet is a convenience returns the snippet if available, otherwise
// the InsertText.
// used for an item, depending on if the callee wants placeholders or not.
@ -135,8 +150,8 @@ func (ipm insensitivePrefixMatcher) Score(candidateLabel string) float32 {
// completer contains the necessary information for a single completion request.
type completer struct {
snapshot Snapshot
pkg Package
snapshot source.Snapshot
pkg source.Package
qf types.Qualifier
opts *completionOptions
@ -228,7 +243,7 @@ type compLitInfo struct {
type importInfo struct {
importPath string
name string
pkg Package
pkg source.Package
}
type methodSetKey struct {
@ -240,7 +255,7 @@ type methodSetKey struct {
type Selection struct {
content string
cursor token.Pos
mappedRange
source.MappedRange
}
func (p Selection) Content() string {
@ -248,19 +263,19 @@ func (p Selection) Content() string {
}
func (p Selection) Start() token.Pos {
return p.mappedRange.spanRange.Start
return p.MappedRange.SpanRange().Start
}
func (p Selection) End() token.Pos {
return p.mappedRange.spanRange.End
return p.MappedRange.SpanRange().End
}
func (p Selection) Prefix() string {
return p.content[:p.cursor-p.spanRange.Start]
return p.content[:p.cursor-p.SpanRange().Start]
}
func (p Selection) Suffix() string {
return p.content[p.cursor-p.spanRange.Start:]
return p.content[p.cursor-p.SpanRange().Start:]
}
func (c *completer) setSurrounding(ident *ast.Ident) {
@ -275,7 +290,7 @@ func (c *completer) setSurrounding(ident *ast.Ident) {
content: ident.Name,
cursor: c.pos,
// Overwrite the prefix only.
mappedRange: newMappedRange(c.snapshot.FileSet(), c.mapper, ident.Pos(), ident.End()),
MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, ident.Pos(), ident.End()),
}
c.setMatcherFromPrefix(c.surrounding.Prefix())
@ -283,9 +298,9 @@ func (c *completer) setSurrounding(ident *ast.Ident) {
func (c *completer) setMatcherFromPrefix(prefix string) {
switch c.opts.matcher {
case Fuzzy:
case source.Fuzzy:
c.matcher = fuzzy.NewMatcher(prefix)
case CaseSensitive:
case source.CaseSensitive:
c.matcher = prefixMatcher(prefix)
default:
c.matcher = insensitivePrefixMatcher(strings.ToLower(prefix))
@ -297,7 +312,7 @@ func (c *completer) getSurrounding() *Selection {
c.surrounding = &Selection{
content: "",
cursor: c.pos,
mappedRange: newMappedRange(c.snapshot.FileSet(), c.mapper, c.pos, c.pos),
MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, c.pos, c.pos),
}
}
return c.surrounding
@ -470,13 +485,13 @@ func (e ErrIsDefinition) Error() string {
// The selection is computed based on the preceding identifier and can be used by
// the client to score the quality of the completion. For instance, some clients
// may tolerate imperfect matches as valid completion results, since users may make typos.
func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, protoPos protocol.Position, triggerCharacter string) ([]CompletionItem, *Selection, error) {
func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, protoPos protocol.Position, triggerCharacter string) ([]CompletionItem, *Selection, error) {
ctx, done := event.Start(ctx, "source.Completion")
defer done()
startTime := time.Now()
pkg, pgf, err := getParsedFile(ctx, snapshot, fh, NarrowestPackage)
pkg, pgf, err := source.GetParsedFile(ctx, snapshot, fh, source.NarrowestPackage)
if err != nil || pgf.File.Package == token.NoPos {
// If we can't parse this file or find position for the package
// keyword, it may be missing a package declaration. Try offering
@ -485,7 +500,7 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, protoPos
// present but no package name exists.
items, surrounding, innerErr := packageClauseCompletions(ctx, snapshot, fh, protoPos)
if innerErr != nil {
// return the error for getParsedFile since it's more relevant in this situation.
// return the error for GetParsedFile since it's more relevant in this situation.
return nil, nil, errors.Errorf("getting file for Completion: %w", err)
}
@ -547,7 +562,7 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, protoPos
c := &completer{
pkg: pkg,
snapshot: snapshot,
qf: qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()),
qf: source.Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()),
triggerCharacter: triggerCharacter,
filename: fh.URI().Filename(),
file: pgf.File,
@ -561,7 +576,7 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, protoPos
deepCompletion: opts.DeepCompletion,
unimported: opts.UnimportedCompletion,
documentation: opts.CompletionDocumentation,
fullDocumentation: opts.HoverKind == FullDocumentation,
fullDocumentation: opts.HoverKind == source.FullDocumentation,
placeholders: opts.Placeholders,
literal: opts.LiteralCompletions && opts.InsertTextFormat == protocol.SnippetTextFormat,
budget: opts.CompletionBudget,
@ -777,7 +792,7 @@ func (c *completer) populateImportCompletions(ctx context.Context, searchImport
c.surrounding = &Selection{
content: searchImport.Path.Value,
cursor: c.pos,
mappedRange: newMappedRange(c.snapshot.FileSet(), c.mapper, searchImport.Path.Pos(), searchImport.Path.End()),
MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, searchImport.Path.Pos(), searchImport.Path.End()),
}
seenImports := make(map[string]struct{})
@ -1010,7 +1025,7 @@ func (c *completer) setSurroundingForComment(comments *ast.CommentGroup) {
c.surrounding = &Selection{
content: cursorComment.Text[start:end],
cursor: c.pos,
mappedRange: newMappedRange(c.snapshot.FileSet(), c.mapper,
MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper,
token.Pos(int(cursorComment.Slash)+start), token.Pos(int(cursorComment.Slash)+end)),
}
c.setMatcherFromPrefix(c.surrounding.Prefix())
@ -1242,7 +1257,7 @@ func (c *completer) methodsAndFields(ctx context.Context, typ types.Type, addres
// lexical finds completions in the lexical environment.
func (c *completer) lexical(ctx context.Context) error {
scopes := collectScopes(c.pkg.GetTypesInfo(), c.path, c.pos)
scopes := source.CollectScopes(c.pkg.GetTypesInfo(), c.path, c.pos)
scopes = append(scopes, c.pkg.GetTypes().Scope(), types.Universe)
var (
@ -1320,7 +1335,7 @@ func (c *completer) lexical(ctx context.Context) error {
}
if c.inference.objType != nil {
if named, _ := deref(c.inference.objType).(*types.Named); named != nil {
if named, _ := source.Deref(c.inference.objType).(*types.Named); named != nil {
// If we expected a named type, check the type's package for
// completion items. This is useful when the current file hasn't
// imported the type's package yet.
@ -1356,7 +1371,7 @@ func (c *completer) lexical(ctx context.Context) error {
}
if t := c.inference.objType; t != nil {
t = deref(t)
t = source.Deref(t)
// If we have an expected type and it is _not_ a named type,
// handle it specially. Non-named types like "[]int" will never be
@ -1390,26 +1405,6 @@ func (c *completer) lexical(ctx context.Context) error {
return nil
}
func collectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope {
// scopes[i], where i<len(path), is the possibly nil Scope of path[i].
var scopes []*types.Scope
for _, n := range path {
// Include *FuncType scope if pos is inside the function body.
switch node := n.(type) {
case *ast.FuncDecl:
if node.Body != nil && nodeContains(node.Body, pos) {
n = node.Type
}
case *ast.FuncLit:
if node.Body != nil && nodeContains(node.Body, pos) {
n = node.Type
}
}
scopes = append(scopes, info.Scopes[n])
}
return scopes
}
func (c *completer) unimportedPackages(ctx context.Context, seen map[string]struct{}) error {
var prefix string
if c.surrounding != nil {
@ -1507,27 +1502,13 @@ func (c *completer) unimportedPackages(ctx context.Context, seen map[string]stru
// alreadyImports reports whether f has an import with the specified path.
func alreadyImports(f *ast.File, path string) bool {
for _, s := range f.Imports {
if importPath(s) == path {
if source.ImportPath(s) == path {
return true
}
}
return false
}
// importPath returns the unquoted import path of s,
// or "" if the path is not properly quoted.
func importPath(s *ast.ImportSpec) string {
t, err := strconv.Unquote(s.Path.Value)
if err != nil {
return ""
}
return t
}
func nodeContains(n ast.Node, pos token.Pos) bool {
return n != nil && n.Pos() <= pos && pos <= n.End()
}
func (c *completer) inConstDecl() bool {
for _, n := range c.path {
if decl, ok := n.(*ast.GenDecl); ok && decl.Tok == token.CONST {
@ -1614,7 +1595,7 @@ func enclosingCompositeLiteral(path []ast.Node, pos token.Pos, info *types.Info)
clInfo := compLitInfo{
cl: n,
clType: deref(tv.Type).Underlying(),
clType: source.Deref(tv.Type).Underlying(),
}
var (
@ -2083,7 +2064,7 @@ Nodes:
}
return inf
case *ast.RangeStmt:
if nodeContains(node.X, c.pos) {
if source.NodeContains(node.X, c.pos) {
inf.objKind |= kindSlice | kindArray | kindMap | kindString
if node.Value == nil {
inf.objKind |= kindChan
@ -2239,11 +2220,11 @@ func breaksExpectedTypeInference(n ast.Node, pos token.Pos) bool {
case *ast.CompositeLit:
// Doesn't break inference if pos is in type name.
// For example: "Foo<>{Bar: 123}"
return !nodeContains(n.Type, pos)
return !source.NodeContains(n.Type, pos)
case *ast.CallExpr:
// Doesn't break inference if pos is in func name.
// For example: "Foo<>(123)"
return !nodeContains(n.Fun, pos)
return !source.NodeContains(n.Fun, pos)
case *ast.FuncLit, *ast.IndexExpr, *ast.SliceExpr:
return true
default:
@ -2356,7 +2337,7 @@ Nodes:
case *ast.MapType:
inf.wantTypeName = true
if n.Key != nil {
inf.wantComparable = nodeContains(n.Key, c.pos)
inf.wantComparable = source.NodeContains(n.Key, c.pos)
} else {
// If the key is empty, assume we are completing the key if
// pos is directly after the "map[".
@ -2364,10 +2345,10 @@ Nodes:
}
break Nodes
case *ast.ValueSpec:
inf.wantTypeName = nodeContains(n.Type, c.pos)
inf.wantTypeName = source.NodeContains(n.Type, c.pos)
break Nodes
case *ast.TypeSpec:
inf.wantTypeName = nodeContains(n.Type, c.pos)
inf.wantTypeName = source.NodeContains(n.Type, c.pos)
default:
if breaksExpectedTypeInference(p, c.pos) {
return typeNameInference{}
@ -2752,7 +2733,7 @@ func (c *completer) matchingTypeName(cand *candidate) bool {
return true
}
if !isInterface(t) && typeMatches(types.NewPointer(t)) {
if !source.IsInterface(t) && typeMatches(types.NewPointer(t)) {
if c.inference.typeName.compLitType {
// If we are completing a composite literal type as in
// "foo<>{}", to make a pointer we must prepend "&".

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"context"

View File

@ -2,12 +2,11 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"context"
"fmt"
"go/ast"
"go/types"
"strings"
@ -16,6 +15,7 @@ import (
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/snippet"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
)
@ -43,25 +43,25 @@ func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, e
// expandFuncCall mutates the completion label, detail, and snippet
// to that of an invocation of sig.
expandFuncCall := func(sig *types.Signature) error {
s, err := newSignature(ctx, c.snapshot, c.pkg, c.file, "", sig, nil, c.qf)
s, err := source.NewSignature(ctx, c.snapshot, c.pkg, c.file, "", sig, nil, c.qf)
if err != nil {
return err
}
snip = c.functionCallSnippet(label, s.params)
detail = "func" + s.format()
snip = c.functionCallSnippet(label, s.Params())
detail = "func" + s.Format()
return nil
}
switch obj := obj.(type) {
case *types.TypeName:
detail, kind = formatType(obj.Type(), c.qf)
detail, kind = source.FormatType(obj.Type(), c.qf)
case *types.Const:
kind = protocol.ConstantCompletion
case *types.Var:
if _, ok := obj.Type().(*types.Struct); ok {
detail = "struct{...}" // for anonymous structs
} else if obj.IsField() {
detail = formatVarType(ctx, c.snapshot, c.pkg, c.file, obj, c.qf)
detail = source.FormatVarType(ctx, c.snapshot, c.pkg, c.file, obj, c.qf)
}
if obj.IsField() {
kind = protocol.FieldCompletion
@ -184,7 +184,7 @@ func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, e
searchPkg = cand.imp.pkg
}
pgf, pkg, err := findPosInPackage(c.snapshot, searchPkg, obj.Pos())
pgf, pkg, err := source.FindPosInPackage(c.snapshot, searchPkg, obj.Pos())
if err != nil {
return item, nil
}
@ -198,7 +198,7 @@ func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, e
return item, nil
}
hover, err := hoverInfo(pkg, obj, decl)
hover, err := source.HoverInfo(pkg, obj, decl)
if err != nil {
event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri))
return item, nil
@ -221,7 +221,7 @@ func (c *completer) importEdits(ctx context.Context, imp *importInfo) ([]protoco
return nil, err
}
return computeOneImportFixEdits(ctx, c.snapshot, pgf, &imports.ImportFix{
return source.ComputeOneImportFixEdits(ctx, c.snapshot, pgf, &imports.ImportFix{
StmtInfo: imports.ImportInfo{
ImportPath: imp.importPath,
Name: imp.name,
@ -243,12 +243,12 @@ func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (Completi
item.Kind = protocol.ConstantCompletion
case *types.Builtin:
item.Kind = protocol.FunctionCompletion
sig, err := newBuiltinSignature(ctx, c.snapshot, obj.Name())
sig, err := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name())
if err != nil {
return CompletionItem{}, err
}
item.Detail = "func" + sig.format()
item.snippet = c.functionCallSnippet(obj.Name(), sig.params)
item.Detail = "func" + sig.Format()
item.snippet = c.functionCallSnippet(obj.Name(), sig.Params())
case *types.TypeName:
if types.IsInterface(obj.Type()) {
item.Kind = protocol.InterfaceCompletion
@ -260,31 +260,3 @@ func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (Completi
}
return item, nil
}
// qualifier returns a function that appropriately formats a types.PkgName
// appearing in a *ast.File.
func qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier {
// Construct mapping of import paths to their defined or implicit names.
imports := make(map[*types.Package]string)
for _, imp := range f.Imports {
var obj types.Object
if imp.Name != nil {
obj = info.Defs[imp.Name]
} else {
obj = info.Implicits[imp]
}
if pkgname, ok := obj.(*types.PkgName); ok {
imports[pkgname.Imported()] = pkgname.Name()
}
}
// Define qualifier to replace full package paths with names of the imports.
return func(p *types.Package) string {
if p == pkg {
return ""
}
if name, ok := imports[p]; ok {
return name
}
return p.Name()
}
}

View File

@ -1,9 +1,14 @@
package source
// 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 completion
import (
"go/ast"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
const (
@ -69,7 +74,7 @@ func (c *completer) addKeywordCompletions() {
if len(c.path) > 2 {
// Offer "range" if we are in ast.ForStmt.Init. This is what the
// AST looks like before "range" is typed, e.g. "for i := r<>".
if loop, ok := c.path[2].(*ast.ForStmt); ok && nodeContains(loop.Init, c.pos) {
if loop, ok := c.path[2].(*ast.ForStmt); ok && source.NodeContains(loop.Init, c.pos) {
c.addKeywordItems(seen, stdScore, RANGE)
}
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"context"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"context"
@ -17,6 +17,7 @@ import (
"golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/snippet"
"golang.org/x/tools/internal/lsp/source"
)
// literal generates composite literal, function literal, and make()
@ -52,7 +53,7 @@ func (c *completer) literal(ctx context.Context, literalType types.Type, imp *im
// don't offer "mySlice{}" since we have already added a candidate
// of "[]int{}".
if _, named := literalType.(*types.Named); named && expType != nil {
if _, named := deref(expType).(*types.Named); !named {
if _, named := source.Deref(expType).(*types.Named); !named {
return
}
}
@ -129,7 +130,7 @@ func (c *completer) literal(ctx context.Context, literalType types.Type, imp *im
// Add a literal completion for a signature type that implements
// an interface. For example, offer "http.HandlerFunc()" when
// expected type is "http.Handler".
if isInterface(expType) {
if source.IsInterface(expType) {
c.basicLiteral(t, typeName, float64(score), addlEdits)
}
case *types.Basic:
@ -137,7 +138,7 @@ func (c *completer) literal(ctx context.Context, literalType types.Type, imp *im
// expected interface (e.g. named string type http.Dir
// implements http.FileSystem), or are identical to our expected
// type (i.e. yielding a type conversion such as "float64()").
if isInterface(expType) || types.Identical(expType, literalType) {
if source.IsInterface(expType) || types.Identical(expType, literalType) {
c.basicLiteral(t, typeName, float64(score), addlEdits)
}
}
@ -159,7 +160,7 @@ func (c *completer) literal(ctx context.Context, literalType types.Type, imp *im
}
// If prefix matches "func", client may want a function literal.
if score := c.matcher.Score("func"); !cand.takeAddress && score > 0 && !isInterface(expType) {
if score := c.matcher.Score("func"); !cand.takeAddress && score > 0 && !source.IsInterface(expType) {
switch t := literalType.Underlying().(type) {
case *types.Signature:
c.functionLiteral(ctx, t, float64(score))
@ -170,12 +171,12 @@ func (c *completer) literal(ctx context.Context, literalType types.Type, imp *im
// prependEdit produces text edits that preprend the specified prefix
// to the specified node.
func prependEdit(fset *token.FileSet, m *protocol.ColumnMapper, node ast.Node, prefix string) ([]protocol.TextEdit, error) {
rng := newMappedRange(fset, m, node.Pos(), node.Pos())
rng := source.NewMappedRange(fset, m, node.Pos(), node.Pos())
spn, err := rng.Span()
if err != nil {
return nil, err
}
return ToProtocolEdits(m, []diff.TextEdit{{
return source.ToProtocolEdits(m, []diff.TextEdit{{
Span: spn,
NewText: prefix,
}})
@ -210,7 +211,7 @@ func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, m
// If the param has no name in the signature, guess a name based
// on the type. Use an empty qualifier to ignore the package.
// For example, we want to name "http.Request" "r", not "hr".
name = formatVarType(ctx, c.snapshot, c.pkg, c.file, p, func(p *types.Package) string {
name = source.FormatVarType(ctx, c.snapshot, c.pkg, c.file, p, func(p *types.Package) string {
return ""
})
name = abbreviateTypeName(name)
@ -264,7 +265,7 @@ func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, m
// of "i int, j int".
if i == sig.Params().Len()-1 || !types.Identical(p.Type(), sig.Params().At(i+1).Type()) {
snip.WriteText(" ")
typeStr := formatVarType(ctx, c.snapshot, c.pkg, c.file, p, c.qf)
typeStr := source.FormatVarType(ctx, c.snapshot, c.pkg, c.file, p, c.qf)
if sig.Variadic() && i == sig.Params().Len()-1 {
typeStr = strings.Replace(typeStr, "[]", "...", 1)
}
@ -292,7 +293,7 @@ func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, m
if name := r.Name(); name != "" {
snip.WriteText(name + " ")
}
snip.WriteText(formatVarType(ctx, c.snapshot, c.pkg, c.file, r, c.qf))
snip.WriteText(source.FormatVarType(ctx, c.snapshot, c.pkg, c.file, r, c.qf))
}
if resultsNeedParens {
snip.WriteText(")")

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"context"
@ -17,16 +17,17 @@ import (
"golang.org/x/tools/internal/lsp/fuzzy"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
// packageClauseCompletions offers completions for a package declaration when
// one is not present in the given file.
func packageClauseCompletions(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]CompletionItem, *Selection, error) {
func packageClauseCompletions(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, pos protocol.Position) ([]CompletionItem, *Selection, error) {
// We know that the AST for this file will be empty due to the missing
// package declaration, but parse it anyway to get a mapper.
pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull)
if err != nil {
return nil, nil, err
}
@ -67,7 +68,7 @@ func packageClauseCompletions(ctx context.Context, snapshot Snapshot, fh FileHan
// packageCompletionSurrounding returns surrounding for package completion if a
// package completions can be suggested at a given position. A valid location
// for package completion is above any declarations or import statements.
func packageCompletionSurrounding(ctx context.Context, fset *token.FileSet, fh FileHandle, pgf *ParsedGoFile, pos token.Pos) (*Selection, error) {
func packageCompletionSurrounding(ctx context.Context, fset *token.FileSet, fh source.FileHandle, pgf *source.ParsedGoFile, pos token.Pos) (*Selection, error) {
src, err := fh.Read()
if err != nil {
return nil, err
@ -98,7 +99,7 @@ func packageCompletionSurrounding(ctx context.Context, fset *token.FileSet, fh F
return &Selection{
content: name.Name,
cursor: cursor,
mappedRange: newMappedRange(fset, m, name.Pos(), name.End()),
MappedRange: source.NewMappedRange(fset, m, name.Pos(), name.End()),
}, nil
}
}
@ -135,7 +136,7 @@ func packageCompletionSurrounding(ctx context.Context, fset *token.FileSet, fh F
return &Selection{
content: content,
cursor: cursor,
mappedRange: newMappedRange(fset, m, start, end),
MappedRange: source.NewMappedRange(fset, m, start, end),
}, nil
}
}
@ -162,7 +163,7 @@ func packageCompletionSurrounding(ctx context.Context, fset *token.FileSet, fh F
return &Selection{
content: "",
cursor: cursor,
mappedRange: newMappedRange(fset, m, start, end),
MappedRange: source.NewMappedRange(fset, m, start, end),
}, nil
}
@ -207,7 +208,7 @@ func (c *completer) packageNameCompletions(ctx context.Context, fileURI span.URI
// have the given prefix and are used in the the same directory as the given
// file. This also includes test packages for these packages (<pkg>_test) and
// the directory name itself.
func packageSuggestions(ctx context.Context, snapshot Snapshot, fileURI span.URI, prefix string) ([]candidate, error) {
func packageSuggestions(ctx context.Context, snapshot source.Snapshot, fileURI span.URI, prefix string) ([]candidate, error) {
workspacePackages, err := snapshot.WorkspacePackages(ctx)
if err != nil {
return nil, err

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"go/ast"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"fmt"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"go/ast"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"fmt"
@ -12,6 +12,7 @@ import (
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/snippet"
"golang.org/x/tools/internal/lsp/source"
)
// addStatementCandidates adds full statement completion candidates
@ -79,7 +80,7 @@ func (c *completer) addAssignAppend() {
}
// The name or our slice is whatever's in the LHS expression.
sliceText = formatNode(fset, n.Lhs[exprIdx])
sliceText = source.FormatNode(fset, n.Lhs[exprIdx])
case *ast.SelectorExpr:
// Make sure we are a selector at the beginning of a statement.
if _, parentIsExprtStmt := c.path[2].(*ast.ExprStmt); !parentIsExprtStmt {
@ -89,7 +90,7 @@ func (c *completer) addAssignAppend() {
// So far we only know the first part of our slice name. For
// example in "s.a<>" we only know our slice begins with "s."
// since the user could still be typing.
sliceText = formatNode(fset, n.X) + "."
sliceText = source.FormatNode(fset, n.X) + "."
needsLHS = true
case *ast.ExprStmt:
needsLHS = true
@ -205,7 +206,7 @@ func (c *completer) addErrCheckAndReturn() {
var (
// errText is e.g. "err" in "foo, err := bar()".
errText = formatNode(c.snapshot.FileSet(), lastAssignee)
errText = source.FormatNode(c.snapshot.FileSet(), lastAssignee)
// Whether we need to include the "if" keyword in our candidate.
needsIf = true

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"context"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"testing"

View File

@ -0,0 +1,295 @@
// 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 completion
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/internal/lsp/source"
)
// exprAtPos returns the index of the expression containing pos.
func exprAtPos(pos token.Pos, args []ast.Expr) int {
for i, expr := range args {
if expr.Pos() <= pos && pos <= expr.End() {
return i
}
}
return len(args)
}
// eachField invokes fn for each field that can be selected from a
// value of type T.
func eachField(T types.Type, fn func(*types.Var)) {
// TODO(adonovan): this algorithm doesn't exclude ambiguous
// selections that match more than one field/method.
// types.NewSelectionSet should do that for us.
// for termination on recursive types
var seen map[*types.Struct]bool
var visit func(T types.Type)
visit = func(T types.Type) {
if T, ok := source.Deref(T).Underlying().(*types.Struct); ok {
if seen[T] {
return
}
for i := 0; i < T.NumFields(); i++ {
f := T.Field(i)
fn(f)
if f.Anonymous() {
if seen == nil {
// Lazily create "seen" since it is only needed for
// embedded structs.
seen = make(map[*types.Struct]bool)
}
seen[T] = true
visit(f.Type())
}
}
}
}
visit(T)
}
// typeIsValid reports whether typ doesn't contain any Invalid types.
func typeIsValid(typ types.Type) bool {
// Check named types separately, because we don't want
// to call Underlying() on them to avoid problems with recursive types.
if _, ok := typ.(*types.Named); ok {
return true
}
switch typ := typ.Underlying().(type) {
case *types.Basic:
return typ.Kind() != types.Invalid
case *types.Array:
return typeIsValid(typ.Elem())
case *types.Slice:
return typeIsValid(typ.Elem())
case *types.Pointer:
return typeIsValid(typ.Elem())
case *types.Map:
return typeIsValid(typ.Key()) && typeIsValid(typ.Elem())
case *types.Chan:
return typeIsValid(typ.Elem())
case *types.Signature:
return typeIsValid(typ.Params()) && typeIsValid(typ.Results())
case *types.Tuple:
for i := 0; i < typ.Len(); i++ {
if !typeIsValid(typ.At(i).Type()) {
return false
}
}
return true
case *types.Struct, *types.Interface:
// Don't bother checking structs, interfaces for validity.
return true
default:
return false
}
}
// resolveInvalid traverses the node of the AST that defines the scope
// containing the declaration of obj, and attempts to find a user-friendly
// name for its invalid type. The resulting Object and its Type are fake.
func resolveInvalid(fset *token.FileSet, obj types.Object, node ast.Node, info *types.Info) types.Object {
var resultExpr ast.Expr
ast.Inspect(node, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.ValueSpec:
for _, name := range n.Names {
if info.Defs[name] == obj {
resultExpr = n.Type
}
}
return false
case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
for _, name := range n.Names {
if info.Defs[name] == obj {
resultExpr = n.Type
}
}
return false
default:
return true
}
})
// Construct a fake type for the object and return a fake object with this type.
typename := source.FormatNode(fset, resultExpr)
typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), types.Typ[types.Invalid], nil)
return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
}
func isPointer(T types.Type) bool {
_, ok := T.(*types.Pointer)
return ok
}
func isVar(obj types.Object) bool {
_, ok := obj.(*types.Var)
return ok
}
func isTypeName(obj types.Object) bool {
_, ok := obj.(*types.TypeName)
return ok
}
func isFunc(obj types.Object) bool {
_, ok := obj.(*types.Func)
return ok
}
func isEmptyInterface(T types.Type) bool {
intf, _ := T.(*types.Interface)
return intf != nil && intf.NumMethods() == 0
}
func isUntyped(T types.Type) bool {
if basic, ok := T.(*types.Basic); ok {
return basic.Info()&types.IsUntyped > 0
}
return false
}
func isPkgName(obj types.Object) bool {
_, ok := obj.(*types.PkgName)
return ok
}
func isASTFile(n ast.Node) bool {
_, ok := n.(*ast.File)
return ok
}
func deslice(T types.Type) types.Type {
if slice, ok := T.Underlying().(*types.Slice); ok {
return slice.Elem()
}
return nil
}
// isSelector returns the enclosing *ast.SelectorExpr when pos is in the
// selector.
func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr {
if len(path) == 0 {
return nil
}
if sel, ok := path[0].(*ast.SelectorExpr); ok {
return sel
}
if _, ok := path[0].(*ast.Ident); ok && len(path) > 1 {
if sel, ok := path[1].(*ast.SelectorExpr); ok && pos >= sel.Sel.Pos() {
return sel
}
}
return nil
}
func enclosingValueSpec(path []ast.Node) *ast.ValueSpec {
for _, n := range path {
if vs, ok := n.(*ast.ValueSpec); ok {
return vs
}
}
return nil
}
// exprObj returns the types.Object associated with the *ast.Ident or
// *ast.SelectorExpr e.
func exprObj(info *types.Info, e ast.Expr) types.Object {
var ident *ast.Ident
switch expr := e.(type) {
case *ast.Ident:
ident = expr
case *ast.SelectorExpr:
ident = expr.Sel
default:
return nil
}
return info.ObjectOf(ident)
}
// typeConversion returns the type being converted to if call is a type
// conversion expression.
func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
// Type conversion (e.g. "float64(foo)").
if fun, _ := exprObj(info, call.Fun).(*types.TypeName); fun != nil {
return fun.Type()
}
return nil
}
// fieldsAccessible returns whether s has at least one field accessible by p.
func fieldsAccessible(s *types.Struct, p *types.Package) bool {
for i := 0; i < s.NumFields(); i++ {
f := s.Field(i)
if f.Exported() || f.Pkg() == p {
return true
}
}
return false
}
// prevStmt returns the statement that precedes the statement containing pos.
// For example:
//
// foo := 1
// bar(1 + 2<>)
//
// If "<>" is pos, prevStmt returns "foo := 1"
func prevStmt(pos token.Pos, path []ast.Node) ast.Stmt {
var blockLines []ast.Stmt
for i := 0; i < len(path) && blockLines == nil; i++ {
switch n := path[i].(type) {
case *ast.BlockStmt:
blockLines = n.List
case *ast.CommClause:
blockLines = n.Body
case *ast.CaseClause:
blockLines = n.Body
}
}
for i := len(blockLines) - 1; i >= 0; i-- {
if blockLines[i].End() < pos {
return blockLines[i]
}
}
return nil
}
// formatZeroValue produces Go code representing the zero value of T. It
// returns the empty string if T is invalid.
func formatZeroValue(T types.Type, qf types.Qualifier) string {
switch u := T.Underlying().(type) {
case *types.Basic:
switch {
case u.Info()&types.IsNumeric > 0:
return "0"
case u.Info()&types.IsString > 0:
return `""`
case u.Info()&types.IsBoolean > 0:
return "false"
default:
return ""
}
case *types.Pointer, *types.Interface, *types.Chan, *types.Map, *types.Slice, *types.Signature:
return "nil"
default:
return types.TypeString(T, qf) + "{}"
}
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
package completion
import (
"go/types"

View File

@ -129,7 +129,7 @@ func FileDiagnostics(ctx context.Context, snapshot Snapshot, uri span.URI) (Vers
if err != nil {
return VersionedFileIdentity{}, nil, err
}
pkg, _, err := getParsedFile(ctx, snapshot, fh, NarrowestPackage)
pkg, _, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
if err != nil {
return VersionedFileIdentity{}, nil, err
}

View File

@ -135,7 +135,7 @@ func calculateIndentation(content []byte, tok *token.File, insertBeforeStmt ast.
// generateAvailableIdentifier adjusts the new function name until there are no collisons in scope.
// Possible collisions include other function and variable names.
func generateAvailableIdentifier(pos token.Pos, file *ast.File, path []ast.Node, info *types.Info, prefix string, idx int) string {
scopes := collectScopes(info, path, pos)
scopes := CollectScopes(info, path, pos)
name := prefix + fmt.Sprintf("%d", idx)
for file.Scope.Lookup(name) != nil || !isValidName(name, scopes) {
idx++

View File

@ -11,7 +11,7 @@ import (
// FoldingRangeInfo holds range and kind info of folding for an ast.Node
type FoldingRangeInfo struct {
mappedRange
MappedRange
Kind protocol.FoldingRangeKind
}
@ -106,7 +106,7 @@ func foldingRangeFunc(fset *token.FileSet, m *protocol.ColumnMapper, n ast.Node,
return nil
}
return &FoldingRangeInfo{
mappedRange: newMappedRange(fset, m, start, end),
MappedRange: NewMappedRange(fset, m, start, end),
Kind: kind,
}
}
@ -144,7 +144,7 @@ func commentsFoldingRange(fset *token.FileSet, m *protocol.ColumnMapper, file *a
}
comments = append(comments, &FoldingRangeInfo{
// Fold from the end of the first line comment to the end of the comment block.
mappedRange: newMappedRange(fset, m, commentGrp.List[0].End(), commentGrp.End()),
MappedRange: NewMappedRange(fset, m, commentGrp.List[0].End(), commentGrp.End()),
Kind: protocol.Comment,
})
}

View File

@ -133,7 +133,8 @@ func computeImportEdits(ctx context.Context, snapshot Snapshot, pgf *ParsedGoFil
return allFixEdits, editsPerFix, nil
}
func computeOneImportFixEdits(ctx context.Context, snapshot Snapshot, pgf *ParsedGoFile, fix *imports.ImportFix) ([]protocol.TextEdit, error) {
// ComputeOneImportFixEdits returns text edits for a single import fix.
func ComputeOneImportFixEdits(ctx context.Context, snapshot Snapshot, pgf *ParsedGoFile, fix *imports.ImportFix) ([]protocol.TextEdit, error) {
options := &imports.Options{
LocalPrefix: snapshot.View().Options().LocalPrefix,
// Defaults.

View File

@ -22,7 +22,7 @@ func Highlight(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protoc
ctx, done := event.Start(ctx, "source.Highlight")
defer done()
pkg, pgf, err := getParsedFile(ctx, snapshot, fh, WidestPackage)
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, WidestPackage)
if err != nil {
return nil, errors.Errorf("getting file for Highlight: %w", err)
}

View File

@ -158,7 +158,7 @@ func pathLinkAndSymbolName(i *IdentifierInfo) (string, string, string) {
return "", "", ""
}
if r := typ.Recv(); r != nil {
switch rtyp := deref(r.Type()).(type) {
switch rtyp := Deref(r.Type()).(type) {
case *types.Struct:
rTypeName = r.Name()
case *types.Named:
@ -226,10 +226,12 @@ func hover(ctx context.Context, fset *token.FileSet, pkg Package, d Declaration)
_, done := event.Start(ctx, "source.hover")
defer done()
return hoverInfo(pkg, d.obj, d.node)
return HoverInfo(pkg, d.obj, d.node)
}
func hoverInfo(pkg Package, obj types.Object, node ast.Node) (*HoverInformation, error) {
// HoverInfo returns a HoverInformation struct for an ast node and its type
// object.
func HoverInfo(pkg Package, obj types.Object, node ast.Node) (*HoverInformation, error) {
var info *HoverInformation
switch node := node.(type) {

View File

@ -22,10 +22,10 @@ import (
type IdentifierInfo struct {
Name string
Snapshot Snapshot
mappedRange
MappedRange
Type struct {
mappedRange
MappedRange
Object types.Object
}
@ -42,7 +42,7 @@ type IdentifierInfo struct {
}
type Declaration struct {
MappedRange []mappedRange
MappedRange []MappedRange
node ast.Node
obj types.Object
@ -108,7 +108,7 @@ func findIdentifier(ctx context.Context, snapshot Snapshot, pkg Package, file *a
return nil, ErrNoIdentFound
}
qf := qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
qf := Qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
ident, _ := path[0].(*ast.Ident)
if ident == nil {
@ -138,13 +138,13 @@ func findIdentifier(ctx context.Context, snapshot Snapshot, pkg Package, file *a
return &IdentifierInfo{
Name: file.Name.Name,
ident: file.Name,
mappedRange: rng,
MappedRange: rng,
pkg: pkg,
qf: qf,
Snapshot: snapshot,
Declaration: Declaration{
node: declAST.Name,
MappedRange: []mappedRange{declRng},
MappedRange: []MappedRange{declRng},
},
}, nil
}
@ -167,7 +167,7 @@ func findIdentifier(ctx context.Context, snapshot Snapshot, pkg Package, file *a
result.Name = result.ident.Name
var err error
if result.mappedRange, err = posToMappedRange(snapshot, pkg, result.ident.Pos(), result.ident.End()); err != nil {
if result.MappedRange, err = posToMappedRange(snapshot, pkg, result.ident.Pos(), result.ident.End()); err != nil {
return nil, err
}
@ -206,7 +206,7 @@ func findIdentifier(ctx context.Context, snapshot Snapshot, pkg Package, file *a
// The builtin package isn't in the dependency graph, so the usual utilities
// won't work here.
rng := newMappedRange(snapshot.FileSet(), builtin.ParsedFile.Mapper, decl.Pos(), decl.Pos()+token.Pos(len(result.Name)))
rng := NewMappedRange(snapshot.FileSet(), builtin.ParsedFile.Mapper, decl.Pos(), decl.Pos()+token.Pos(len(result.Name)))
result.Declaration.MappedRange = append(result.Declaration.MappedRange, rng)
return result, nil
@ -242,7 +242,7 @@ func findIdentifier(ctx context.Context, snapshot Snapshot, pkg Package, file *a
if hasErrorType(result.Type.Object) {
return result, nil
}
if result.Type.mappedRange, err = objToMappedRange(snapshot, pkg, result.Type.Object); err != nil {
if result.Type.MappedRange, err = objToMappedRange(snapshot, pkg, result.Type.Object); err != nil {
return nil, err
}
}
@ -254,7 +254,7 @@ func searchForEnclosing(info *types.Info, path []ast.Node) types.Type {
switch n := n.(type) {
case *ast.SelectorExpr:
if sel, ok := info.Selections[n]; ok {
recv := deref(sel.Recv())
recv := Deref(sel.Recv())
// Keep track of the last exported type seen.
var exported types.Type
@ -265,7 +265,7 @@ func searchForEnclosing(info *types.Info, path []ast.Node) types.Type {
// method itself.
for _, index := range sel.Index()[:len(sel.Index())-1] {
if r, ok := recv.Underlying().(*types.Struct); ok {
recv = deref(r.Field(index).Type())
recv = Deref(r.Field(index).Type())
if named, ok := recv.(*types.Named); ok && named.Obj().Exported() {
exported = named
}
@ -304,7 +304,7 @@ func hasErrorType(obj types.Object) bool {
}
func objToDecl(ctx context.Context, snapshot Snapshot, srcPkg Package, obj types.Object) (ast.Decl, error) {
pgf, _, err := findPosInPackage(snapshot, srcPkg, obj.Pos())
pgf, _, err := FindPosInPackage(snapshot, srcPkg, obj.Pos())
if err != nil {
return nil, err
}
@ -335,7 +335,7 @@ func importSpec(snapshot Snapshot, pkg Package, file *ast.File, pos token.Pos) (
Name: importPath,
pkg: pkg,
}
if result.mappedRange, err = posToMappedRange(snapshot, pkg, imp.Path.Pos(), imp.Path.End()); err != nil {
if result.MappedRange, err = posToMappedRange(snapshot, pkg, imp.Path.Pos(), imp.Path.End()); err != nil {
return nil, err
}
// Consider the "declaration" of an import spec to be the imported package.

View File

@ -165,7 +165,7 @@ func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.
// concreteImplementsIntf returns true if a is an interface type implemented by
// concrete type b, or vice versa.
func concreteImplementsIntf(a, b types.Type) bool {
aIsIntf, bIsIntf := isInterface(a), isInterface(b)
aIsIntf, bIsIntf := IsInterface(a), IsInterface(b)
// Make sure exactly one is an interface type.
if aIsIntf == bIsIntf {
@ -184,7 +184,7 @@ func concreteImplementsIntf(a, b types.Type) bool {
// type. This is useful to make sure you consider a named type's full method
// set.
func ensurePointer(T types.Type) types.Type {
if _, ok := T.(*types.Named); ok && !isInterface(T) {
if _, ok := T.(*types.Named); ok && !IsInterface(T) {
return types.NewPointer(T)
}
@ -248,7 +248,7 @@ func qualifiedObjsAtProtocolPos(ctx context.Context, s Snapshot, fh FileHandle,
// Look up the implicit *types.PkgName.
obj := searchpkg.GetTypesInfo().Implicits[leaf]
if obj == nil {
return nil, xerrors.Errorf("%w for import %q", errNoObjectFound, importPath(leaf))
return nil, xerrors.Errorf("%w for import %q", errNoObjectFound, ImportPath(leaf))
}
objs = append(objs, obj)
}

View File

@ -250,17 +250,6 @@ func (s ImportShortcut) ShowDefinition() bool {
return s == Both || s == Definition
}
type completionOptions struct {
deepCompletion bool
unimported bool
documentation bool
fullDocumentation bool
placeholders bool
literal bool
matcher Matcher
budget time.Duration
}
// Hooks contains configuration that is provided to the Gopls command by the
// main package.
type Hooks struct {

View File

@ -20,7 +20,7 @@ import (
// ReferenceInfo holds information about reference to an identifier in Go source.
type ReferenceInfo struct {
Name string
mappedRange
MappedRange
ident *ast.Ident
obj types.Object
pkg Package
@ -78,7 +78,7 @@ func references(ctx context.Context, snapshot Snapshot, qos []qualifiedObject, i
return nil, err
}
references = append(references, &ReferenceInfo{
mappedRange: ident.mappedRange,
MappedRange: ident.MappedRange,
Name: qos[0].obj.Name(),
ident: ident.ident,
obj: qos[0].obj,
@ -118,7 +118,7 @@ func references(ctx context.Context, snapshot Snapshot, qos []qualifiedObject, i
ident: ident,
pkg: pkg,
obj: obj,
mappedRange: rng,
MappedRange: rng,
})
}
}

View File

@ -111,7 +111,7 @@ func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position,
for _, ref := range refs {
if obj, ok := ref.obj.(*types.Func); ok {
recv := obj.Type().(*types.Signature).Recv()
if recv != nil && isInterface(recv.Type().Underlying()) {
if recv != nil && IsInterface(recv.Type().Underlying()) {
r.changeMethods = true
break
}

View File

@ -321,7 +321,7 @@ func forEachLexicalRef(pkg Package, obj types.Object, fn func(id *ast.Ident, blo
if !ok {
return visit(nil) // pop stack, don't descend
}
if _, ok := deref(tv.Type).Underlying().(*types.Struct); ok {
if _, ok := Deref(tv.Type).Underlying().(*types.Struct); ok {
if n.Type != nil {
ast.Inspect(n.Type, visit)
}
@ -450,7 +450,7 @@ func (r *renamer) checkStructField(from *types.Var) {
if from.Anonymous() {
if named, ok := from.Type().(*types.Named); ok {
r.check(named.Obj())
} else if named, ok := deref(from.Type()).(*types.Named); ok {
} else if named, ok := Deref(from.Type()).(*types.Named); ok {
r.check(named.Obj())
}
}
@ -570,7 +570,7 @@ func (r *renamer) checkMethod(from *types.Func) {
// Check for conflict at point of declaration.
// Check to ensure preservation of assignability requirements.
R := recv(from).Type()
if isInterface(R) {
if IsInterface(R) {
// Abstract method
// declaration
@ -587,7 +587,7 @@ func (r *renamer) checkMethod(from *types.Func) {
for _, pkg := range r.packages {
// Start with named interface types (better errors)
for _, obj := range pkg.GetTypesInfo().Defs {
if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) {
if obj, ok := obj.(*types.TypeName); ok && IsInterface(obj.Type()) {
f, _, _ := types.LookupFieldOrMethod(
obj.Type(), false, from.Pkg(), from.Name())
if f == nil {
@ -659,7 +659,7 @@ func (r *renamer) checkMethod(from *types.Func) {
// yields abstract method I.f. This can make error
// messages less than obvious.
//
if !isInterface(key.RHS) {
if !IsInterface(key.RHS) {
// The logic below was derived from checkSelections.
rtosel := rmethods.Lookup(from.Pkg(), r.to)
@ -734,7 +734,7 @@ func (r *renamer) checkMethod(from *types.Func) {
//
for key := range r.satisfy() {
// key = (lhs, rhs) where lhs is always an interface.
if isInterface(key.RHS) {
if IsInterface(key.RHS) {
continue
}
rsel := r.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name())
@ -936,10 +936,6 @@ func isPackageLevel(obj types.Object) bool {
return obj.Pkg().Scope().Lookup(obj.Name()) == obj
}
func isInterface(T types.Type) bool {
return T != nil && types.IsInterface(T)
}
// -- Plundered from go/scanner: ---------------------------------------
func isLetter(ch rune) bool {

View File

@ -21,7 +21,7 @@ func SignatureHelp(ctx context.Context, snapshot Snapshot, fh FileHandle, pos pr
ctx, done := event.Start(ctx, "source.SignatureHelp")
defer done()
pkg, pgf, err := getParsedFile(ctx, snapshot, fh, NarrowestPackage)
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
if err != nil {
return nil, 0, errors.Errorf("getting file for SignatureHelp: %w", err)
}
@ -58,7 +58,7 @@ FindCall:
return nil, 0, errors.Errorf("cannot find an enclosing function")
}
qf := qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo())
qf := Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo())
// Get the object representing the function, if available.
// There is no object in certain cases such as calling a function returned by
@ -116,7 +116,7 @@ FindCall:
} else {
name = "func"
}
s, err := newSignature(ctx, snapshot, pkg, pgf.File, name, sig, comment, qf)
s, err := NewSignature(ctx, snapshot, pkg, pgf.File, name, sig, comment, qf)
if err != nil {
return nil, 0, err
}
@ -125,14 +125,14 @@ FindCall:
paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p})
}
return &protocol.SignatureInformation{
Label: name + s.format(),
Label: name + s.Format(),
Documentation: doc.Synopsis(s.doc),
Parameters: paramInfo,
}, activeParam, nil
}
func builtinSignature(ctx context.Context, snapshot Snapshot, callExpr *ast.CallExpr, name string, pos token.Pos) (*protocol.SignatureInformation, int, error) {
sig, err := newBuiltinSignature(ctx, snapshot, name)
sig, err := NewBuiltinSignature(ctx, snapshot, name)
if err != nil {
return nil, 0, err
}
@ -142,7 +142,7 @@ func builtinSignature(ctx context.Context, snapshot Snapshot, callExpr *ast.Call
}
activeParam := activeParameter(callExpr, len(sig.params), sig.variadic, pos)
return &protocol.SignatureInformation{
Label: sig.name + sig.format(),
Label: sig.name + sig.Format(),
Documentation: doc.Synopsis(sig.doc),
Parameters: paramInfo,
}, activeParam, nil

View File

@ -21,6 +21,7 @@ import (
"golang.org/x/tools/internal/lsp/fuzzy"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/source/completion"
"golang.org/x/tools/internal/lsp/tests"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/testenv"
@ -304,11 +305,11 @@ func (r *runner) callCompletion(t *testing.T, src span.Span, options func(*sourc
}
defer r.view.SetOptions(r.ctx, original)
list, surrounding, err := source.Completion(r.ctx, r.snapshot, fh, protocol.Position{
list, surrounding, err := completion.Completion(r.ctx, r.snapshot, fh, protocol.Position{
Line: float64(src.Start().Line() - 1),
Character: float64(src.Start().Column() - 1),
}, "")
if err != nil && !errors.As(err, &source.ErrIsDefinition{}) {
if err != nil && !errors.As(err, &completion.ErrIsDefinition{}) {
t.Fatalf("failed for %v: %v", src, err)
}
var prefix string
@ -317,14 +318,14 @@ func (r *runner) callCompletion(t *testing.T, src span.Span, options func(*sourc
}
var numDeepCompletionsSeen int
var items []source.CompletionItem
var items []completion.CompletionItem
// Apply deep completion filtering.
for _, item := range list {
if item.Depth > 0 {
if !modified.DeepCompletion {
continue
}
if numDeepCompletionsSeen >= source.MaxDeepCompletions {
if numDeepCompletionsSeen >= completion.MaxDeepCompletions {
continue
}
numDeepCompletionsSeen++

View File

@ -19,13 +19,13 @@ func DocumentSymbols(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]p
ctx, done := event.Start(ctx, "source.DocumentSymbols")
defer done()
pkg, pgf, err := getParsedFile(ctx, snapshot, fh, NarrowestPackage)
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
if err != nil {
return nil, errors.Errorf("getting file for DocumentSymbols: %w", err)
}
info := pkg.GetTypesInfo()
q := qualifier(pgf.File, pkg.GetTypes(), info)
q := Qualifier(pgf.File, pkg.GetTypes(), info)
symbolsToReceiver := make(map[types.Type]int)
var symbols []protocol.DocumentSymbol
@ -113,7 +113,7 @@ func typeSymbol(snapshot Snapshot, pkg Package, info *types.Info, spec *ast.Type
s := protocol.DocumentSymbol{
Name: obj.Name(),
}
s.Detail, _ = formatType(obj.Type(), qf)
s.Detail, _ = FormatType(obj.Type(), qf)
s.Kind = typeToKind(obj.Type())
var err error
@ -134,7 +134,7 @@ func typeSymbol(snapshot Snapshot, pkg Package, info *types.Info, spec *ast.Type
Name: f.Name(),
Kind: protocol.Field,
}
child.Detail, _ = formatType(f.Type(), qf)
child.Detail, _ = FormatType(f.Type(), qf)
spanNode, selectionNode := nodesForStructField(i, st)
if span, err := nodeToProtocolRange(snapshot, pkg, spanNode); err == nil {

View File

@ -19,8 +19,8 @@ import (
"golang.org/x/tools/internal/lsp/protocol"
)
// formatType returns the detail and kind for a types.Type.
func formatType(typ types.Type, qf types.Qualifier) (detail string, kind protocol.CompletionItemKind) {
// FormatType returns the detail and kind for a types.Type.
func FormatType(typ types.Type, qf types.Qualifier) (detail string, kind protocol.CompletionItemKind) {
if types.IsInterface(typ) {
detail = "interface{...}"
kind = protocol.InterfaceCompletion
@ -28,7 +28,7 @@ func formatType(typ types.Type, qf types.Qualifier) (detail string, kind protoco
detail = "struct{...}"
kind = protocol.StructCompletion
} else if typ != typ.Underlying() {
detail, kind = formatType(typ.Underlying(), qf)
detail, kind = FormatType(typ.Underlying(), qf)
} else {
detail = types.TypeString(typ, qf)
kind = protocol.ClassCompletion
@ -43,7 +43,7 @@ type signature struct {
needResultParens bool
}
func (s *signature) format() string {
func (s *signature) Format() string {
var b strings.Builder
b.WriteByte('(')
for i, p := range s.params {
@ -73,7 +73,13 @@ func (s *signature) format() string {
return b.String()
}
func newBuiltinSignature(ctx context.Context, snapshot Snapshot, name string) (*signature, error) {
func (s *signature) Params() []string {
return s.params
}
// NewBuiltinSignature returns signature for the builtin object with a given
// name, if a builtin object with the name exists.
func NewBuiltinSignature(ctx context.Context, snapshot Snapshot, name string) (*signature, error) {
builtin, err := snapshot.BuiltinPackage(ctx)
if err != nil {
return nil, err
@ -153,11 +159,12 @@ func formatFieldList(ctx context.Context, snapshot Snapshot, list *ast.FieldList
return result, writeResultParens
}
func newSignature(ctx context.Context, s Snapshot, pkg Package, file *ast.File, name string, sig *types.Signature, comment *ast.CommentGroup, qf types.Qualifier) (*signature, error) {
// NewSignature returns formatted signature for a types.Signature struct.
func NewSignature(ctx context.Context, s Snapshot, pkg Package, file *ast.File, name string, sig *types.Signature, comment *ast.CommentGroup, qf types.Qualifier) (*signature, error) {
params := make([]string, 0, sig.Params().Len())
for i := 0; i < sig.Params().Len(); i++ {
el := sig.Params().At(i)
typ := formatVarType(ctx, s, pkg, file, el, qf)
typ := FormatVarType(ctx, s, pkg, file, el, qf)
p := typ
if el.Name() != "" {
p = el.Name() + " " + typ
@ -171,7 +178,7 @@ func newSignature(ctx context.Context, s Snapshot, pkg Package, file *ast.File,
needResultParens = true
}
el := sig.Results().At(i)
typ := formatVarType(ctx, s, pkg, file, el, qf)
typ := FormatVarType(ctx, s, pkg, file, el, qf)
if el.Name() == "" {
results = append(results, typ)
} else {
@ -194,11 +201,11 @@ func newSignature(ctx context.Context, s Snapshot, pkg Package, file *ast.File,
}, nil
}
// formatVarType formats a *types.Var, accounting for type aliases.
// FormatVarType formats a *types.Var, accounting for type aliases.
// To do this, it looks in the AST of the file in which the object is declared.
// On any errors, it always fallbacks back to types.TypeString.
func formatVarType(ctx context.Context, snapshot Snapshot, srcpkg Package, srcfile *ast.File, obj *types.Var, qf types.Qualifier) string {
pgf, pkg, err := findPosInPackage(snapshot, srcpkg, obj.Pos())
func FormatVarType(ctx context.Context, snapshot Snapshot, srcpkg Package, srcfile *ast.File, obj *types.Var, qf types.Qualifier) string {
pgf, pkg, err := FindPosInPackage(snapshot, srcpkg, obj.Pos())
if err != nil {
return types.TypeString(obj.Type(), qf)
}
@ -218,7 +225,7 @@ func formatVarType(ctx context.Context, snapshot Snapshot, srcpkg Package, srcfi
// If the request came from a different package than the one in which the
// types are defined, we may need to modify the qualifiers.
qualified = qualifyExpr(snapshot.FileSet(), qualified, srcpkg, pkg, srcfile, clonedInfo, qf)
fmted := formatNode(snapshot.FileSet(), qualified)
fmted := FormatNode(snapshot.FileSet(), qualified)
return fmted
}

View File

@ -15,6 +15,7 @@ import (
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"golang.org/x/tools/internal/lsp/protocol"
@ -22,7 +23,9 @@ import (
errors "golang.org/x/xerrors"
)
type mappedRange struct {
// MappedRange provides mapped protocol.Range for a span.Range, accounting for
// UTF-16 code points.
type MappedRange struct {
spanRange span.Range
m *protocol.ColumnMapper
@ -31,8 +34,9 @@ type mappedRange struct {
protocolRange *protocol.Range
}
func newMappedRange(fset *token.FileSet, m *protocol.ColumnMapper, start, end token.Pos) mappedRange {
return mappedRange{
// NewMappedRange returns a MappedRange for the given start and end token.Pos.
func NewMappedRange(fset *token.FileSet, m *protocol.ColumnMapper, start, end token.Pos) MappedRange {
return MappedRange{
spanRange: span.Range{
FileSet: fset,
Start: start,
@ -43,7 +47,7 @@ func newMappedRange(fset *token.FileSet, m *protocol.ColumnMapper, start, end to
}
}
func (s mappedRange) Range() (protocol.Range, error) {
func (s MappedRange) Range() (protocol.Range, error) {
if s.protocolRange == nil {
spn, err := s.spanRange.Span()
if err != nil {
@ -58,17 +62,22 @@ func (s mappedRange) Range() (protocol.Range, error) {
return *s.protocolRange, nil
}
func (s mappedRange) Span() (span.Span, error) {
func (s MappedRange) Span() (span.Span, error) {
return s.spanRange.Span()
}
func (s mappedRange) URI() span.URI {
func (s MappedRange) SpanRange() span.Range {
return s.spanRange
}
func (s MappedRange) URI() span.URI {
return s.m.URI
}
// getParsedFile is a convenience function that extracts the Package and ParsedGoFile for a File in a Snapshot.
// selectPackage is typically Narrowest/WidestPackageHandle below.
func getParsedFile(ctx context.Context, snapshot Snapshot, fh FileHandle, selectPackage PackagePolicy) (Package, *ParsedGoFile, error) {
// GetParsedFile is a convenience function that extracts the Package and
// ParsedGoFile for a File in a Snapshot. selectPackage is typically
// Narrowest/WidestPackageHandle below.
func GetParsedFile(ctx context.Context, snapshot Snapshot, fh FileHandle, selectPackage PackagePolicy) (Package, *ParsedGoFile, error) {
phs, err := snapshot.PackagesForFile(ctx, fh.URI(), TypecheckWorkspace)
if err != nil {
return nil, nil, err
@ -158,7 +167,7 @@ func nodeToProtocolRange(snapshot Snapshot, pkg Package, n ast.Node) (protocol.R
return mrng.Range()
}
func objToMappedRange(snapshot Snapshot, pkg Package, obj types.Object) (mappedRange, error) {
func objToMappedRange(snapshot Snapshot, pkg Package, obj types.Object) (MappedRange, error) {
if pkgName, ok := obj.(*types.PkgName); ok {
// An imported Go package has a package-local, unqualified name.
// When the name matches the imported package name, there is no
@ -177,23 +186,23 @@ func objToMappedRange(snapshot Snapshot, pkg Package, obj types.Object) (mappedR
return nameToMappedRange(snapshot, pkg, obj.Pos(), obj.Name())
}
func nameToMappedRange(snapshot Snapshot, pkg Package, pos token.Pos, name string) (mappedRange, error) {
func nameToMappedRange(snapshot Snapshot, pkg Package, pos token.Pos, name string) (MappedRange, error) {
return posToMappedRange(snapshot, pkg, pos, pos+token.Pos(len(name)))
}
func posToMappedRange(snapshot Snapshot, pkg Package, pos, end token.Pos) (mappedRange, error) {
func posToMappedRange(snapshot Snapshot, pkg Package, pos, end token.Pos) (MappedRange, error) {
logicalFilename := snapshot.FileSet().File(pos).Position(pos).Filename
pgf, _, err := findFileInDeps(pkg, span.URIFromPath(logicalFilename))
if err != nil {
return mappedRange{}, err
return MappedRange{}, err
}
if !pos.IsValid() {
return mappedRange{}, errors.Errorf("invalid position for %v", pos)
return MappedRange{}, errors.Errorf("invalid position for %v", pos)
}
if !end.IsValid() {
return mappedRange{}, errors.Errorf("invalid position for %v", end)
return MappedRange{}, errors.Errorf("invalid position for %v", end)
}
return newMappedRange(snapshot.FileSet(), pgf.Mapper, pos, end), nil
return NewMappedRange(snapshot.FileSet(), pgf.Mapper, pos, end), nil
}
// Matches cgo generated comment as well as the proposed standard:
@ -231,7 +240,8 @@ func (k FileKind) String() string {
}
}
// Returns the index and the node whose position is contained inside the node list.
// nodeAtPos returns the index and the node whose position is contained inside
// the node list.
func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) {
if nodes == nil {
return nil, -1
@ -244,121 +254,13 @@ func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) {
return nil, -1
}
// indexExprAtPos returns the index of the expression containing pos.
func exprAtPos(pos token.Pos, args []ast.Expr) int {
for i, expr := range args {
if expr.Pos() <= pos && pos <= expr.End() {
return i
}
}
return len(args)
// IsInterface returns if a types.Type is an interface
func IsInterface(T types.Type) bool {
return T != nil && types.IsInterface(T)
}
// eachField invokes fn for each field that can be selected from a
// value of type T.
func eachField(T types.Type, fn func(*types.Var)) {
// TODO(adonovan): this algorithm doesn't exclude ambiguous
// selections that match more than one field/method.
// types.NewSelectionSet should do that for us.
// for termination on recursive types
var seen map[*types.Struct]bool
var visit func(T types.Type)
visit = func(T types.Type) {
if T, ok := deref(T).Underlying().(*types.Struct); ok {
if seen[T] {
return
}
for i := 0; i < T.NumFields(); i++ {
f := T.Field(i)
fn(f)
if f.Anonymous() {
if seen == nil {
// Lazily create "seen" since it is only needed for
// embedded structs.
seen = make(map[*types.Struct]bool)
}
seen[T] = true
visit(f.Type())
}
}
}
}
visit(T)
}
// typeIsValid reports whether typ doesn't contain any Invalid types.
func typeIsValid(typ types.Type) bool {
// Check named types separately, because we don't want
// to call Underlying() on them to avoid problems with recursive types.
if _, ok := typ.(*types.Named); ok {
return true
}
switch typ := typ.Underlying().(type) {
case *types.Basic:
return typ.Kind() != types.Invalid
case *types.Array:
return typeIsValid(typ.Elem())
case *types.Slice:
return typeIsValid(typ.Elem())
case *types.Pointer:
return typeIsValid(typ.Elem())
case *types.Map:
return typeIsValid(typ.Key()) && typeIsValid(typ.Elem())
case *types.Chan:
return typeIsValid(typ.Elem())
case *types.Signature:
return typeIsValid(typ.Params()) && typeIsValid(typ.Results())
case *types.Tuple:
for i := 0; i < typ.Len(); i++ {
if !typeIsValid(typ.At(i).Type()) {
return false
}
}
return true
case *types.Struct, *types.Interface:
// Don't bother checking structs, interfaces for validity.
return true
default:
return false
}
}
// resolveInvalid traverses the node of the AST that defines the scope
// containing the declaration of obj, and attempts to find a user-friendly
// name for its invalid type. The resulting Object and its Type are fake.
func resolveInvalid(fset *token.FileSet, obj types.Object, node ast.Node, info *types.Info) types.Object {
var resultExpr ast.Expr
ast.Inspect(node, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.ValueSpec:
for _, name := range n.Names {
if info.Defs[name] == obj {
resultExpr = n.Type
}
}
return false
case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
for _, name := range n.Names {
if info.Defs[name] == obj {
resultExpr = n.Type
}
}
return false
default:
return true
}
})
// Construct a fake type for the object and return a fake object with this type.
typename := formatNode(fset, resultExpr)
typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), types.Typ[types.Invalid], nil)
return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
}
func formatNode(fset *token.FileSet, n ast.Node) string {
// FormatNode returns the "pretty-print" output for an ast node.
func FormatNode(fset *token.FileSet, n ast.Node) string {
var buf strings.Builder
if err := printer.Fprint(&buf, fset, n); err != nil {
return ""
@ -366,19 +268,9 @@ func formatNode(fset *token.FileSet, n ast.Node) string {
return buf.String()
}
func isPointer(T types.Type) bool {
_, ok := T.(*types.Pointer)
return ok
}
func isVar(obj types.Object) bool {
_, ok := obj.(*types.Var)
return ok
}
// deref returns a pointer's element type, traversing as many levels as needed.
// Deref returns a pointer's element type, traversing as many levels as needed.
// Otherwise it returns typ.
func deref(typ types.Type) types.Type {
func Deref(typ types.Type) types.Type {
for {
p, ok := typ.Underlying().(*types.Pointer)
if !ok {
@ -388,113 +280,6 @@ func deref(typ types.Type) types.Type {
}
}
func isTypeName(obj types.Object) bool {
_, ok := obj.(*types.TypeName)
return ok
}
func isFunc(obj types.Object) bool {
_, ok := obj.(*types.Func)
return ok
}
func isEmptyInterface(T types.Type) bool {
intf, _ := T.(*types.Interface)
return intf != nil && intf.NumMethods() == 0
}
func isUntyped(T types.Type) bool {
if basic, ok := T.(*types.Basic); ok {
return basic.Info()&types.IsUntyped > 0
}
return false
}
func isPkgName(obj types.Object) bool {
_, ok := obj.(*types.PkgName)
return ok
}
func isASTFile(n ast.Node) bool {
_, ok := n.(*ast.File)
return ok
}
func deslice(T types.Type) types.Type {
if slice, ok := T.Underlying().(*types.Slice); ok {
return slice.Elem()
}
return nil
}
// isSelector returns the enclosing *ast.SelectorExpr when pos is in the
// selector.
func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr {
if len(path) == 0 {
return nil
}
if sel, ok := path[0].(*ast.SelectorExpr); ok {
return sel
}
if _, ok := path[0].(*ast.Ident); ok && len(path) > 1 {
if sel, ok := path[1].(*ast.SelectorExpr); ok && pos >= sel.Sel.Pos() {
return sel
}
}
return nil
}
func enclosingValueSpec(path []ast.Node) *ast.ValueSpec {
for _, n := range path {
if vs, ok := n.(*ast.ValueSpec); ok {
return vs
}
}
return nil
}
// exprObj returns the types.Object associated with the *ast.Ident or
// *ast.SelectorExpr e.
func exprObj(info *types.Info, e ast.Expr) types.Object {
var ident *ast.Ident
switch expr := e.(type) {
case *ast.Ident:
ident = expr
case *ast.SelectorExpr:
ident = expr.Sel
default:
return nil
}
return info.ObjectOf(ident)
}
// typeConversion returns the type being converted to if call is a type
// conversion expression.
func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
// Type conversion (e.g. "float64(foo)").
if fun, _ := exprObj(info, call.Fun).(*types.TypeName); fun != nil {
return fun.Type()
}
return nil
}
// fieldsAccessible returns whether s has at least one field accessible by p.
func fieldsAccessible(s *types.Struct, p *types.Package) bool {
for i := 0; i < s.NumFields(); i++ {
f := s.Field(i)
if f.Exported() || f.Pkg() == p {
return true
}
}
return false
}
func SortDiagnostics(d []*Diagnostic) {
sort.Slice(d, func(i int, j int) bool {
return CompareDiagnostic(d[i], d[j]) < 0
@ -517,7 +302,9 @@ func CompareDiagnostic(a, b *Diagnostic) int {
return 1
}
func findPosInPackage(snapshot Snapshot, searchpkg Package, pos token.Pos) (*ParsedGoFile, Package, error) {
// FindPosInPackage finds the parsed file for a position in a given search
// package.
func FindPosInPackage(snapshot Snapshot, searchpkg Package, pos token.Pos) (*ParsedGoFile, Package, error) {
tok := snapshot.FileSet().File(pos)
if tok == nil {
return nil, nil, errors.Errorf("no file for pos in package %s", searchpkg.ID())
@ -553,57 +340,6 @@ func findFileInDeps(pkg Package, uri span.URI) (*ParsedGoFile, Package, error) {
return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
}
// prevStmt returns the statement that precedes the statement containing pos.
// For example:
//
// foo := 1
// bar(1 + 2<>)
//
// If "<>" is pos, prevStmt returns "foo := 1"
func prevStmt(pos token.Pos, path []ast.Node) ast.Stmt {
var blockLines []ast.Stmt
for i := 0; i < len(path) && blockLines == nil; i++ {
switch n := path[i].(type) {
case *ast.BlockStmt:
blockLines = n.List
case *ast.CommClause:
blockLines = n.Body
case *ast.CaseClause:
blockLines = n.Body
}
}
for i := len(blockLines) - 1; i >= 0; i-- {
if blockLines[i].End() < pos {
return blockLines[i]
}
}
return nil
}
// formatZeroValue produces Go code representing the zero value of T. It
// returns the empty string if T is invalid.
func formatZeroValue(T types.Type, qf types.Qualifier) string {
switch u := T.Underlying().(type) {
case *types.Basic:
switch {
case u.Info()&types.IsNumeric > 0:
return "0"
case u.Info()&types.IsString > 0:
return `""`
case u.Info()&types.IsBoolean > 0:
return "false"
default:
return ""
}
case *types.Pointer, *types.Interface, *types.Chan, *types.Map, *types.Slice, *types.Signature:
return "nil"
default:
return types.TypeString(T, qf) + "{}"
}
}
// MarshalArgs encodes the given arguments to json.RawMessages. This function
// is used to construct arguments to a protocol.Command.
//
@ -647,3 +383,68 @@ func UnmarshalArgs(jsonArgs []json.RawMessage, args ...interface{}) error {
}
return nil
}
// ImportPath returns the unquoted import path of s,
// or "" if the path is not properly quoted.
func ImportPath(s *ast.ImportSpec) string {
t, err := strconv.Unquote(s.Path.Value)
if err != nil {
return ""
}
return t
}
// NodeContains returns true if a node encloses a given position pos.
func NodeContains(n ast.Node, pos token.Pos) bool {
return n != nil && n.Pos() <= pos && pos <= n.End()
}
// CollectScopes returns all scopes in an ast path, ordered as innermost scope
// first.
func CollectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope {
// scopes[i], where i<len(path), is the possibly nil Scope of path[i].
var scopes []*types.Scope
for _, n := range path {
// Include *FuncType scope if pos is inside the function body.
switch node := n.(type) {
case *ast.FuncDecl:
if node.Body != nil && NodeContains(node.Body, pos) {
n = node.Type
}
case *ast.FuncLit:
if node.Body != nil && NodeContains(node.Body, pos) {
n = node.Type
}
}
scopes = append(scopes, info.Scopes[n])
}
return scopes
}
// Qualifier returns a function that appropriately formats a types.PkgName
// appearing in a *ast.File.
func Qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier {
// Construct mapping of import paths to their defined or implicit names.
imports := make(map[*types.Package]string)
for _, imp := range f.Imports {
var obj types.Object
if imp.Name != nil {
obj = info.Defs[imp.Name]
} else {
obj = info.Implicits[imp]
}
if pkgname, ok := obj.(*types.PkgName); ok {
imports[pkgname.Imported()] = pkgname.Name()
}
}
// Define qualifier to replace full package paths with names of the imports.
return func(p *types.Package) string {
if p == pkg {
return ""
}
if name, ok := imports[p]; ok {
return name
}
return p.Name()
}
}

View File

@ -512,7 +512,7 @@ func (sc *symbolCollector) match(name string, kind protocol.SymbolKind, node ast
return
}
mrng := newMappedRange(sc.current.snapshot.FileSet(), sc.curFile.Mapper, node.Pos(), node.End())
mrng := NewMappedRange(sc.current.snapshot.FileSet(), sc.curFile.Mapper, node.Pos(), node.End())
rng, err := mrng.Range()
if err != nil {
return

View File

@ -28,6 +28,7 @@ import (
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/source/completion"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/txtar"
@ -46,7 +47,7 @@ var UpdateGolden = flag.Bool("golden", false, "Update golden files")
type CallHierarchy map[span.Span]*CallHierarchyResult
type CodeLens map[span.URI][]protocol.CodeLens
type Diagnostics map[span.URI][]*source.Diagnostic
type CompletionItems map[token.Pos]*source.CompletionItem
type CompletionItems map[token.Pos]*completion.CompletionItem
type Completions map[span.Span][]Completion
type CompletionSnippets map[span.Span][]CompletionSnippet
type UnimportedCompletions map[span.Span][]Completion
@ -1037,7 +1038,7 @@ func (data *Data) collectCompletionItems(pos token.Pos, args []string) {
if len(args) == 4 {
documentation = args[3]
}
data.CompletionItems[pos] = &source.CompletionItem{
data.CompletionItems[pos] = &completion.CompletionItem{
Label: label,
Detail: detail,
Kind: protocol.ParseCompletionItemKind(kind),

View File

@ -18,6 +18,7 @@ import (
"golang.org/x/tools/internal/lsp/diff/myers"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/source/completion"
"golang.org/x/tools/internal/span"
)
@ -332,7 +333,7 @@ func DiffCallHierarchyItems(gotCalls []protocol.CallHierarchyItem, expectedCalls
return ""
}
func ToProtocolCompletionItems(items []source.CompletionItem) []protocol.CompletionItem {
func ToProtocolCompletionItems(items []completion.CompletionItem) []protocol.CompletionItem {
var result []protocol.CompletionItem
for _, item := range items {
result = append(result, ToProtocolCompletionItem(item))
@ -340,7 +341,7 @@ func ToProtocolCompletionItems(items []source.CompletionItem) []protocol.Complet
return result
}
func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem {
func ToProtocolCompletionItem(item completion.CompletionItem) protocol.CompletionItem {
pItem := protocol.CompletionItem{
Label: item.Label,
Kind: item.Kind,
@ -463,7 +464,7 @@ func DiffSnippets(want string, got *protocol.CompletionItem) string {
return ""
}
func FindItem(list []protocol.CompletionItem, want source.CompletionItem) *protocol.CompletionItem {
func FindItem(list []protocol.CompletionItem, want completion.CompletionItem) *protocol.CompletionItem {
for _, item := range list {
if item.Label == want.Label {
return &item