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>
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index a49f5ae..f02f915 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -13,6 +13,7 @@
"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 @@
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 @@
}, 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 @@
if !options.DeepCompletion {
continue
}
- if numDeepCompletionsSeen >= source.MaxDeepCompletions {
+ if numDeepCompletionsSeen >= completion.MaxDeepCompletions {
continue
}
numDeepCompletionsSeen++
diff --git a/internal/lsp/source/call_hierarchy.go b/internal/lsp/source/call_hierarchy.go
index 180f6b1..62de3ab 100644
--- a/internal/lsp/source/call_hierarchy.go
+++ b/internal/lsp/source/call_hierarchy.go
@@ -152,7 +152,7 @@
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 @@
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
}
diff --git a/internal/lsp/source/code_lens.go b/internal/lsp/source/code_lens.go
index 966dcf2..427b548 100644
--- a/internal/lsp/source/code_lens.go
+++ b/internal/lsp/source/code_lens.go
@@ -40,7 +40,7 @@
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 @@
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 @@
}
}
// 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 @@
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 @@
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 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
}
diff --git a/internal/lsp/source/command.go b/internal/lsp/source/command.go
index b927aa0..66d2f1d 100644
--- a/internal/lsp/source/command.go
+++ b/internal/lsp/source/command.go
@@ -208,7 +208,7 @@
// 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)
}
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion/completion.go
similarity index 96%
rename from internal/lsp/source/completion.go
rename to internal/lsp/source/completion/completion.go
index 101d2df..edaf6f0 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion/completion.go
@@ -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 @@
"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 @@
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 @@
// 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 importInfo struct {
importPath string
name string
- pkg Package
+ pkg source.Package
}
type methodSetKey struct {
@@ -240,7 +255,7 @@
type Selection struct {
content string
cursor token.Pos
- mappedRange
+ source.MappedRange
}
func (p Selection) Content() string {
@@ -248,19 +263,19 @@
}
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 @@
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) 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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
}
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 @@
}
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 @@
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 @@
// 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 @@
clInfo := compLitInfo{
cl: n,
- clType: deref(tv.Type).Underlying(),
+ clType: source.Deref(tv.Type).Underlying(),
}
var (
@@ -2083,7 +2064,7 @@
}
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 @@
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 @@
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 @@
}
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 @@
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 "&".
diff --git a/internal/lsp/source/completion_builtin.go b/internal/lsp/source/completion/completion_builtin.go
similarity index 99%
rename from internal/lsp/source/completion_builtin.go
rename to internal/lsp/source/completion/completion_builtin.go
index 2ddb553..f4137b3 100644
--- a/internal/lsp/source/completion_builtin.go
+++ b/internal/lsp/source/completion/completion_builtin.go
@@ -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"
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion/completion_format.go
similarity index 81%
rename from internal/lsp/source/completion_format.go
rename to internal/lsp/source/completion/completion_format.go
index 6dfa260..793f1ec 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion/completion_format.go
@@ -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 @@
"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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
}
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()
- }
-}
diff --git a/internal/lsp/source/completion_keywords.go b/internal/lsp/source/completion/completion_keywords.go
similarity index 93%
rename from internal/lsp/source/completion_keywords.go
rename to internal/lsp/source/completion/completion_keywords.go
index 324d5bd..bbf59b0 100644
--- a/internal/lsp/source/completion_keywords.go
+++ b/internal/lsp/source/completion/completion_keywords.go
@@ -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 @@
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)
}
}
diff --git a/internal/lsp/source/completion_labels.go b/internal/lsp/source/completion/completion_labels.go
similarity index 98%
rename from internal/lsp/source/completion_labels.go
rename to internal/lsp/source/completion/completion_labels.go
index abb2041..3c54412 100644
--- a/internal/lsp/source/completion_labels.go
+++ b/internal/lsp/source/completion/completion_labels.go
@@ -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"
diff --git a/internal/lsp/source/completion_literal.go b/internal/lsp/source/completion/completion_literal.go
similarity index 94%
rename from internal/lsp/source/completion_literal.go
rename to internal/lsp/source/completion/completion_literal.go
index 342306d..7582e57 100644
--- a/internal/lsp/source/completion_literal.go
+++ b/internal/lsp/source/completion/completion_literal.go
@@ -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 @@
"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 @@
// 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 @@
// 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 @@
// 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 @@
}
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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(")")
diff --git a/internal/lsp/source/completion_package.go b/internal/lsp/source/completion/completion_package.go
similarity index 92%
rename from internal/lsp/source/completion_package.go
rename to internal/lsp/source/completion/completion_package.go
index 3b82a86..a6ac7af 100644
--- a/internal/lsp/source/completion_package.go
+++ b/internal/lsp/source/completion/completion_package.go
@@ -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 @@
"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 @@
// 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 @@
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 @@
return &Selection{
content: content,
cursor: cursor,
- mappedRange: newMappedRange(fset, m, start, end),
+ MappedRange: source.NewMappedRange(fset, m, start, end),
}, nil
}
}
@@ -162,7 +163,7 @@
return &Selection{
content: "",
cursor: cursor,
- mappedRange: newMappedRange(fset, m, start, end),
+ MappedRange: source.NewMappedRange(fset, m, start, end),
}, nil
}
@@ -207,7 +208,7 @@
// 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
diff --git a/internal/lsp/source/completion_printf.go b/internal/lsp/source/completion/completion_printf.go
similarity index 99%
rename from internal/lsp/source/completion_printf.go
rename to internal/lsp/source/completion/completion_printf.go
index e13a938..c90a365 100644
--- a/internal/lsp/source/completion_printf.go
+++ b/internal/lsp/source/completion/completion_printf.go
@@ -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"
diff --git a/internal/lsp/source/completion_printf_test.go b/internal/lsp/source/completion/completion_printf_test.go
similarity index 98%
rename from internal/lsp/source/completion_printf_test.go
rename to internal/lsp/source/completion/completion_printf_test.go
index ab20f27..19d295b 100644
--- a/internal/lsp/source/completion_printf_test.go
+++ b/internal/lsp/source/completion/completion_printf_test.go
@@ -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"
diff --git a/internal/lsp/source/completion_snippet.go b/internal/lsp/source/completion/completion_snippet.go
similarity index 99%
rename from internal/lsp/source/completion_snippet.go
rename to internal/lsp/source/completion/completion_snippet.go
index b6efc02..c254c65 100644
--- a/internal/lsp/source/completion_snippet.go
+++ b/internal/lsp/source/completion/completion_snippet.go
@@ -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"
diff --git a/internal/lsp/source/completion_statements.go b/internal/lsp/source/completion/completion_statements.go
similarity index 96%
rename from internal/lsp/source/completion_statements.go
rename to internal/lsp/source/completion/completion_statements.go
index 0badd6c..62d3cf0 100644
--- a/internal/lsp/source/completion_statements.go
+++ b/internal/lsp/source/completion/completion_statements.go
@@ -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 @@
"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 @@
}
// 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 @@
// 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 @@
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
diff --git a/internal/lsp/source/deep_completion.go b/internal/lsp/source/completion/deep_completion.go
similarity index 99%
rename from internal/lsp/source/deep_completion.go
rename to internal/lsp/source/completion/deep_completion.go
index 2afff8b..3f06d13 100644
--- a/internal/lsp/source/deep_completion.go
+++ b/internal/lsp/source/completion/deep_completion.go
@@ -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"
diff --git a/internal/lsp/source/deep_completion_test.go b/internal/lsp/source/completion/deep_completion_test.go
similarity index 97%
rename from internal/lsp/source/deep_completion_test.go
rename to internal/lsp/source/completion/deep_completion_test.go
index 47d5179..27009af 100644
--- a/internal/lsp/source/deep_completion_test.go
+++ b/internal/lsp/source/completion/deep_completion_test.go
@@ -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"
diff --git a/internal/lsp/source/completion/util.go b/internal/lsp/source/completion/util.go
new file mode 100644
index 0000000..def769f
--- /dev/null
+++ b/internal/lsp/source/completion/util.go
@@ -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) + "{}"
+ }
+}
diff --git a/internal/lsp/source/util_test.go b/internal/lsp/source/completion/util_test.go
similarity index 96%
rename from internal/lsp/source/util_test.go
rename to internal/lsp/source/completion/util_test.go
index 0dfa0b0..c94d279 100644
--- a/internal/lsp/source/util_test.go
+++ b/internal/lsp/source/completion/util_test.go
@@ -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"
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 583bb31..320d39b 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -129,7 +129,7 @@
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
}
diff --git a/internal/lsp/source/extract.go b/internal/lsp/source/extract.go
index 0c30fd5..84679dc 100644
--- a/internal/lsp/source/extract.go
+++ b/internal/lsp/source/extract.go
@@ -135,7 +135,7 @@
// 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++
diff --git a/internal/lsp/source/folding_range.go b/internal/lsp/source/folding_range.go
index fc3f886..7061017 100644
--- a/internal/lsp/source/folding_range.go
+++ b/internal/lsp/source/folding_range.go
@@ -11,7 +11,7 @@
// FoldingRangeInfo holds range and kind info of folding for an ast.Node
type FoldingRangeInfo struct {
- mappedRange
+ MappedRange
Kind protocol.FoldingRangeKind
}
@@ -106,7 +106,7 @@
return nil
}
return &FoldingRangeInfo{
- mappedRange: newMappedRange(fset, m, start, end),
+ MappedRange: NewMappedRange(fset, m, start, end),
Kind: kind,
}
}
@@ -144,7 +144,7 @@
}
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,
})
}
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index 5114d50..310bac3 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -133,7 +133,8 @@
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.
diff --git a/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go
index f4f7793..d18a971 100644
--- a/internal/lsp/source/highlight.go
+++ b/internal/lsp/source/highlight.go
@@ -22,7 +22,7 @@
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)
}
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index 5f68a52..d331e72 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -158,7 +158,7 @@
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 @@
_, 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) {
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index f059821..88eecc1 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -22,10 +22,10 @@
type IdentifierInfo struct {
Name string
Snapshot Snapshot
- mappedRange
+ MappedRange
Type struct {
- mappedRange
+ MappedRange
Object types.Object
}
@@ -42,7 +42,7 @@
}
type Declaration struct {
- MappedRange []mappedRange
+ MappedRange []MappedRange
node ast.Node
obj types.Object
@@ -108,7 +108,7 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
// 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 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 @@
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.
diff --git a/internal/lsp/source/implementation.go b/internal/lsp/source/implementation.go
index feaa848..379471f 100644
--- a/internal/lsp/source/implementation.go
+++ b/internal/lsp/source/implementation.go
@@ -165,7 +165,7 @@
// 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 @@
// 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 @@
// 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)
}
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index b9adbb9..dd026cb 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -250,17 +250,6 @@
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 {
diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go
index 204e185..cd8a34c 100644
--- a/internal/lsp/source/references.go
+++ b/internal/lsp/source/references.go
@@ -20,7 +20,7 @@
// 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 @@
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 @@
ident: ident,
pkg: pkg,
obj: obj,
- mappedRange: rng,
+ MappedRange: rng,
})
}
}
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
index fefbf49..23e7425 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -111,7 +111,7 @@
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
}
diff --git a/internal/lsp/source/rename_check.go b/internal/lsp/source/rename_check.go
index 2b2088b..9cc3e5b 100644
--- a/internal/lsp/source/rename_check.go
+++ b/internal/lsp/source/rename_check.go
@@ -321,7 +321,7 @@
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 @@
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 @@
// 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 @@
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 @@
// 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 @@
//
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 @@
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 {
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
index 10aee4f..890d8e0 100644
--- a/internal/lsp/source/signature_help.go
+++ b/internal/lsp/source/signature_help.go
@@ -21,7 +21,7 @@
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 @@
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 @@
} 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 @@
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 @@
}
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
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index c29899a..b16dcee 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -21,6 +21,7 @@
"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 @@
}
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 @@
}
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++
diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go
index 883ef5d..0565062 100644
--- a/internal/lsp/source/symbols.go
+++ b/internal/lsp/source/symbols.go
@@ -19,13 +19,13 @@
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 @@
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 @@
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 {
diff --git a/internal/lsp/source/types_format.go b/internal/lsp/source/types_format.go
index 2bf9f26..07784c1 100644
--- a/internal/lsp/source/types_format.go
+++ b/internal/lsp/source/types_format.go
@@ -19,8 +19,8 @@
"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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}, 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 @@
// 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
}
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index 63bfd4e..5cb50ec 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -15,6 +15,7 @@
"path/filepath"
"regexp"
"sort"
+ "strconv"
"strings"
"golang.org/x/tools/internal/lsp/protocol"
@@ -22,7 +23,9 @@
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 @@
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 (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 @@
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 @@
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 @@
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 @@
}
}
-// 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 @@
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 @@
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 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 @@
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 @@
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 @@
}
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()
+ }
+}
diff --git a/internal/lsp/source/workspace_symbol.go b/internal/lsp/source/workspace_symbol.go
index 2b9cc91..62f6adf 100644
--- a/internal/lsp/source/workspace_symbol.go
+++ b/internal/lsp/source/workspace_symbol.go
@@ -512,7 +512,7 @@
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
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 63bed53..f358fc5 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -28,6 +28,7 @@
"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 @@
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 @@
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),
diff --git a/internal/lsp/tests/util.go b/internal/lsp/tests/util.go
index c3633a1..fbef835 100644
--- a/internal/lsp/tests/util.go
+++ b/internal/lsp/tests/util.go
@@ -18,6 +18,7 @@
"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 @@
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 @@
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 @@
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