Skip to content

Commit dd4579c

Browse files
aputmangopherbot
authored andcommitted
gopls/internal/analysis/deprecated: support line comments
More generally, this adjusts astutil.Deprecation to also check for line comments that follow the following rules: * The doc.Text() is a single line. * The comment uses the "// ..." format. This also restricts the deprecated analyzer to only look for deprecated line comments for struct fields and interface methods. We can expand this in the future if desired. Will update the documentation in https://go.dev/wiki/Deprecated in a follow up cl. Also added tests for more of the use cases of the deprecated analyzer and unit tests for the astutil.Deprecation function. Fixes golang/go#79801 Change-Id: Iab35855bd24b38eb54159000b8b4945c6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/tools/+/787100 Auto-Submit: Alex Putman <aputman@golang.org> Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 3e8d8c4 commit dd4579c

5 files changed

Lines changed: 237 additions & 15 deletions

File tree

gopls/internal/analysis/deprecated/deprecated.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,19 @@ type deprecatedNames struct {
156156
// them both as Facts and the return value. This is a simplified copy
157157
// of staticcheck's fact_deprecated analyzer.
158158
func collectDeprecatedNames(pass *analysis.Pass, ins *inspector.Inspector) (deprecatedNames, error) {
159-
doDocs := func(names []*ast.Ident, docs *ast.CommentGroup) {
160-
alt := strings.TrimPrefix(internalastutil.Deprecation(docs), "Deprecated: ")
161-
if alt == "" {
162-
return
163-
}
159+
doDocs := func(names []*ast.Ident, docs ...*ast.CommentGroup) {
160+
for _, doc := range docs {
161+
// Find the first doc with a deprecation marker.
162+
alt := strings.TrimPrefix(internalastutil.Deprecation(doc), "Deprecated: ")
163+
if alt == "" {
164+
continue
165+
}
164166

165-
for _, name := range names {
166-
obj := pass.TypesInfo.ObjectOf(name)
167-
pass.ExportObjectFact(obj, &deprecationFact{alt})
167+
for _, name := range names {
168+
obj := pass.TypesInfo.ObjectOf(name)
169+
pass.ExportObjectFact(obj, &deprecationFact{alt})
170+
}
171+
return
168172
}
169173
}
170174

@@ -222,14 +226,16 @@ func collectDeprecatedNames(pass *analysis.Pass, ins *inspector.Inspector) (depr
222226
names = node.Names
223227
case *ast.StructType:
224228
for _, field := range node.Fields.List {
225-
doDocs(field.Names, field.Doc)
229+
doDocs(field.Names, field.Doc, field.Comment)
226230
}
227231
case *ast.InterfaceType:
228232
for _, field := range node.Methods.List {
229-
doDocs(field.Names, field.Doc)
233+
doDocs(field.Names, field.Doc, field.Comment)
230234
}
231235
}
232236
if docs != nil && len(names) > 0 {
237+
// Only consider line comments for struct fields and interface
238+
// methods.
233239
doDocs(names, docs)
234240
}
235241
})

gopls/internal/analysis/deprecated/testdata/src/a/a.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,35 @@
44

55
package usedeprecated
66

7-
import "io/ioutil" // want "\"io/ioutil\" is deprecated: .*"
7+
import (
8+
"io/ioutil" // want "\"io/ioutil\" is deprecated: .*"
9+
10+
"legacy"
11+
)
812

913
func x() {
1014
_, _ = ioutil.ReadFile("") // want "ioutil.ReadFile is deprecated: As of Go 1.16, .*"
1115
Legacy() // expect no deprecation notice.
16+
17+
x := legacy.Object{} // want "legacy.Object is deprecated: Use obj instead"
18+
19+
x.DocCommentMethod(1) // expect no deprecation notice, deprecation is only on interface.
20+
x.LineCommentMethod(1) // expect no deprecation notice, deprecation is only on interface.
21+
22+
_ = x.DocCommentField // want "x.DocCommentField is deprecated: Use `Field` instead."
23+
_ = x.LineCommentField // want "x.LineCommentField is deprecated: Use `Field` instead."
24+
25+
// Make sure that the doc comment is chosen over the line comment if both have
26+
// deprecation tags.
27+
_ = x.BothCommentField // want "x.BothCommentField is deprecated: Doc comment chosen"
28+
29+
legacy.Legacy() // want "Legacy is deprecated: use X instead."
30+
y(x)
31+
}
32+
33+
func y(i legacy.Interface) {
34+
i.DocCommentMethod(1) // want "i.DocCommentMethod is deprecated: Use Method instead."
35+
i.LineCommentMethod(1) // want "i.LineCommentMethod is deprecated: Use Method instead."
1236
}
1337

1438
// Legacy is deprecated.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Package legacy
2+
package legacy
3+
4+
// Object docs
5+
//
6+
// Deprecated: Use obj instead.
7+
type Object struct {
8+
// Don't use.
9+
//
10+
// Deprecated: Use `Field` instead.
11+
//
12+
// Paragraph after.
13+
DocCommentField int
14+
15+
LineCommentField int // Deprecated: Use `Field` instead.
16+
17+
// Deprecated: Doc comment chosen
18+
BothCommentField int // Deprecated: Line comment chosen
19+
}
20+
21+
type Interface interface {
22+
// Deprecated: Use Method instead.
23+
DocCommentMethod(int) int
24+
LineCommentMethod(int) int // Old method. Deprecated: Use Method instead.
25+
}
26+
27+
// No deprecation notice in concrete method docs.
28+
func (Object) DocCommentMethod(int) int {
29+
return 1
30+
}
31+
32+
// No deprecation notice in concrete method docs.
33+
func (Object) LineCommentMethod(int) int {
34+
return 1
35+
}
36+
37+
// Deprecated: use X instead.
38+
func Legacy() {}

internal/astutil/comment.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ import (
1313
)
1414

1515
// Deprecation returns the paragraph of the doc comment that starts with the
16-
// conventional "Deprecation: " marker, as defined by
17-
// https://go.dev/wiki/Deprecated, or "" if the documented symbol is not
18-
// deprecated.
16+
// conventional "Deprecation: " marker, or the end of a single-line comment
17+
// with the deprecation marker, as defined by https://go.dev/wiki/Deprecated.
18+
// Returns "" if the documented symbol is not deprecated.
19+
//
20+
// Deprecation(nil) returns the empty string.
1921
func Deprecation(doc *ast.CommentGroup) string {
20-
for p := range strings.SplitSeq(doc.Text(), "\n\n") {
22+
// doc.Text() is newline-terminated. For legacy reasons, this function will
23+
// return as newline-terminated if is the last segment of the CommentGroup
24+
// but not if it is a paragraph in the middle of the CommentGroup.
25+
docText := doc.Text()
26+
for p := range strings.SplitSeq(docText, "\n\n") {
2127
// There is still some ambiguity for deprecation message. This function
2228
// only returns the paragraph introduced by "Deprecated: ". More
2329
// information related to the deprecation may follow in additional
@@ -27,6 +33,19 @@ func Deprecation(doc *ast.CommentGroup) string {
2733
return p
2834
}
2935
}
36+
37+
// We also want to support deprecation markers in line comments. Not all
38+
// call sites know whether they have a line comment or the type of AST node
39+
// the comment is associated with; so to best match line deprecations,
40+
// the CommentGroup must meet these criteria:
41+
// * The doc.Text() is a single line.
42+
// * The comment uses the "// ..." format.
43+
if doc == nil || len(doc.List) != 1 || !strings.HasPrefix(doc.List[0].Text, "//") {
44+
return ""
45+
}
46+
if i := strings.Index(docText, "Deprecated: "); i != -1 {
47+
return docText[i:]
48+
}
3049
return ""
3150
}
3251

internal/astutil/comment_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package astutil_test
66

77
import (
8+
"fmt"
89
"go/ast"
910
"go/parser"
1011
"go/token"
@@ -57,3 +58,137 @@ func fn() { }`
5758
})
5859
}
5960
}
61+
62+
func TestDeprecation(t *testing.T) {
63+
testsCases := []struct {
64+
name string
65+
in string
66+
want string
67+
}{
68+
{
69+
name: "doc_comment_only_paragraph",
70+
in: `// Deprecated: Test
71+
// a whole paragraph.
72+
type A struct {}`,
73+
want: "Deprecated: Test\na whole paragraph.\n",
74+
},
75+
{
76+
name: "doc_comment_any_paragraph",
77+
in: `// First paragraph
78+
//
79+
// Deprecated: Middle
80+
// paragraph.
81+
//
82+
// Last Paragraph
83+
type A struct {}`,
84+
want: "Deprecated: Middle\nparagraph.",
85+
},
86+
{
87+
name: "doc_comment_finds_the_first",
88+
in: `// Deprecated: First
89+
//
90+
// Deprecated: second
91+
type A struct {}`,
92+
want: "Deprecated: First",
93+
},
94+
{
95+
name: "doc_comment_not_found_inside_paragraph",
96+
in: `// First paragraph
97+
// Deprecated: Middle paragraph.
98+
type A struct {}`,
99+
want: "",
100+
},
101+
{
102+
name: "multi_line_doc_comment_supported_if_no_whitespace",
103+
in: `
104+
/*First paragraph
105+
106+
Deprecated: Middle paragraph
107+
108+
Last Paragraph
109+
*/
110+
type A struct {}`,
111+
want: "Deprecated: Middle paragraph",
112+
},
113+
{
114+
name: "multi_line_doc_comment_weird_format_not_supported",
115+
// This is what the go formatter formats when the text starts on
116+
// the second line of a /* */ comment.
117+
in: `
118+
/*
119+
First paragraph
120+
121+
Deprecated: Middle paragraph
122+
123+
Last Paragraph
124+
*/
125+
type A struct {}`,
126+
// Not found, as the "Deprecated: ..." line has leading whitespace.
127+
want: "",
128+
},
129+
{
130+
name: "line_comment_just_deprecated_tag",
131+
in: `type A interface {
132+
B(int) int // Deprecated: use 'C()'
133+
}`,
134+
want: "Deprecated: use 'C()'\n",
135+
},
136+
{
137+
name: "line_comment_comment_before_deprecated_tag",
138+
in: `type A struct {
139+
b int // This does x. Deprecated: use 'c'. Will cleanup.
140+
}`,
141+
want: "Deprecated: use 'c'. Will cleanup.\n",
142+
},
143+
{
144+
name: "line_comment_finds_first_deprecated_tag",
145+
in: `type A struct {
146+
int // Deprecated: use 'c'. Deprecated: use 'd'.
147+
}`,
148+
want: "Deprecated: use 'c'. Deprecated: use 'd'.\n",
149+
},
150+
{
151+
name: "line_comment_doesnt_support_multi_line_comment_type",
152+
in: `type A struct {
153+
b int /*Deprecated: use 'c'*/
154+
}`,
155+
// We can't prevent this, as ast.CommentGroup doesn't specify
156+
// where the comment happened. We won't advertise this.
157+
want: "Deprecated: use 'c'\n",
158+
},
159+
{
160+
name: "multiline_comment_cant_have_comment_before_deprecated_tag",
161+
in: `type A struct {
162+
b int /* test Deprecated: use 'c' */
163+
}`,
164+
want: "",
165+
},
166+
}
167+
168+
for _, test := range testsCases {
169+
t.Run(test.name, func(t *testing.T) {
170+
src := fmt.Sprintf("package a; \n\n%s", test.in)
171+
f, err := parser.ParseFile(token.NewFileSet(), "a.go", src, parser.ParseComments)
172+
if err != nil {
173+
t.Fatal(err)
174+
}
175+
switch len(f.Comments) {
176+
case 0:
177+
t.Error("No `ast.CommentGroup` found")
178+
case 1:
179+
default:
180+
t.Errorf("%d `ast.CommentGroup`s found, only want one", len(f.Comments))
181+
}
182+
if got := astutil.Deprecation(f.Comments[0]); got != test.want {
183+
// align 'got' and 'want' for easier inspection
184+
t.Errorf("\nfound comment: %q\ngot: %q\nwant: %q", f.Comments[0].Text(), got, test.want)
185+
}
186+
})
187+
}
188+
189+
t.Run("Deprecation(nil)", func(t *testing.T) {
190+
if got := astutil.Deprecation(nil); got != "" {
191+
t.Errorf("Deprecation(nil) = %q, want: \"\"", got)
192+
}
193+
})
194+
}

0 commit comments

Comments
 (0)