diff --git a/gopls/doc/generate/generate.go b/gopls/doc/generate/generate.go
index 7d92b2629d5..42d41bbb1b6 100644
--- a/gopls/doc/generate/generate.go
+++ b/gopls/doc/generate/generate.go
@@ -44,6 +44,7 @@ import (
"golang.org/x/tools/gopls/internal/mod"
"golang.org/x/tools/gopls/internal/settings"
"golang.org/x/tools/gopls/internal/util/safetoken"
+ internalastutil "golang.org/x/tools/internal/astutil"
)
func main() {
@@ -221,11 +222,13 @@ func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Pa
if len(path) < 2 {
return nil, fmt.Errorf("could not find AST node for field %v", typesField)
}
+
// The AST field gives us the doc.
astField, ok := path[1].(*ast.Field)
if !ok {
return nil, fmt.Errorf("unexpected AST path %v", path)
}
+ description, deprecation := astField.Doc.Text(), internalastutil.Deprecation(astField.Doc)
// The reflect field gives us the default value.
reflectField := category.FieldByName(typesField.Name())
@@ -285,14 +288,15 @@ func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Pa
status := reflectStructField.Tag.Get("status")
opts = append(opts, &doc.Option{
- Name: name,
- Type: typ,
- Doc: lowerFirst(astField.Doc.Text()),
- Default: def,
- EnumKeys: enumKeys,
- EnumValues: enums[typesField.Type()],
- Status: status,
- Hierarchy: hierarchy,
+ Name: name,
+ Type: typ,
+ Doc: lowerFirst(description),
+ Default: def,
+ EnumKeys: enumKeys,
+ EnumValues: enums[typesField.Type()],
+ Status: status,
+ Hierarchy: hierarchy,
+ DeprecationMessage: lowerFirst(strings.TrimPrefix(deprecation, "Deprecated: ")),
})
}
return opts, nil
diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md
index 7dfe0870718..3d170b00dc3 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -208,6 +208,9 @@ Default: `false`.
noSemanticString turns off the sending of the semantic token 'string'
+Deprecated: Use SemanticTokenTypes["string"] = false instead. See
+golang/vscode-go#3632
+
Default: `false`.
@@ -217,6 +220,9 @@ Default: `false`.
noSemanticNumber turns off the sending of the semantic token 'number'
+Deprecated: Use SemanticTokenTypes["number"] = false instead. See
+golang/vscode-go#3632.
+
Default: `false`.
diff --git a/gopls/internal/analysis/deprecated/deprecated.go b/gopls/internal/analysis/deprecated/deprecated.go
index 1a8c4c56766..c6df00b4f50 100644
--- a/gopls/internal/analysis/deprecated/deprecated.go
+++ b/gopls/internal/analysis/deprecated/deprecated.go
@@ -19,6 +19,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
+ internalastutil "golang.org/x/tools/internal/astutil"
)
//go:embed doc.go
@@ -155,26 +156,8 @@ type deprecatedNames struct {
// them both as Facts and the return value. This is a simplified copy
// of staticcheck's fact_deprecated analyzer.
func collectDeprecatedNames(pass *analysis.Pass, ins *inspector.Inspector) (deprecatedNames, error) {
- extractDeprecatedMessage := func(docs []*ast.CommentGroup) string {
- for _, doc := range docs {
- if doc == nil {
- continue
- }
- parts := strings.Split(doc.Text(), "\n\n")
- for _, part := range parts {
- if !strings.HasPrefix(part, "Deprecated: ") {
- continue
- }
- alt := part[len("Deprecated: "):]
- alt = strings.Replace(alt, "\n", " ", -1)
- return strings.TrimSpace(alt)
- }
- }
- return ""
- }
-
doDocs := func(names []*ast.Ident, docs *ast.CommentGroup) {
- alt := extractDeprecatedMessage([]*ast.CommentGroup{docs})
+ alt := strings.TrimPrefix(internalastutil.Deprecation(docs), "Deprecated: ")
if alt == "" {
return
}
@@ -185,19 +168,21 @@ func collectDeprecatedNames(pass *analysis.Pass, ins *inspector.Inspector) (depr
}
}
- var docs []*ast.CommentGroup
- for _, f := range pass.Files {
- docs = append(docs, f.Doc)
- }
- if alt := extractDeprecatedMessage(docs); alt != "" {
- // Don't mark package syscall as deprecated, even though
- // it is. A lot of people still use it for simple
- // constants like SIGKILL, and I am not comfortable
- // telling them to use x/sys for that.
- if pass.Pkg.Path() != "syscall" {
- pass.ExportPackageFact(&deprecationFact{alt})
+ // Is package deprecated?
+ //
+ // Don't mark package syscall as deprecated, even though
+ // it is. A lot of people still use it for simple
+ // constants like SIGKILL, and I am not comfortable
+ // telling them to use x/sys for that.
+ if pass.Pkg.Path() != "syscall" {
+ for _, f := range pass.Files {
+ if depr := internalastutil.Deprecation(f.Doc); depr != "" {
+ pass.ExportPackageFact(&deprecationFact{depr})
+ break
+ }
}
}
+
nodeFilter := []ast.Node{
(*ast.GenDecl)(nil),
(*ast.FuncDecl)(nil),
diff --git a/gopls/internal/doc/api.go b/gopls/internal/doc/api.go
index a096f5ad63e..258f90d49ae 100644
--- a/gopls/internal/doc/api.go
+++ b/gopls/internal/doc/api.go
@@ -27,14 +27,15 @@ type API struct {
}
type Option struct {
- Name string
- Type string // T = bool | string | int | enum | any | []T | map[T]T | time.Duration
- Doc string
- EnumKeys EnumKeys
- EnumValues []EnumValue
- Default string
- Status string
- Hierarchy string
+ Name string
+ Type string // T = bool | string | int | enum | any | []T | map[T]T | time.Duration
+ Doc string
+ EnumKeys EnumKeys
+ EnumValues []EnumValue
+ Default string
+ Status string
+ Hierarchy string
+ DeprecationMessage string
}
type EnumKeys struct {
diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json
index bf9a06ccaad..4a8e10f6132 100644
--- a/gopls/internal/doc/api.json
+++ b/gopls/internal/doc/api.json
@@ -12,7 +12,8 @@
"EnumValues": null,
"Default": "[]",
"Status": "",
- "Hierarchy": "build"
+ "Hierarchy": "build",
+ "DeprecationMessage": ""
},
{
"Name": "env",
@@ -25,7 +26,8 @@
"EnumValues": null,
"Default": "{}",
"Status": "",
- "Hierarchy": "build"
+ "Hierarchy": "build",
+ "DeprecationMessage": ""
},
{
"Name": "directoryFilters",
@@ -38,7 +40,8 @@
"EnumValues": null,
"Default": "[\"-**/node_modules\"]",
"Status": "",
- "Hierarchy": "build"
+ "Hierarchy": "build",
+ "DeprecationMessage": ""
},
{
"Name": "templateExtensions",
@@ -51,7 +54,8 @@
"EnumValues": null,
"Default": "[]",
"Status": "",
- "Hierarchy": "build"
+ "Hierarchy": "build",
+ "DeprecationMessage": ""
},
{
"Name": "memoryMode",
@@ -64,7 +68,8 @@
"EnumValues": null,
"Default": "\"\"",
"Status": "experimental",
- "Hierarchy": "build"
+ "Hierarchy": "build",
+ "DeprecationMessage": ""
},
{
"Name": "expandWorkspaceToModule",
@@ -77,7 +82,8 @@
"EnumValues": null,
"Default": "true",
"Status": "experimental",
- "Hierarchy": "build"
+ "Hierarchy": "build",
+ "DeprecationMessage": ""
},
{
"Name": "standaloneTags",
@@ -90,7 +96,8 @@
"EnumValues": null,
"Default": "[\"ignore\"]",
"Status": "",
- "Hierarchy": "build"
+ "Hierarchy": "build",
+ "DeprecationMessage": ""
},
{
"Name": "hoverKind",
@@ -120,7 +127,8 @@
],
"Default": "\"FullDocumentation\"",
"Status": "",
- "Hierarchy": "ui.documentation"
+ "Hierarchy": "ui.documentation",
+ "DeprecationMessage": ""
},
{
"Name": "linkTarget",
@@ -133,7 +141,8 @@
"EnumValues": null,
"Default": "\"pkg.go.dev\"",
"Status": "",
- "Hierarchy": "ui.documentation"
+ "Hierarchy": "ui.documentation",
+ "DeprecationMessage": ""
},
{
"Name": "linksInHover",
@@ -159,7 +168,8 @@
],
"Default": "true",
"Status": "",
- "Hierarchy": "ui.documentation"
+ "Hierarchy": "ui.documentation",
+ "DeprecationMessage": ""
},
{
"Name": "usePlaceholders",
@@ -172,7 +182,8 @@
"EnumValues": null,
"Default": "false",
"Status": "",
- "Hierarchy": "ui.completion"
+ "Hierarchy": "ui.completion",
+ "DeprecationMessage": ""
},
{
"Name": "completionBudget",
@@ -185,7 +196,8 @@
"EnumValues": null,
"Default": "\"100ms\"",
"Status": "debug",
- "Hierarchy": "ui.completion"
+ "Hierarchy": "ui.completion",
+ "DeprecationMessage": ""
},
{
"Name": "matcher",
@@ -211,7 +223,8 @@
],
"Default": "\"Fuzzy\"",
"Status": "advanced",
- "Hierarchy": "ui.completion"
+ "Hierarchy": "ui.completion",
+ "DeprecationMessage": ""
},
{
"Name": "experimentalPostfixCompletions",
@@ -224,7 +237,8 @@
"EnumValues": null,
"Default": "true",
"Status": "experimental",
- "Hierarchy": "ui.completion"
+ "Hierarchy": "ui.completion",
+ "DeprecationMessage": ""
},
{
"Name": "completeFunctionCalls",
@@ -237,7 +251,8 @@
"EnumValues": null,
"Default": "true",
"Status": "",
- "Hierarchy": "ui.completion"
+ "Hierarchy": "ui.completion",
+ "DeprecationMessage": ""
},
{
"Name": "importShortcut",
@@ -263,7 +278,8 @@
],
"Default": "\"Both\"",
"Status": "",
- "Hierarchy": "ui.navigation"
+ "Hierarchy": "ui.navigation",
+ "DeprecationMessage": ""
},
{
"Name": "symbolMatcher",
@@ -293,7 +309,8 @@
],
"Default": "\"FastFuzzy\"",
"Status": "advanced",
- "Hierarchy": "ui.navigation"
+ "Hierarchy": "ui.navigation",
+ "DeprecationMessage": ""
},
{
"Name": "symbolStyle",
@@ -319,7 +336,8 @@
],
"Default": "\"Dynamic\"",
"Status": "advanced",
- "Hierarchy": "ui.navigation"
+ "Hierarchy": "ui.navigation",
+ "DeprecationMessage": ""
},
{
"Name": "symbolScope",
@@ -341,7 +359,8 @@
],
"Default": "\"all\"",
"Status": "",
- "Hierarchy": "ui.navigation"
+ "Hierarchy": "ui.navigation",
+ "DeprecationMessage": ""
},
{
"Name": "analyses",
@@ -630,7 +649,8 @@
"EnumValues": null,
"Default": "{}",
"Status": "",
- "Hierarchy": "ui.diagnostic"
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
},
{
"Name": "staticcheck",
@@ -643,7 +663,8 @@
"EnumValues": null,
"Default": "false",
"Status": "experimental",
- "Hierarchy": "ui.diagnostic"
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
},
{
"Name": "vulncheck",
@@ -665,7 +686,8 @@
],
"Default": "\"Off\"",
"Status": "experimental",
- "Hierarchy": "ui.diagnostic"
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
},
{
"Name": "diagnosticsDelay",
@@ -678,7 +700,8 @@
"EnumValues": null,
"Default": "\"1s\"",
"Status": "advanced",
- "Hierarchy": "ui.diagnostic"
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
},
{
"Name": "diagnosticsTrigger",
@@ -700,7 +723,8 @@
],
"Default": "\"Edit\"",
"Status": "experimental",
- "Hierarchy": "ui.diagnostic"
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
},
{
"Name": "analysisProgressReporting",
@@ -713,7 +737,8 @@
"EnumValues": null,
"Default": "true",
"Status": "",
- "Hierarchy": "ui.diagnostic"
+ "Hierarchy": "ui.diagnostic",
+ "DeprecationMessage": ""
},
{
"Name": "hints",
@@ -762,7 +787,8 @@
"EnumValues": null,
"Default": "{}",
"Status": "experimental",
- "Hierarchy": "ui.inlayhint"
+ "Hierarchy": "ui.inlayhint",
+ "DeprecationMessage": ""
},
{
"Name": "codelenses",
@@ -816,7 +842,8 @@
"EnumValues": null,
"Default": "{\"generate\":true,\"regenerate_cgo\":true,\"run_govulncheck\":false,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}",
"Status": "",
- "Hierarchy": "ui"
+ "Hierarchy": "ui",
+ "DeprecationMessage": ""
},
{
"Name": "semanticTokens",
@@ -829,12 +856,13 @@
"EnumValues": null,
"Default": "false",
"Status": "experimental",
- "Hierarchy": "ui"
+ "Hierarchy": "ui",
+ "DeprecationMessage": ""
},
{
"Name": "noSemanticString",
"Type": "bool",
- "Doc": "noSemanticString turns off the sending of the semantic token 'string'\n",
+ "Doc": "noSemanticString turns off the sending of the semantic token 'string'\n\nDeprecated: Use SemanticTokenTypes[\"string\"] = false instead. See\ngolang/vscode-go#3632\n",
"EnumKeys": {
"ValueType": "",
"Keys": null
@@ -842,12 +870,13 @@
"EnumValues": null,
"Default": "false",
"Status": "experimental",
- "Hierarchy": "ui"
+ "Hierarchy": "ui",
+ "DeprecationMessage": "use SemanticTokenTypes[\"string\"] = false instead. See\ngolang/vscode-go#3632\n"
},
{
"Name": "noSemanticNumber",
"Type": "bool",
- "Doc": "noSemanticNumber turns off the sending of the semantic token 'number'\n",
+ "Doc": "noSemanticNumber turns off the sending of the semantic token 'number'\n\nDeprecated: Use SemanticTokenTypes[\"number\"] = false instead. See\ngolang/vscode-go#3632.\n",
"EnumKeys": {
"ValueType": "",
"Keys": null
@@ -855,7 +884,8 @@
"EnumValues": null,
"Default": "false",
"Status": "experimental",
- "Hierarchy": "ui"
+ "Hierarchy": "ui",
+ "DeprecationMessage": "use SemanticTokenTypes[\"number\"] = false instead. See\ngolang/vscode-go#3632.\n"
},
{
"Name": "semanticTokenTypes",
@@ -868,7 +898,8 @@
"EnumValues": null,
"Default": "{}",
"Status": "experimental",
- "Hierarchy": "ui"
+ "Hierarchy": "ui",
+ "DeprecationMessage": ""
},
{
"Name": "semanticTokenModifiers",
@@ -881,7 +912,8 @@
"EnumValues": null,
"Default": "{}",
"Status": "experimental",
- "Hierarchy": "ui"
+ "Hierarchy": "ui",
+ "DeprecationMessage": ""
},
{
"Name": "local",
@@ -894,7 +926,8 @@
"EnumValues": null,
"Default": "\"\"",
"Status": "",
- "Hierarchy": "formatting"
+ "Hierarchy": "formatting",
+ "DeprecationMessage": ""
},
{
"Name": "gofumpt",
@@ -907,7 +940,8 @@
"EnumValues": null,
"Default": "false",
"Status": "",
- "Hierarchy": "formatting"
+ "Hierarchy": "formatting",
+ "DeprecationMessage": ""
},
{
"Name": "verboseOutput",
@@ -920,7 +954,8 @@
"EnumValues": null,
"Default": "false",
"Status": "debug",
- "Hierarchy": ""
+ "Hierarchy": "",
+ "DeprecationMessage": ""
}
]
},
diff --git a/gopls/internal/golang/completion/format.go b/gopls/internal/golang/completion/format.go
index cf46463078a..69339bffe84 100644
--- a/gopls/internal/golang/completion/format.go
+++ b/gopls/internal/golang/completion/format.go
@@ -18,6 +18,7 @@ import (
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/gopls/internal/util/safetoken"
"golang.org/x/tools/gopls/internal/util/typesutil"
+ internalastutil "golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/imports"
)
@@ -261,10 +262,7 @@ Suffixes:
} else {
item.Documentation = doc.Synopsis(comment.Text())
}
- // The desired pattern is `^// Deprecated`, but the prefix has been removed
- // TODO(rfindley): It doesn't look like this does the right thing for
- // multi-line comments.
- if strings.HasPrefix(comment.Text(), "Deprecated") {
+ if internalastutil.Deprecation(comment) != "" {
if c.snapshot.Options().CompletionTags {
item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated}
} else if c.snapshot.Options().CompletionDeprecated {
diff --git a/gopls/internal/settings/settings.go b/gopls/internal/settings/settings.go
index 496062c40ec..13aaa61bdd9 100644
--- a/gopls/internal/settings/settings.go
+++ b/gopls/internal/settings/settings.go
@@ -172,9 +172,15 @@ type UIOptions struct {
SemanticTokens bool `status:"experimental"`
// NoSemanticString turns off the sending of the semantic token 'string'
+ //
+ // Deprecated: Use SemanticTokenTypes["string"] = false instead. See
+ // golang/vscode-go#3632
NoSemanticString bool `status:"experimental"`
// NoSemanticNumber turns off the sending of the semantic token 'number'
+ //
+ // Deprecated: Use SemanticTokenTypes["number"] = false instead. See
+ // golang/vscode-go#3632.
NoSemanticNumber bool `status:"experimental"`
// SemanticTokenTypes configures the semantic token types. It allows
@@ -1095,10 +1101,16 @@ func (o *Options) setOne(name string, value any) error {
// TODO(hxjiang): deprecate noSemanticString and noSemanticNumber.
case "noSemanticString":
- return setBool(&o.NoSemanticString, value)
+ if err := setBool(&o.NoSemanticString, value); err != nil {
+ return err
+ }
+ return &SoftError{fmt.Sprintf("noSemanticString setting is deprecated, use semanticTokenTypes instead (though you can continue to apply them for the time being).")}
case "noSemanticNumber":
- return setBool(&o.NoSemanticNumber, value)
+ if err := setBool(&o.NoSemanticNumber, value); err != nil {
+ return nil
+ }
+ return &SoftError{fmt.Sprintf("noSemanticNumber setting is deprecated, use semanticTokenTypes instead (though you can continue to apply them for the time being).")}
case "semanticTokenTypes":
return setBoolMap(&o.SemanticTokenTypes, value)
diff --git a/gopls/internal/test/integration/completion/completion_test.go b/gopls/internal/test/integration/completion/completion_test.go
index 1f6eb2fe0fb..fe6a367e71b 100644
--- a/gopls/internal/test/integration/completion/completion_test.go
+++ b/gopls/internal/test/integration/completion/completion_test.go
@@ -471,12 +471,12 @@ module test.com
go 1.16
-- prog.go --
package waste
-// Deprecated, use newFoof
+// Deprecated: use newFoof.
func fooFunc() bool {
return false
}
-// Deprecated
+// Deprecated: bad.
const badPi = 3.14
func doit() {
diff --git a/internal/astutil/comment.go b/internal/astutil/comment.go
new file mode 100644
index 00000000000..192d6430de0
--- /dev/null
+++ b/internal/astutil/comment.go
@@ -0,0 +1,28 @@
+// Copyright 2025 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 astutil
+
+import (
+ "go/ast"
+ "strings"
+)
+
+// Deprecation returns the paragraph of the doc comment that starts with the
+// conventional "Deprecation: " marker, as defined by
+// https://go.dev/wiki/Deprecated, or "" if the documented symbol is not
+// deprecated.
+func Deprecation(doc *ast.CommentGroup) string {
+ for _, p := range strings.Split(doc.Text(), "\n\n") {
+ // There is still some ambiguity for deprecation message. This function
+ // only returns the paragraph introduced by "Deprecated: ". More
+ // information related to the deprecation may follow in additional
+ // paragraphs, but the deprecation message should be able to stand on
+ // its own. See golang/go#38743.
+ if strings.HasPrefix(p, "Deprecated: ") {
+ return p
+ }
+ }
+ return ""
+}