mirror of
https://github.com/golang/go
synced 2024-11-18 16:54:43 -07:00
internal/lsp: add completions of unimported std lib pkgs
Unimported packages may be suggested as completion items. Since these are not yet imported, they should be ranked lower than other candidates. They also require an additional import statement to be valid, which is provided as an AdditionalTextEdit. Adding this import does not use astutil.AddNamedImport, to avoid editing the current ast and work even if there are errors. Additionally, it can be hard to determine what changes need to be made to the source document from the ast, as astutil.AddNamedImport includes a merging pass. Instead, the completion item simply adds another import declaration. Change-Id: Icbde226d843bd49ee3713cafcbd5299d51530695 Reviewed-on: https://go-review.googlesource.com/c/tools/+/190338 Run-TryBot: Suzy Mueller <suzmue@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
a857023c21
commit
caa95bb40b
@ -35,6 +35,7 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara
|
||||
DeepComplete: s.useDeepCompletions,
|
||||
WantDocumentaton: s.wantCompletionDocumentation,
|
||||
WantFullDocumentation: s.hoverKind == fullDocumentation,
|
||||
WantUnimported: s.wantUnimportedCompletions,
|
||||
})
|
||||
if err != nil {
|
||||
log.Print(ctx, "no completions found", tag.Of("At", rng), tag.Of("Failure", err))
|
||||
@ -96,7 +97,11 @@ func (s *Server) toProtocolCompletionItems(ctx context.Context, view source.View
|
||||
if s.insertTextFormat == protocol.SnippetTextFormat {
|
||||
insertText = candidate.Snippet(s.usePlaceholders)
|
||||
}
|
||||
|
||||
addlEdits, err := ToProtocolEdits(m, candidate.AdditionalTextEdits)
|
||||
if err != nil {
|
||||
log.Error(ctx, "failed to convert to protocol edits", err)
|
||||
continue
|
||||
}
|
||||
item := protocol.CompletionItem{
|
||||
Label: candidate.Label,
|
||||
Detail: candidate.Detail,
|
||||
@ -105,7 +110,8 @@ func (s *Server) toProtocolCompletionItems(ctx context.Context, view source.View
|
||||
NewText: insertText,
|
||||
Range: insertionRange,
|
||||
},
|
||||
InsertTextFormat: s.insertTextFormat,
|
||||
InsertTextFormat: s.insertTextFormat,
|
||||
AdditionalTextEdits: addlEdits,
|
||||
// This is a hack so that the client sorts completion results in the order
|
||||
// according to their score. This can be removed upon the resolution of
|
||||
// https://github.com/Microsoft/language-server-protocol/issues/348.
|
||||
|
@ -261,6 +261,10 @@ func (s *Server) processConfig(ctx context.Context, view source.View, config int
|
||||
if useDeepCompletions, ok := c["useDeepCompletions"].(bool); ok {
|
||||
s.useDeepCompletions = useDeepCompletions
|
||||
}
|
||||
// Check if want unimported package completions.
|
||||
if wantUnimportedCompletions, ok := c["wantUnimportedCompletions"].(bool); ok {
|
||||
s.wantUnimportedCompletions = wantUnimportedCompletions
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -100,6 +100,7 @@ func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) {
|
||||
func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
|
||||
defer func() {
|
||||
r.server.useDeepCompletions = false
|
||||
r.server.wantUnimportedCompletions = false
|
||||
r.server.wantCompletionDocumentation = false
|
||||
}()
|
||||
|
||||
@ -112,6 +113,7 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests
|
||||
}
|
||||
|
||||
r.server.useDeepCompletions = strings.Contains(string(src.URI()), "deepcomplete")
|
||||
r.server.wantUnimportedCompletions = strings.Contains(string(src.URI()), "unimported")
|
||||
|
||||
list := r.runCompletion(t, src)
|
||||
|
||||
@ -141,6 +143,7 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests
|
||||
|
||||
for src, want := range snippets {
|
||||
r.server.useDeepCompletions = strings.Contains(string(src.URI()), "deepcomplete")
|
||||
r.server.wantUnimportedCompletions = strings.Contains(string(src.URI()), "unimported")
|
||||
|
||||
list := r.runCompletion(t, src)
|
||||
|
||||
|
@ -82,6 +82,7 @@ type Server struct {
|
||||
hoverKind hoverKind
|
||||
useDeepCompletions bool
|
||||
wantCompletionDocumentation bool
|
||||
wantUnimportedCompletions bool
|
||||
insertTextFormat protocol.InsertTextFormat
|
||||
configurationSupported bool
|
||||
dynamicConfigurationSupported bool
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/internal/imports"
|
||||
"golang.org/x/tools/internal/lsp/fuzzy"
|
||||
"golang.org/x/tools/internal/lsp/snippet"
|
||||
"golang.org/x/tools/internal/span"
|
||||
@ -34,6 +35,14 @@ type CompletionItem struct {
|
||||
|
||||
Kind CompletionItemKind
|
||||
|
||||
// An optional array of additional TextEdits that are applied when
|
||||
// selecting this completion.
|
||||
//
|
||||
// Additional text edits should be used to change text unrelated to the current cursor position
|
||||
// (for example adding an import statement at the top of the file if the completion item will
|
||||
// insert an unqualified type).
|
||||
AdditionalTextEdits []TextEdit
|
||||
|
||||
// Depth is how many levels were searched to find this completion.
|
||||
// For example when completing "foo<>", "fooBar" is depth 0, and
|
||||
// "fooBar.Baz" is depth 1.
|
||||
@ -146,6 +155,12 @@ type completer struct {
|
||||
// ctx is the context associated with this completion request.
|
||||
ctx context.Context
|
||||
|
||||
// filename is the name of the file associated with this completion request.
|
||||
filename string
|
||||
|
||||
// file is the AST of the file associated with this completion request.
|
||||
file *ast.File
|
||||
|
||||
// pos is the position at which the request was triggered.
|
||||
pos token.Pos
|
||||
|
||||
@ -237,7 +252,7 @@ func (c *completer) setSurrounding(ident *ast.Ident) {
|
||||
|
||||
// found adds a candidate completion. We will also search through the object's
|
||||
// members for more candidates.
|
||||
func (c *completer) found(obj types.Object, score float64) error {
|
||||
func (c *completer) found(obj types.Object, score float64, imp *imports.ImportInfo) error {
|
||||
if obj.Pkg() != nil && obj.Pkg() != c.types && !obj.Exported() {
|
||||
return errors.Errorf("%s is inaccessible from %s", obj.Name(), c.types.Path())
|
||||
}
|
||||
@ -262,6 +277,7 @@ func (c *completer) found(obj types.Object, score float64) error {
|
||||
cand := candidate{
|
||||
obj: obj,
|
||||
score: score,
|
||||
imp: imp,
|
||||
}
|
||||
|
||||
if c.matchingType(&cand) {
|
||||
@ -301,12 +317,17 @@ type candidate struct {
|
||||
// expandFuncCall is true if obj should be invoked in the completion.
|
||||
// For example, expandFuncCall=true yields "foo()", expandFuncCall=false yields "foo".
|
||||
expandFuncCall bool
|
||||
|
||||
// imp is the import that needs to be added to this package in order
|
||||
// for this candidate to be valid. nil if no import needed.
|
||||
imp *imports.ImportInfo
|
||||
}
|
||||
|
||||
type CompletionOptions struct {
|
||||
DeepComplete bool
|
||||
WantDocumentaton bool
|
||||
WantFullDocumentation bool
|
||||
WantUnimported bool
|
||||
}
|
||||
|
||||
// Completion returns a list of possible candidates for completion, given a
|
||||
@ -351,6 +372,8 @@ func Completion(ctx context.Context, view View, f GoFile, pos token.Pos, opts Co
|
||||
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
|
||||
view: view,
|
||||
ctx: ctx,
|
||||
filename: f.URI().Filename(),
|
||||
file: file,
|
||||
path: path,
|
||||
pos: pos,
|
||||
seen: make(map[types.Object]bool),
|
||||
@ -469,7 +492,7 @@ func (c *completer) selector(sel *ast.SelectorExpr) error {
|
||||
func (c *completer) packageMembers(pkg *types.PkgName) {
|
||||
scope := pkg.Imported().Scope()
|
||||
for _, name := range scope.Names() {
|
||||
c.found(scope.Lookup(name), stdScore)
|
||||
c.found(scope.Lookup(name), stdScore, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -485,12 +508,12 @@ func (c *completer) methodsAndFields(typ types.Type, addressable bool) error {
|
||||
}
|
||||
|
||||
for i := 0; i < mset.Len(); i++ {
|
||||
c.found(mset.At(i).Obj(), stdScore)
|
||||
c.found(mset.At(i).Obj(), stdScore, nil)
|
||||
}
|
||||
|
||||
// Add fields of T.
|
||||
for _, f := range fieldSelections(typ) {
|
||||
c.found(f, stdScore)
|
||||
c.found(f, stdScore, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -550,7 +573,27 @@ func (c *completer) lexical() error {
|
||||
// If we haven't already added a candidate for an object with this name.
|
||||
if _, ok := seen[obj.Name()]; !ok {
|
||||
seen[obj.Name()] = struct{}{}
|
||||
c.found(obj, score)
|
||||
c.found(obj, score, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.opts.WantUnimported {
|
||||
// Suggest packages that have not been imported yet.
|
||||
pkgs, err := CandidateImports(c.ctx, c.view, c.filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
score := stdScore
|
||||
// Rank unimported packages significantly lower than other results.
|
||||
score *= 0.07
|
||||
for _, pkg := range pkgs {
|
||||
if _, ok := seen[pkg.IdentName]; !ok {
|
||||
// Do not add the unimported packages to seen, since we can have
|
||||
// multiple packages of the same name as completion suggestions, since
|
||||
// only one will be chosen.
|
||||
obj := types.NewPkgName(0, nil, pkg.IdentName, types.NewPackage(pkg.StmtInfo.ImportPath, pkg.IdentName))
|
||||
c.found(obj, score, &pkg.StmtInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -585,7 +628,7 @@ func (c *completer) structLiteralFieldName() error {
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
field := t.Field(i)
|
||||
if !addedFields[field] {
|
||||
c.found(field, highScore)
|
||||
c.found(field, highScore, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
||||
kind CompletionItemKind
|
||||
plainSnippet *snippet.Builder
|
||||
placeholderSnippet *snippet.Builder
|
||||
addlEdits []TextEdit
|
||||
)
|
||||
|
||||
// expandFuncCall mutates the completion label, detail, and snippets
|
||||
@ -85,16 +86,27 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
||||
detail = fmt.Sprintf("%q", obj.Imported().Path())
|
||||
}
|
||||
|
||||
// If this candidate needs an additional import statement,
|
||||
// add the additional text edits needed.
|
||||
if cand.imp != nil {
|
||||
edit, err := AddNamedImport(c.view.Session().Cache().FileSet(), c.file, cand.imp.Name, cand.imp.ImportPath)
|
||||
if err != nil {
|
||||
return CompletionItem{}, err
|
||||
}
|
||||
addlEdits = append(addlEdits, edit...)
|
||||
}
|
||||
|
||||
detail = strings.TrimPrefix(detail, "untyped ")
|
||||
item := CompletionItem{
|
||||
Label: label,
|
||||
InsertText: insert,
|
||||
Detail: detail,
|
||||
Kind: kind,
|
||||
Score: cand.score,
|
||||
Depth: len(c.deepState.chain),
|
||||
plainSnippet: plainSnippet,
|
||||
placeholderSnippet: placeholderSnippet,
|
||||
Label: label,
|
||||
InsertText: insert,
|
||||
AdditionalTextEdits: addlEdits,
|
||||
Detail: detail,
|
||||
Kind: kind,
|
||||
Score: cand.score,
|
||||
Depth: len(c.deepState.chain),
|
||||
plainSnippet: plainSnippet,
|
||||
placeholderSnippet: placeholderSnippet,
|
||||
}
|
||||
// TODO(rstambler): Log errors when this feature is enabled.
|
||||
if c.opts.WantDocumentaton {
|
||||
|
@ -175,6 +175,35 @@ func AllImportsFixes(ctx context.Context, view View, f GoFile, rng span.Range) (
|
||||
return edits, editsPerFix, nil
|
||||
}
|
||||
|
||||
// AllImportsFixes formats f for each possible fix to the imports.
|
||||
// In addition to returning the result of applying all edits,
|
||||
// it returns a list of fixes that could be applied to the file, with the
|
||||
// corresponding TextEdits that would be needed to apply that fix.
|
||||
func CandidateImports(ctx context.Context, view View, filename string) (pkgs []imports.ImportFix, err error) {
|
||||
ctx, done := trace.StartSpan(ctx, "source.CandidateImports")
|
||||
defer done()
|
||||
|
||||
options := &imports.Options{
|
||||
// Defaults.
|
||||
AllErrors: true,
|
||||
Comments: true,
|
||||
Fragment: true,
|
||||
FormatOnly: false,
|
||||
TabIndent: true,
|
||||
TabWidth: 8,
|
||||
}
|
||||
importFn := func(opts *imports.Options) error {
|
||||
pkgs, err = imports.GetAllCandidates(filename, opts)
|
||||
return err
|
||||
}
|
||||
err = view.RunProcessEnvFunc(ctx, importFn, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
// hasParseErrors returns true if the given file has parse errors.
|
||||
func hasParseErrors(pkg Package, uri span.URI) bool {
|
||||
for _, err := range pkg.GetErrors() {
|
||||
|
133
internal/lsp/source/imports.go
Normal file
133
internal/lsp/source/imports.go
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
// Taken and then modified from golang.org/x/tools/go/ast/astutil.
|
||||
//
|
||||
// We currently choose to create our own version of AddNamedImport for the following reasons:
|
||||
// 1. We do not want to edit the current ast. This is so that we can use the same ast
|
||||
// to get the changes from multiple distinct modifications.
|
||||
// 2. We need the changes that *only* affect the import declarations, because the edits
|
||||
// are not allowed to overlap with the position in the source that is being edited.
|
||||
// astutil.AddNamedImport makes it hard to determine what changes need to be made
|
||||
// to the source document from the ast, as astutil.AddNamedImport includes a merging pass.
|
||||
|
||||
// AddNamedImport adds the import with the given name and path to the file f, if absent.
|
||||
// If name is not empty, it is used to rename the import.
|
||||
//
|
||||
// For example, calling
|
||||
// AddNamedImport(fset, f, "pathpkg", "path")
|
||||
// adds
|
||||
// import pathpkg "path"
|
||||
//
|
||||
// AddNamedImport only returns edits that affect the import declarations.
|
||||
func AddNamedImport(fset *token.FileSet, f *ast.File, name, path string) (edits []TextEdit, err error) {
|
||||
if alreadyImports(f, name, path) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
newImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: strconv.Quote(path),
|
||||
},
|
||||
}
|
||||
if name != "" {
|
||||
newImport.Name = &ast.Ident{Name: name}
|
||||
}
|
||||
|
||||
// TODO(suzmue): insert the import statement in the location that would be chosen
|
||||
// by astutil.AddNamedImport
|
||||
// Find the last import decl.
|
||||
var lastImport = -1 // index in f.Decls of the file's final import decl
|
||||
for i, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
lastImport = i
|
||||
}
|
||||
}
|
||||
|
||||
// Add an import decl after the last import.
|
||||
impDecl := &ast.GenDecl{
|
||||
Tok: token.IMPORT,
|
||||
}
|
||||
impDecl.Specs = append(impDecl.Specs, newImport)
|
||||
|
||||
var insertPos token.Pos
|
||||
if lastImport >= 0 {
|
||||
insertPos = f.Decls[lastImport].End()
|
||||
} else {
|
||||
// There are no existing imports.
|
||||
// Our new import, preceded by a blank line, goes after the package declaration
|
||||
// and after the comment, if any, that starts on the same line as the
|
||||
// package declaration.
|
||||
insertPos = f.Name.End()
|
||||
|
||||
file := fset.File(f.Package)
|
||||
pkgLine := file.Line(f.Package)
|
||||
for _, c := range f.Comments {
|
||||
if file.Line(c.Pos()) > pkgLine {
|
||||
break
|
||||
}
|
||||
insertPos = c.End()
|
||||
}
|
||||
}
|
||||
|
||||
// Print this import declaration.
|
||||
var buf bytes.Buffer
|
||||
format.Node(&buf, fset, impDecl)
|
||||
newText := "\n\n" + buf.String() + "\n"
|
||||
|
||||
rng := span.NewRange(fset, insertPos, insertPos)
|
||||
spn, err := rng.Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
edits = append(edits, TextEdit{
|
||||
Span: spn,
|
||||
NewText: newText,
|
||||
})
|
||||
return edits, nil
|
||||
}
|
||||
|
||||
// alreadyImports reports whether f has an import with the specified name and path.
|
||||
func alreadyImports(f *ast.File, name, path string) bool {
|
||||
for _, s := range f.Imports {
|
||||
if importName(s) == name && importPath(s) == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// importName returns the name of s,
|
||||
// or "" if the import is not named.
|
||||
func importName(s *ast.ImportSpec) string {
|
||||
if s.Name == nil {
|
||||
return ""
|
||||
}
|
||||
return s.Name.Name
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
306
internal/lsp/source/imports_test.go
Normal file
306
internal/lsp/source/imports_test.go
Normal file
@ -0,0 +1,306 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var fset = token.NewFileSet()
|
||||
|
||||
func parse(t *testing.T, name, in string) *ast.File {
|
||||
file, err := parser.ParseFile(fset, name, in, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatalf("%s parse: %v", name, err)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
func print(t *testing.T, name string, f *ast.File) string {
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, fset, f); err != nil {
|
||||
t.Fatalf("%s gofmt: %v", name, err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type test struct {
|
||||
name string
|
||||
renamedPkg string
|
||||
pkg string
|
||||
in string
|
||||
want []importInfo
|
||||
unchanged bool // Expect added/deleted return value to be false.
|
||||
}
|
||||
|
||||
type importInfo struct {
|
||||
name string
|
||||
path string
|
||||
}
|
||||
|
||||
var addTests = []test{
|
||||
{
|
||||
name: "leave os alone",
|
||||
pkg: "os",
|
||||
in: `package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "os",
|
||||
},
|
||||
},
|
||||
unchanged: true,
|
||||
},
|
||||
{
|
||||
name: "package statement only",
|
||||
pkg: "os",
|
||||
in: `package main
|
||||
`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "os",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "package statement no new line",
|
||||
pkg: "os",
|
||||
in: `package main`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "os",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "package statement comments",
|
||||
pkg: "os",
|
||||
in: `// This is a comment
|
||||
package main // This too`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "os",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "package statement multiline comments",
|
||||
pkg: "os",
|
||||
in: `package main /* This is a multiline comment
|
||||
and it extends
|
||||
further down*/`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "os",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "import c",
|
||||
pkg: "os",
|
||||
in: `package main
|
||||
|
||||
import "C"
|
||||
`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "os",
|
||||
},
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "C",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing imports",
|
||||
pkg: "os",
|
||||
in: `package main
|
||||
|
||||
import "io"
|
||||
`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "os",
|
||||
},
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "io",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing imports with comment",
|
||||
pkg: "os",
|
||||
in: `package main
|
||||
|
||||
import "io" // A comment
|
||||
`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "os",
|
||||
},
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "io",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing imports multiline comment",
|
||||
pkg: "os",
|
||||
in: `package main
|
||||
|
||||
import "io" /* A comment
|
||||
that
|
||||
extends */
|
||||
`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "os",
|
||||
},
|
||||
importInfo{
|
||||
name: "",
|
||||
path: "io",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "renamed import",
|
||||
renamedPkg: "o",
|
||||
pkg: "os",
|
||||
in: `package main
|
||||
`,
|
||||
want: []importInfo{
|
||||
importInfo{
|
||||
name: "o",
|
||||
path: "os",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAddImport(t *testing.T) {
|
||||
for _, test := range addTests {
|
||||
file := parse(t, test.name, test.in)
|
||||
var before bytes.Buffer
|
||||
ast.Fprint(&before, fset, file, nil)
|
||||
edits, err := AddNamedImport(fset, file, test.renamedPkg, test.pkg)
|
||||
if err != nil && !test.unchanged {
|
||||
t.Errorf("error adding import: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply the edits and parse the file.
|
||||
got := applyEdits(test.in, edits)
|
||||
gotFile := parse(t, test.name, got)
|
||||
|
||||
compareImports(t, fmt.Sprintf("first run: %s:\n", test.name), gotFile.Imports, test.want)
|
||||
|
||||
// AddNamedImport should be idempotent. Verify that by calling it again,
|
||||
// expecting no change to the AST, and the returned added value to always be false.
|
||||
edits, err = AddNamedImport(fset, gotFile, test.renamedPkg, test.pkg)
|
||||
if err != nil && !test.unchanged {
|
||||
t.Errorf("error adding import: %s", err)
|
||||
continue
|
||||
}
|
||||
// Apply the edits and parse the file.
|
||||
got = applyEdits(got, edits)
|
||||
gotFile = parse(t, test.name, got)
|
||||
|
||||
compareImports(t, test.name, gotFile.Imports, test.want)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoubleAddNamedImport(t *testing.T) {
|
||||
name := "doublenamedimport"
|
||||
in := "package main\n"
|
||||
file := parse(t, name, in)
|
||||
// Add a named import
|
||||
edits, err := AddNamedImport(fset, file, "o", "os")
|
||||
if err != nil {
|
||||
t.Errorf("error adding import: %s", err)
|
||||
return
|
||||
}
|
||||
got := applyEdits(in, edits)
|
||||
log.Println(got)
|
||||
gotFile := parse(t, name, got)
|
||||
|
||||
// Add a second named import
|
||||
edits, err = AddNamedImport(fset, gotFile, "i", "io")
|
||||
if err != nil {
|
||||
t.Errorf("error adding import: %s", err)
|
||||
return
|
||||
}
|
||||
got = applyEdits(got, edits)
|
||||
gotFile = parse(t, name, got)
|
||||
|
||||
want := []importInfo{
|
||||
importInfo{
|
||||
name: "o",
|
||||
path: "os",
|
||||
},
|
||||
importInfo{
|
||||
name: "i",
|
||||
path: "io",
|
||||
},
|
||||
}
|
||||
compareImports(t, "", gotFile.Imports, want)
|
||||
}
|
||||
|
||||
func compareImports(t *testing.T, prefix string, got []*ast.ImportSpec, want []importInfo) {
|
||||
if len(got) != len(want) {
|
||||
t.Errorf("%s\ngot %d imports\nwant %d", prefix, len(got), len(want))
|
||||
return
|
||||
}
|
||||
|
||||
for _, imp := range got {
|
||||
name := importName(imp)
|
||||
path := importPath(imp)
|
||||
found := false
|
||||
for _, want := range want {
|
||||
if want.name == name && want.path == path {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("%s\n\ngot unexpected import: name: %q,path: %q", prefix, name, path)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applyEdits(contents string, edits []TextEdit) string {
|
||||
res := contents
|
||||
|
||||
// Apply the edits from the end of the file forward
|
||||
// to preserve the offsets
|
||||
for i := len(edits) - 1; i >= 0; i-- {
|
||||
edit := edits[i]
|
||||
start := edit.Span.Start().Offset()
|
||||
end := edit.Span.End().Offset()
|
||||
tmp := res[0:start] + edit.NewText
|
||||
res = tmp + res[end:]
|
||||
}
|
||||
return res
|
||||
}
|
@ -93,9 +93,11 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests
|
||||
}
|
||||
pos := tok.Pos(src.Start().Offset())
|
||||
deepComplete := strings.Contains(string(src.URI()), "deepcomplete")
|
||||
unimported := strings.Contains(string(src.URI()), "unimported")
|
||||
list, surrounding, err := source.Completion(ctx, r.view, f.(source.GoFile), pos, source.CompletionOptions{
|
||||
DeepComplete: deepComplete,
|
||||
WantDocumentaton: true,
|
||||
WantUnimported: unimported,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed for %v: %v", src, err)
|
||||
|
115
internal/lsp/testdata/unimported/mkunimported.go
vendored
Normal file
115
internal/lsp/testdata/unimported/mkunimported.go
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
// +build ignore
|
||||
|
||||
// mkunimported generates the unimported.go file, containing the Go standard
|
||||
// library packages.
|
||||
// The completion items from the std library are computed in the same way as in the
|
||||
// golang.org/x/tools/internal/imports.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
pkgpath "path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func mustOpen(name string) io.Reader {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func api(base string) string {
|
||||
return filepath.Join(runtime.GOROOT(), "api", base)
|
||||
}
|
||||
|
||||
var sym = regexp.MustCompile(`^pkg (\S+).*?, (?:var|func|type|const) ([A-Z]\w*)`)
|
||||
|
||||
var unsafeSyms = map[string]bool{"Alignof": true, "ArbitraryType": true, "Offsetof": true, "Pointer": true, "Sizeof": true}
|
||||
|
||||
func main() {
|
||||
var buf bytes.Buffer
|
||||
outf := func(format string, args ...interface{}) {
|
||||
fmt.Fprintf(&buf, format, args...)
|
||||
}
|
||||
outf("// Code generated by mkstdlib.go. DO NOT EDIT.\n\n")
|
||||
outf("package unimported\n")
|
||||
outf("func _() {\n")
|
||||
f := io.MultiReader(
|
||||
mustOpen(api("go1.txt")),
|
||||
mustOpen(api("go1.1.txt")),
|
||||
mustOpen(api("go1.2.txt")),
|
||||
mustOpen(api("go1.3.txt")),
|
||||
mustOpen(api("go1.4.txt")),
|
||||
mustOpen(api("go1.5.txt")),
|
||||
mustOpen(api("go1.6.txt")),
|
||||
mustOpen(api("go1.7.txt")),
|
||||
mustOpen(api("go1.8.txt")),
|
||||
mustOpen(api("go1.9.txt")),
|
||||
mustOpen(api("go1.10.txt")),
|
||||
mustOpen(api("go1.11.txt")),
|
||||
mustOpen(api("go1.12.txt")),
|
||||
)
|
||||
sc := bufio.NewScanner(f)
|
||||
|
||||
pkgs := map[string]bool{
|
||||
"unsafe": true,
|
||||
"syscall/js": true,
|
||||
}
|
||||
paths := []string{"unsafe", "syscall/js"}
|
||||
for sc.Scan() {
|
||||
l := sc.Text()
|
||||
has := func(v string) bool { return strings.Contains(l, v) }
|
||||
if has("struct, ") || has("interface, ") || has(", method (") {
|
||||
continue
|
||||
}
|
||||
if m := sym.FindStringSubmatch(l); m != nil {
|
||||
path, _ := m[1], m[2]
|
||||
|
||||
if _, ok := pkgs[path]; !ok {
|
||||
pkgs[path] = true
|
||||
paths = append(paths, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sort.Strings(paths)
|
||||
|
||||
var markers []string
|
||||
for _, path := range paths {
|
||||
marker := strings.ReplaceAll(path, "/", "slash")
|
||||
markers = append(markers, marker)
|
||||
}
|
||||
outf(" //@complete(\"\", %s)\n", strings.Join(markers, ", "))
|
||||
outf("}\n")
|
||||
outf("// Create markers for unimported std lib packages. Only for use by this test.\n")
|
||||
|
||||
for i, path := range paths {
|
||||
name := pkgpath.Base(path)
|
||||
marker := markers[i]
|
||||
outf("/* %s *///@item(%s, \"%s\", \"\\\"%s\\\"\", \"package\")\n", name, marker, name, path)
|
||||
}
|
||||
|
||||
fmtbuf, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile("unimported.go", fmtbuf, 0666)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
151
internal/lsp/testdata/unimported/unimported.go
vendored
Normal file
151
internal/lsp/testdata/unimported/unimported.go
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
// Code generated by mkstdlib.go. DO NOT EDIT.
|
||||
|
||||
package unimported
|
||||
|
||||
func _() {
|
||||
//@complete("", archiveslashtar, archiveslashzip, bufio, bytes, compressslashbzip2, compressslashflate, compressslashgzip, compressslashlzw, compressslashzlib, containerslashheap, containerslashlist, containerslashring, context, crypto, cryptoslashaes, cryptoslashcipher, cryptoslashdes, cryptoslashdsa, cryptoslashecdsa, cryptoslashelliptic, cryptoslashhmac, cryptoslashmd5, cryptoslashrand, cryptoslashrc4, cryptoslashrsa, cryptoslashsha1, cryptoslashsha256, cryptoslashsha512, cryptoslashsubtle, cryptoslashtls, cryptoslashx509, cryptoslashx509slashpkix, databaseslashsql, databaseslashsqlslashdriver, debugslashdwarf, debugslashelf, debugslashgosym, debugslashmacho, debugslashpe, debugslashplan9obj, encoding, encodingslashascii85, encodingslashasn1, encodingslashbase32, encodingslashbase64, encodingslashbinary, encodingslashcsv, encodingslashgob, encodingslashhex, encodingslashjson, encodingslashpem, encodingslashxml, errors, expvar, flag, fmt, goslashast, goslashbuild, goslashconstant, goslashdoc, goslashformat, goslashimporter, goslashparser, goslashprinter, goslashscanner, goslashtoken, goslashtypes, hash, hashslashadler32, hashslashcrc32, hashslashcrc64, hashslashfnv, html, htmlslashtemplate, image, imageslashcolor, imageslashcolorslashpalette, imageslashdraw, imageslashgif, imageslashjpeg, imageslashpng, indexslashsuffixarray, io, ioslashioutil, log, logslashsyslog, math, mathslashbig, mathslashbits, mathslashcmplx, mathslashrand, mime, mimeslashmultipart, mimeslashquotedprintable, net, netslashhttp, netslashhttpslashcgi, netslashhttpslashcookiejar, netslashhttpslashfcgi, netslashhttpslashhttptest, netslashhttpslashhttptrace, netslashhttpslashhttputil, netslashhttpslashpprof, netslashmail, netslashrpc, netslashrpcslashjsonrpc, netslashsmtp, netslashtextproto, netslashurl, os, osslashexec, osslashsignal, osslashuser, path, pathslashfilepath, plugin, reflect, regexp, regexpslashsyntax, runtime, runtimeslashdebug, runtimeslashpprof, runtimeslashtrace, sort, strconv, strings, sync, syncslashatomic, syscall, syscallslashjs, testing, testingslashiotest, testingslashquick, textslashscanner, textslashtabwriter, textslashtemplate, textslashtemplateslashparse, time, unicode, unicodeslashutf16, unicodeslashutf8, unsafe)
|
||||
}
|
||||
|
||||
// Create markers for unimported std lib packages. Only for use by this test.
|
||||
/* tar */ //@item(archiveslashtar, "tar", "\"archive/tar\"", "package")
|
||||
/* zip */ //@item(archiveslashzip, "zip", "\"archive/zip\"", "package")
|
||||
/* bufio */ //@item(bufio, "bufio", "\"bufio\"", "package")
|
||||
/* bytes */ //@item(bytes, "bytes", "\"bytes\"", "package")
|
||||
/* bzip2 */ //@item(compressslashbzip2, "bzip2", "\"compress/bzip2\"", "package")
|
||||
/* flate */ //@item(compressslashflate, "flate", "\"compress/flate\"", "package")
|
||||
/* gzip */ //@item(compressslashgzip, "gzip", "\"compress/gzip\"", "package")
|
||||
/* lzw */ //@item(compressslashlzw, "lzw", "\"compress/lzw\"", "package")
|
||||
/* zlib */ //@item(compressslashzlib, "zlib", "\"compress/zlib\"", "package")
|
||||
/* heap */ //@item(containerslashheap, "heap", "\"container/heap\"", "package")
|
||||
/* list */ //@item(containerslashlist, "list", "\"container/list\"", "package")
|
||||
/* ring */ //@item(containerslashring, "ring", "\"container/ring\"", "package")
|
||||
/* context */ //@item(context, "context", "\"context\"", "package")
|
||||
/* crypto */ //@item(crypto, "crypto", "\"crypto\"", "package")
|
||||
/* aes */ //@item(cryptoslashaes, "aes", "\"crypto/aes\"", "package")
|
||||
/* cipher */ //@item(cryptoslashcipher, "cipher", "\"crypto/cipher\"", "package")
|
||||
/* des */ //@item(cryptoslashdes, "des", "\"crypto/des\"", "package")
|
||||
/* dsa */ //@item(cryptoslashdsa, "dsa", "\"crypto/dsa\"", "package")
|
||||
/* ecdsa */ //@item(cryptoslashecdsa, "ecdsa", "\"crypto/ecdsa\"", "package")
|
||||
/* elliptic */ //@item(cryptoslashelliptic, "elliptic", "\"crypto/elliptic\"", "package")
|
||||
/* hmac */ //@item(cryptoslashhmac, "hmac", "\"crypto/hmac\"", "package")
|
||||
/* md5 */ //@item(cryptoslashmd5, "md5", "\"crypto/md5\"", "package")
|
||||
/* rand */ //@item(cryptoslashrand, "rand", "\"crypto/rand\"", "package")
|
||||
/* rc4 */ //@item(cryptoslashrc4, "rc4", "\"crypto/rc4\"", "package")
|
||||
/* rsa */ //@item(cryptoslashrsa, "rsa", "\"crypto/rsa\"", "package")
|
||||
/* sha1 */ //@item(cryptoslashsha1, "sha1", "\"crypto/sha1\"", "package")
|
||||
/* sha256 */ //@item(cryptoslashsha256, "sha256", "\"crypto/sha256\"", "package")
|
||||
/* sha512 */ //@item(cryptoslashsha512, "sha512", "\"crypto/sha512\"", "package")
|
||||
/* subtle */ //@item(cryptoslashsubtle, "subtle", "\"crypto/subtle\"", "package")
|
||||
/* tls */ //@item(cryptoslashtls, "tls", "\"crypto/tls\"", "package")
|
||||
/* x509 */ //@item(cryptoslashx509, "x509", "\"crypto/x509\"", "package")
|
||||
/* pkix */ //@item(cryptoslashx509slashpkix, "pkix", "\"crypto/x509/pkix\"", "package")
|
||||
/* sql */ //@item(databaseslashsql, "sql", "\"database/sql\"", "package")
|
||||
/* driver */ //@item(databaseslashsqlslashdriver, "driver", "\"database/sql/driver\"", "package")
|
||||
/* dwarf */ //@item(debugslashdwarf, "dwarf", "\"debug/dwarf\"", "package")
|
||||
/* elf */ //@item(debugslashelf, "elf", "\"debug/elf\"", "package")
|
||||
/* gosym */ //@item(debugslashgosym, "gosym", "\"debug/gosym\"", "package")
|
||||
/* macho */ //@item(debugslashmacho, "macho", "\"debug/macho\"", "package")
|
||||
/* pe */ //@item(debugslashpe, "pe", "\"debug/pe\"", "package")
|
||||
/* plan9obj */ //@item(debugslashplan9obj, "plan9obj", "\"debug/plan9obj\"", "package")
|
||||
/* encoding */ //@item(encoding, "encoding", "\"encoding\"", "package")
|
||||
/* ascii85 */ //@item(encodingslashascii85, "ascii85", "\"encoding/ascii85\"", "package")
|
||||
/* asn1 */ //@item(encodingslashasn1, "asn1", "\"encoding/asn1\"", "package")
|
||||
/* base32 */ //@item(encodingslashbase32, "base32", "\"encoding/base32\"", "package")
|
||||
/* base64 */ //@item(encodingslashbase64, "base64", "\"encoding/base64\"", "package")
|
||||
/* binary */ //@item(encodingslashbinary, "binary", "\"encoding/binary\"", "package")
|
||||
/* csv */ //@item(encodingslashcsv, "csv", "\"encoding/csv\"", "package")
|
||||
/* gob */ //@item(encodingslashgob, "gob", "\"encoding/gob\"", "package")
|
||||
/* hex */ //@item(encodingslashhex, "hex", "\"encoding/hex\"", "package")
|
||||
/* json */ //@item(encodingslashjson, "json", "\"encoding/json\"", "package")
|
||||
/* pem */ //@item(encodingslashpem, "pem", "\"encoding/pem\"", "package")
|
||||
/* xml */ //@item(encodingslashxml, "xml", "\"encoding/xml\"", "package")
|
||||
/* errors */ //@item(errors, "errors", "\"errors\"", "package")
|
||||
/* expvar */ //@item(expvar, "expvar", "\"expvar\"", "package")
|
||||
/* flag */ //@item(flag, "flag", "\"flag\"", "package")
|
||||
/* fmt */ //@item(fmt, "fmt", "\"fmt\"", "package")
|
||||
/* ast */ //@item(goslashast, "ast", "\"go/ast\"", "package")
|
||||
/* build */ //@item(goslashbuild, "build", "\"go/build\"", "package")
|
||||
/* constant */ //@item(goslashconstant, "constant", "\"go/constant\"", "package")
|
||||
/* doc */ //@item(goslashdoc, "doc", "\"go/doc\"", "package")
|
||||
/* format */ //@item(goslashformat, "format", "\"go/format\"", "package")
|
||||
/* importer */ //@item(goslashimporter, "importer", "\"go/importer\"", "package")
|
||||
/* parser */ //@item(goslashparser, "parser", "\"go/parser\"", "package")
|
||||
/* printer */ //@item(goslashprinter, "printer", "\"go/printer\"", "package")
|
||||
/* scanner */ //@item(goslashscanner, "scanner", "\"go/scanner\"", "package")
|
||||
/* token */ //@item(goslashtoken, "token", "\"go/token\"", "package")
|
||||
/* types */ //@item(goslashtypes, "types", "\"go/types\"", "package")
|
||||
/* hash */ //@item(hash, "hash", "\"hash\"", "package")
|
||||
/* adler32 */ //@item(hashslashadler32, "adler32", "\"hash/adler32\"", "package")
|
||||
/* crc32 */ //@item(hashslashcrc32, "crc32", "\"hash/crc32\"", "package")
|
||||
/* crc64 */ //@item(hashslashcrc64, "crc64", "\"hash/crc64\"", "package")
|
||||
/* fnv */ //@item(hashslashfnv, "fnv", "\"hash/fnv\"", "package")
|
||||
/* html */ //@item(html, "html", "\"html\"", "package")
|
||||
/* template */ //@item(htmlslashtemplate, "template", "\"html/template\"", "package")
|
||||
/* image */ //@item(image, "image", "\"image\"", "package")
|
||||
/* color */ //@item(imageslashcolor, "color", "\"image/color\"", "package")
|
||||
/* palette */ //@item(imageslashcolorslashpalette, "palette", "\"image/color/palette\"", "package")
|
||||
/* draw */ //@item(imageslashdraw, "draw", "\"image/draw\"", "package")
|
||||
/* gif */ //@item(imageslashgif, "gif", "\"image/gif\"", "package")
|
||||
/* jpeg */ //@item(imageslashjpeg, "jpeg", "\"image/jpeg\"", "package")
|
||||
/* png */ //@item(imageslashpng, "png", "\"image/png\"", "package")
|
||||
/* suffixarray */ //@item(indexslashsuffixarray, "suffixarray", "\"index/suffixarray\"", "package")
|
||||
/* io */ //@item(io, "io", "\"io\"", "package")
|
||||
/* ioutil */ //@item(ioslashioutil, "ioutil", "\"io/ioutil\"", "package")
|
||||
/* log */ //@item(log, "log", "\"log\"", "package")
|
||||
/* syslog */ //@item(logslashsyslog, "syslog", "\"log/syslog\"", "package")
|
||||
/* math */ //@item(math, "math", "\"math\"", "package")
|
||||
/* big */ //@item(mathslashbig, "big", "\"math/big\"", "package")
|
||||
/* bits */ //@item(mathslashbits, "bits", "\"math/bits\"", "package")
|
||||
/* cmplx */ //@item(mathslashcmplx, "cmplx", "\"math/cmplx\"", "package")
|
||||
/* rand */ //@item(mathslashrand, "rand", "\"math/rand\"", "package")
|
||||
/* mime */ //@item(mime, "mime", "\"mime\"", "package")
|
||||
/* multipart */ //@item(mimeslashmultipart, "multipart", "\"mime/multipart\"", "package")
|
||||
/* quotedprintable */ //@item(mimeslashquotedprintable, "quotedprintable", "\"mime/quotedprintable\"", "package")
|
||||
/* net */ //@item(net, "net", "\"net\"", "package")
|
||||
/* http */ //@item(netslashhttp, "http", "\"net/http\"", "package")
|
||||
/* cgi */ //@item(netslashhttpslashcgi, "cgi", "\"net/http/cgi\"", "package")
|
||||
/* cookiejar */ //@item(netslashhttpslashcookiejar, "cookiejar", "\"net/http/cookiejar\"", "package")
|
||||
/* fcgi */ //@item(netslashhttpslashfcgi, "fcgi", "\"net/http/fcgi\"", "package")
|
||||
/* httptest */ //@item(netslashhttpslashhttptest, "httptest", "\"net/http/httptest\"", "package")
|
||||
/* httptrace */ //@item(netslashhttpslashhttptrace, "httptrace", "\"net/http/httptrace\"", "package")
|
||||
/* httputil */ //@item(netslashhttpslashhttputil, "httputil", "\"net/http/httputil\"", "package")
|
||||
/* pprof */ //@item(netslashhttpslashpprof, "pprof", "\"net/http/pprof\"", "package")
|
||||
/* mail */ //@item(netslashmail, "mail", "\"net/mail\"", "package")
|
||||
/* rpc */ //@item(netslashrpc, "rpc", "\"net/rpc\"", "package")
|
||||
/* jsonrpc */ //@item(netslashrpcslashjsonrpc, "jsonrpc", "\"net/rpc/jsonrpc\"", "package")
|
||||
/* smtp */ //@item(netslashsmtp, "smtp", "\"net/smtp\"", "package")
|
||||
/* textproto */ //@item(netslashtextproto, "textproto", "\"net/textproto\"", "package")
|
||||
/* url */ //@item(netslashurl, "url", "\"net/url\"", "package")
|
||||
/* os */ //@item(os, "os", "\"os\"", "package")
|
||||
/* exec */ //@item(osslashexec, "exec", "\"os/exec\"", "package")
|
||||
/* signal */ //@item(osslashsignal, "signal", "\"os/signal\"", "package")
|
||||
/* user */ //@item(osslashuser, "user", "\"os/user\"", "package")
|
||||
/* path */ //@item(path, "path", "\"path\"", "package")
|
||||
/* filepath */ //@item(pathslashfilepath, "filepath", "\"path/filepath\"", "package")
|
||||
/* plugin */ //@item(plugin, "plugin", "\"plugin\"", "package")
|
||||
/* reflect */ //@item(reflect, "reflect", "\"reflect\"", "package")
|
||||
/* regexp */ //@item(regexp, "regexp", "\"regexp\"", "package")
|
||||
/* syntax */ //@item(regexpslashsyntax, "syntax", "\"regexp/syntax\"", "package")
|
||||
/* runtime */ //@item(runtime, "runtime", "\"runtime\"", "package")
|
||||
/* debug */ //@item(runtimeslashdebug, "debug", "\"runtime/debug\"", "package")
|
||||
/* pprof */ //@item(runtimeslashpprof, "pprof", "\"runtime/pprof\"", "package")
|
||||
/* trace */ //@item(runtimeslashtrace, "trace", "\"runtime/trace\"", "package")
|
||||
/* sort */ //@item(sort, "sort", "\"sort\"", "package")
|
||||
/* strconv */ //@item(strconv, "strconv", "\"strconv\"", "package")
|
||||
/* strings */ //@item(strings, "strings", "\"strings\"", "package")
|
||||
/* sync */ //@item(sync, "sync", "\"sync\"", "package")
|
||||
/* atomic */ //@item(syncslashatomic, "atomic", "\"sync/atomic\"", "package")
|
||||
/* syscall */ //@item(syscall, "syscall", "\"syscall\"", "package")
|
||||
/* js */ //@item(syscallslashjs, "js", "\"syscall/js\"", "package")
|
||||
/* testing */ //@item(testing, "testing", "\"testing\"", "package")
|
||||
/* iotest */ //@item(testingslashiotest, "iotest", "\"testing/iotest\"", "package")
|
||||
/* quick */ //@item(testingslashquick, "quick", "\"testing/quick\"", "package")
|
||||
/* scanner */ //@item(textslashscanner, "scanner", "\"text/scanner\"", "package")
|
||||
/* tabwriter */ //@item(textslashtabwriter, "tabwriter", "\"text/tabwriter\"", "package")
|
||||
/* template */ //@item(textslashtemplate, "template", "\"text/template\"", "package")
|
||||
/* parse */ //@item(textslashtemplateslashparse, "parse", "\"text/template/parse\"", "package")
|
||||
/* time */ //@item(time, "time", "\"time\"", "package")
|
||||
/* unicode */ //@item(unicode, "unicode", "\"unicode\"", "package")
|
||||
/* utf16 */ //@item(unicodeslashutf16, "utf16", "\"unicode/utf16\"", "package")
|
||||
/* utf8 */ //@item(unicodeslashutf8, "utf8", "\"unicode/utf8\"", "package")
|
||||
/* unsafe */ //@item(unsafe, "unsafe", "\"unsafe\"", "package")
|
@ -29,7 +29,7 @@ import (
|
||||
// We hardcode the expected number of test cases to ensure that all tests
|
||||
// are being executed. If a test is added, this number must be changed.
|
||||
const (
|
||||
ExpectedCompletionsCount = 145
|
||||
ExpectedCompletionsCount = 146
|
||||
ExpectedCompletionSnippetCount = 15
|
||||
ExpectedDiagnosticsCount = 21
|
||||
ExpectedFormatCount = 6
|
||||
|
Loading…
Reference in New Issue
Block a user