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 "" +}