From acefd226e2ccdaf33b9cc49361732bb867ca90c4 Mon Sep 17 00:00:00 2001 From: Danish Dua Date: Fri, 4 Sep 2020 15:37:47 -0400 Subject: [PATCH] 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 TryBot-Result: Gobot Gobot Reviewed-by: Heschi Kreinick --- internal/lsp/completion.go | 11 +- internal/lsp/source/call_hierarchy.go | 4 +- internal/lsp/source/code_lens.go | 14 +- internal/lsp/source/command.go | 2 +- .../lsp/source/{ => completion}/completion.go | 113 ++--- .../{ => completion}/completion_builtin.go | 2 +- .../{ => completion}/completion_format.go | 54 +-- .../{ => completion}/completion_keywords.go | 9 +- .../{ => completion}/completion_labels.go | 2 +- .../{ => completion}/completion_literal.go | 21 +- .../{ => completion}/completion_package.go | 17 +- .../{ => completion}/completion_printf.go | 2 +- .../completion_printf_test.go | 2 +- .../{ => completion}/completion_snippet.go | 2 +- .../{ => completion}/completion_statements.go | 9 +- .../{ => completion}/deep_completion.go | 2 +- .../{ => completion}/deep_completion_test.go | 2 +- internal/lsp/source/completion/util.go | 295 +++++++++++++ .../lsp/source/{ => completion}/util_test.go | 2 +- internal/lsp/source/diagnostics.go | 2 +- internal/lsp/source/extract.go | 2 +- internal/lsp/source/folding_range.go | 6 +- internal/lsp/source/format.go | 3 +- internal/lsp/source/highlight.go | 2 +- internal/lsp/source/hover.go | 8 +- internal/lsp/source/identifier.go | 26 +- internal/lsp/source/implementation.go | 6 +- internal/lsp/source/options.go | 11 - internal/lsp/source/references.go | 6 +- internal/lsp/source/rename.go | 2 +- internal/lsp/source/rename_check.go | 16 +- internal/lsp/source/signature_help.go | 12 +- internal/lsp/source/source_test.go | 9 +- internal/lsp/source/symbols.go | 8 +- internal/lsp/source/types_format.go | 31 +- internal/lsp/source/util.go | 403 +++++------------- internal/lsp/source/workspace_symbol.go | 2 +- internal/lsp/tests/tests.go | 5 +- internal/lsp/tests/util.go | 7 +- 39 files changed, 594 insertions(+), 538 deletions(-) rename internal/lsp/source/{ => completion}/completion.go (96%) rename internal/lsp/source/{ => completion}/completion_builtin.go (99%) rename internal/lsp/source/{ => completion}/completion_format.go (81%) rename internal/lsp/source/{ => completion}/completion_keywords.go (93%) rename internal/lsp/source/{ => completion}/completion_labels.go (99%) rename internal/lsp/source/{ => completion}/completion_literal.go (95%) rename internal/lsp/source/{ => completion}/completion_package.go (92%) rename internal/lsp/source/{ => completion}/completion_printf.go (99%) rename internal/lsp/source/{ => completion}/completion_printf_test.go (99%) rename internal/lsp/source/{ => completion}/completion_snippet.go (99%) rename internal/lsp/source/{ => completion}/completion_statements.go (97%) rename internal/lsp/source/{ => completion}/deep_completion.go (99%) rename internal/lsp/source/{ => completion}/deep_completion_test.go (97%) create mode 100644 internal/lsp/source/completion/util.go rename internal/lsp/source/{ => completion}/util_test.go (97%) diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index a49f5aeb4d..f02f91559b 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -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++ diff --git a/internal/lsp/source/call_hierarchy.go b/internal/lsp/source/call_hierarchy.go index 180f6b1a62..62de3ab184 100644 --- a/internal/lsp/source/call_hierarchy.go +++ b/internal/lsp/source/call_hierarchy.go @@ -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 } diff --git a/internal/lsp/source/code_lens.go b/internal/lsp/source/code_lens.go index 966dcf2bc6..427b548ffd 100644 --- a/internal/lsp/source/code_lens.go +++ b/internal/lsp/source/code_lens.go @@ -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 } diff --git a/internal/lsp/source/command.go b/internal/lsp/source/command.go index b927aa067a..66d2f1d70d 100644 --- a/internal/lsp/source/command.go +++ b/internal/lsp/source/command.go @@ -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) } 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 101d2df525..edaf6f0ca9 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 @@ 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{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 "&". 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 2ddb553714..f4137b3a86 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 6dfa26068f..793f1ec4dd 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 @@ 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() - } -} 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 324d5bd15a..bbf59b0221 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 @@ 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) } } diff --git a/internal/lsp/source/completion_labels.go b/internal/lsp/source/completion/completion_labels.go similarity index 99% rename from internal/lsp/source/completion_labels.go rename to internal/lsp/source/completion/completion_labels.go index abb204153e..3c544129e3 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 95% rename from internal/lsp/source/completion_literal.go rename to internal/lsp/source/completion/completion_literal.go index 342306d109..7582e57ad7 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 @@ 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(")") 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 3b82a86a8b..a6ac7aff3c 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 @@ 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 (_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 e13a938ec9..c90a365c3b 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 99% rename from internal/lsp/source/completion_printf_test.go rename to internal/lsp/source/completion/completion_printf_test.go index ab20f27d09..19d295b8d7 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 b6efc02c1e..c254c6567c 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 97% rename from internal/lsp/source/completion_statements.go rename to internal/lsp/source/completion/completion_statements.go index 0badd6cf73..62d3cf0ed0 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 @@ 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 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 2afff8b080..3f06d13b82 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 47d5179c65..27009af1b4 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 0000000000..def769f34c --- /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 97% rename from internal/lsp/source/util_test.go rename to internal/lsp/source/completion/util_test.go index 0dfa0b0ef9..c94d279fba 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 583bb314fa..320d39bcec 100644 --- a/internal/lsp/source/diagnostics.go +++ b/internal/lsp/source/diagnostics.go @@ -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 } diff --git a/internal/lsp/source/extract.go b/internal/lsp/source/extract.go index 0c30fd51c8..84679dce5e 100644 --- a/internal/lsp/source/extract.go +++ b/internal/lsp/source/extract.go @@ -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++ diff --git a/internal/lsp/source/folding_range.go b/internal/lsp/source/folding_range.go index fc3f886628..7061017059 100644 --- a/internal/lsp/source/folding_range.go +++ b/internal/lsp/source/folding_range.go @@ -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, }) } diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go index 5114d50fcf..310bac30bf 100644 --- a/internal/lsp/source/format.go +++ b/internal/lsp/source/format.go @@ -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. diff --git a/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go index f4f7793a40..d18a971905 100644 --- a/internal/lsp/source/highlight.go +++ b/internal/lsp/source/highlight.go @@ -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) } diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go index 5f68a52b0d..d331e72f2e 100644 --- a/internal/lsp/source/hover.go +++ b/internal/lsp/source/hover.go @@ -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) { diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go index f059821b30..88eecc1951 100644 --- a/internal/lsp/source/identifier.go +++ b/internal/lsp/source/identifier.go @@ -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. diff --git a/internal/lsp/source/implementation.go b/internal/lsp/source/implementation.go index feaa8487d5..379471faae 100644 --- a/internal/lsp/source/implementation.go +++ b/internal/lsp/source/implementation.go @@ -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) } diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index b9adbb9f37..dd026cbd8d 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -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 { diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go index 204e185237..cd8a34c4f1 100644 --- a/internal/lsp/source/references.go +++ b/internal/lsp/source/references.go @@ -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, }) } } diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go index fefbf4989b..23e742514a 100644 --- a/internal/lsp/source/rename.go +++ b/internal/lsp/source/rename.go @@ -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 } diff --git a/internal/lsp/source/rename_check.go b/internal/lsp/source/rename_check.go index 2b2088b9fd..9cc3e5ba54 100644 --- a/internal/lsp/source/rename_check.go +++ b/internal/lsp/source/rename_check.go @@ -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 { diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go index 10aee4ffd9..890d8e0feb 100644 --- a/internal/lsp/source/signature_help.go +++ b/internal/lsp/source/signature_help.go @@ -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 diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index c29899ae0e..b16dceeaf3 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -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++ diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go index 883ef5d33b..0565062d5c 100644 --- a/internal/lsp/source/symbols.go +++ b/internal/lsp/source/symbols.go @@ -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 { diff --git a/internal/lsp/source/types_format.go b/internal/lsp/source/types_format.go index 2bf9f26b0f..07784c1673 100644 --- a/internal/lsp/source/types_format.go +++ b/internal/lsp/source/types_format.go @@ -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 } diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go index 63bfd4e84e..5cb50ec7ea 100644 --- a/internal/lsp/source/util.go +++ b/internal/lsp/source/util.go @@ -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