internal/lsp/completion: indicate completion candidates that are deprecated

In LSP, CompletionItems can say if they are for deprecated names. This
CL implements that for items where the doc comments contain a line
starting // Deprecated.

Semantic tokens now similarly mark deprecated tokens, but in vscode
the default theme doesn't change the display, and the customization
options seem limited to:

    "editor.semanticTokenColorCustomizations": {
        "rules": {
          // only foreground, bold, underline, italic
          "*.deprecated": {"italic": true}
        }
    },

Change-Id: I93ccc227bf4e1e30a4f23b40da4d2cbafe1cd925
Reviewed-on: https://go-review.googlesource.com/c/tools/+/313509
Run-TryBot: Peter Weinberger <pjw@google.com>
Trust: Peter Weinberger <pjw@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
pjw 2021-04-26 09:12:45 -04:00 committed by Peter Weinberger
parent 9b9633e07a
commit edbe9bef04
7 changed files with 89 additions and 2 deletions

View File

@ -456,3 +456,50 @@ func _() {
}
})
}
func TestCompletionDeprecation(t *testing.T) {
const files = `
-- go.mod --
module test.com
go 1.16
-- prog.go --
package waste
// Deprecated, use newFoof
func fooFunc() bool {
return false
}
// Deprecated
const badPi = 3.14
func doit() {
if fooF
panic()
x := badP
}
`
Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("prog.go")
pos := env.RegexpSearch("prog.go", "if fooF")
pos.Column += len("if fooF")
completions := env.Completion("prog.go", pos)
diff := compareCompletionResults([]string{"fooFunc"}, completions.Items)
if diff != "" {
t.Error(diff)
}
if completions.Items[0].Tags == nil {
t.Errorf("expected Tags to show deprecation %#v", diff[0])
}
pos = env.RegexpSearch("prog.go", "= badP")
pos.Column += len("= badP")
completions = env.Completion("prog.go", pos)
diff = compareCompletionResults([]string{"badPi"}, completions.Items)
if diff != "" {
t.Error(diff)
}
if completions.Items[0].Tags == nil {
t.Errorf("expected Tags to show deprecation %#v", diff[0])
}
})
}

View File

@ -154,6 +154,8 @@ func toProtocolCompletionItems(candidates []completion.CompletionItem, rng proto
Preselect: i == 0,
Documentation: candidate.Documentation,
Tags: candidate.Tags,
Deprecated: candidate.Deprecated,
}
items = append(items, item)
}

View File

@ -270,6 +270,7 @@ func (e *Editor) initialize(ctx context.Context, workspaceFolders []string) erro
params.Capabilities.Workspace.Configuration = true
params.Capabilities.Window.WorkDoneProgress = true
// TODO: set client capabilities
params.Capabilities.TextDocument.Completion.CompletionItem.TagSupport.ValueSet = []protocol.CompletionItemTag{protocol.ComplDeprecated}
params.InitializationOptions = e.configuration()
if e.Config.SendPID {
params.ProcessID = int32(os.Getpid())

View File

@ -22,7 +22,8 @@ import (
errors "golang.org/x/xerrors"
)
const maxFullFileSize int = 100000 // reject full semantic token requests for large files
// reject full semantic token requests for large files
const maxFullFileSize int = 100000
func (s *Server) semanticTokensFull(ctx context.Context, p *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) {
ret, err := s.computeSemanticTokens(ctx, p.TextDocument, nil)
@ -426,6 +427,18 @@ func (e *encoded) ident(x *ast.Ident) {
}
}
func isDeprecated(n *ast.CommentGroup) bool {
if n == nil {
return false
}
for _, c := range n.List {
if strings.HasPrefix(c.Text, "// Deprecated") {
return true
}
}
return false
}
func (e *encoded) definitionFor(x *ast.Ident) (tokenType, []string) {
mods := []string{"definition"}
for i := len(e.stack) - 1; i >= 0; i-- {
@ -437,6 +450,9 @@ func (e *encoded) definitionFor(x *ast.Ident) (tokenType, []string) {
}
return "variable", mods
case *ast.GenDecl:
if isDeprecated(y.Doc) {
mods = append(mods, "deprecated")
}
if y.Tok == token.CONST {
mods = append(mods, "readonly")
}
@ -444,6 +460,9 @@ func (e *encoded) definitionFor(x *ast.Ident) (tokenType, []string) {
case *ast.FuncDecl:
// If x is immediately under a FuncDecl, it is a function or method
if i == len(e.stack)-2 {
if isDeprecated(y.Doc) {
mods = append(mods, "deprecated")
}
if y.Recv != nil {
return tokMember, mods
}

View File

@ -45,7 +45,9 @@ type CompletionItem struct {
// The insert text does not contain snippets.
InsertText string
Kind protocol.CompletionItemKind
Kind protocol.CompletionItemKind
Tags []protocol.CompletionItemTag
Deprecated bool // Deprecated, prefer Tags if available
// An optional array of additional TextEdits that are applied when
// selecting this completion.

View File

@ -243,6 +243,14 @@ func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, e
if c.opts.fullDocumentation {
item.Documentation = hover.FullDocumentation
}
// The desired pattern is `^// Deprecated`, but the prefix has been removed
if strings.HasPrefix(hover.FullDocumentation, "Deprecated") {
if c.snapshot.View().Options().CompletionTags {
item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated}
} else if c.snapshot.View().Options().CompletionDeprecated {
item.Deprecated = true
}
}
return item, nil
}

View File

@ -188,6 +188,8 @@ type ClientOptions struct {
SemanticTypes []string
SemanticMods []string
RelatedInformationSupported bool
CompletionTags bool
CompletionDeprecated bool
}
// ServerOptions holds LSP-specific configuration that is provided by the
@ -647,6 +649,12 @@ func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) {
// Check if the client supports diagnostic related information.
o.RelatedInformationSupported = caps.TextDocument.PublishDiagnostics.RelatedInformation
// Check if the client completion support incliudes tags (preferred) or deprecation
if caps.TextDocument.Completion.CompletionItem.TagSupport.ValueSet != nil {
o.CompletionTags = true
} else if caps.TextDocument.Completion.CompletionItem.DeprecatedSupport {
o.CompletionDeprecated = true
}
}
func (o *Options) Clone() *Options {