diff --git a/gopls/internal/regtest/completion/completion_test.go b/gopls/internal/regtest/completion/completion_test.go index a88e861fec..d57a4b237b 100644 --- a/gopls/internal/regtest/completion/completion_test.go +++ b/gopls/internal/regtest/completion/completion_test.go @@ -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]) + } + }) +} diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index 3762b7a4ee..fa0586a306 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -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) } diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go index cceebe3472..18e54530b4 100644 --- a/internal/lsp/fake/editor.go +++ b/internal/lsp/fake/editor.go @@ -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()) diff --git a/internal/lsp/semantic.go b/internal/lsp/semantic.go index 8230a7c46c..029e5bf04e 100644 --- a/internal/lsp/semantic.go +++ b/internal/lsp/semantic.go @@ -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 } diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go index 07ff03108c..2c9a8262cd 100644 --- a/internal/lsp/source/completion/completion.go +++ b/internal/lsp/source/completion/completion.go @@ -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. diff --git a/internal/lsp/source/completion/format.go b/internal/lsp/source/completion/format.go index f34a84e554..3c9c3b2725 100644 --- a/internal/lsp/source/completion/format.go +++ b/internal/lsp/source/completion/format.go @@ -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 } diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index c88f95b0a5..702f5f9578 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -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 {