Skip to content

Commit 255eeeb

Browse files
committed
go/.../analysisutil: ExtractDoc unifies Analyzer.Doc and package doc
This CL defines an internal function, ExtractDoc, which extracts documentation from the analyzer's package doc. This allows a single doc comment to serve as both the package doc comment (which is served by sites such as pkg.go.dev) and the analyzer documentation (which is accessible through the command-line tool), appropriately formatted for both media. Also, change all our analyzers with nontrivial documentation to use it. The chosen syntax permits a single doc comment to document multiple analyzers and looks good in the pkgsite HTML rendering and the go vet help output. The HTML heading anchors are predictable. For now this is internal, but we might want to publish it. (After a proposal.) Updates golang/go#58950 See golang/go#57906 Change-Id: Ifc0f48e54c3e42bc598649a7139e178a1a653c13 Reviewed-on: https://go-review.googlesource.com/c/tools/+/474935 Run-TryBot: Alan Donovan <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 1b71eda commit 255eeeb

File tree

48 files changed

+888
-409
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+888
-409
lines changed

go/analysis/passes/assign/assign.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// Package assign defines an Analyzer that detects useless assignments.
65
package assign
76

87
// TODO(adonovan): check also for assignments to struct fields inside
98
// methods that are on T instead of *T.
109

1110
import (
11+
_ "embed"
1212
"fmt"
1313
"go/ast"
1414
"go/token"
@@ -21,15 +21,12 @@ import (
2121
"golang.org/x/tools/go/ast/inspector"
2222
)
2323

24-
const Doc = `check for useless assignments
25-
26-
This checker reports assignments of the form x = x or a[i] = a[i].
27-
These are almost always useless, and even when they aren't they are
28-
usually a mistake.`
24+
//go:embed doc.go
25+
var doc string
2926

3027
var Analyzer = &analysis.Analyzer{
3128
Name: "assign",
32-
Doc: Doc,
29+
Doc: analysisutil.MustExtractDoc(doc, "assign"),
3330
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign",
3431
Requires: []*analysis.Analyzer{inspect.Analyzer},
3532
Run: run,

go/analysis/passes/assign/doc.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package assign defines an Analyzer that detects useless assignments.
6+
//
7+
// # Analyzer assign
8+
//
9+
// assign: check for useless assignments
10+
//
11+
// This checker reports assignments of the form x = x or a[i] = a[i].
12+
// These are almost always useless, and even when they aren't they are
13+
// usually a mistake.
14+
package assign

go/analysis/passes/atomic/atomic.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// Package atomic defines an Analyzer that checks for common mistakes
6-
// using the sync/atomic package.
75
package atomic
86

97
import (
8+
_ "embed"
109
"go/ast"
1110
"go/token"
1211
"go/types"
@@ -17,17 +16,12 @@ import (
1716
"golang.org/x/tools/go/ast/inspector"
1817
)
1918

20-
const Doc = `check for common mistakes using the sync/atomic package
21-
22-
The atomic checker looks for assignment statements of the form:
23-
24-
x = atomic.AddUint64(&x, 1)
25-
26-
which are not atomic.`
19+
//go:embed doc.go
20+
var doc string
2721

2822
var Analyzer = &analysis.Analyzer{
2923
Name: "atomic",
30-
Doc: Doc,
24+
Doc: analysisutil.MustExtractDoc(doc, "atomic"),
3125
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomic",
3226
Requires: []*analysis.Analyzer{inspect.Analyzer},
3327
RunDespiteErrors: true,

go/analysis/passes/atomic/doc.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package atomic defines an Analyzer that checks for common mistakes
6+
// using the sync/atomic package.
7+
//
8+
// # Analyzer atomic
9+
//
10+
// atomic: check for common mistakes using the sync/atomic package
11+
//
12+
// The atomic checker looks for assignment statements of the form:
13+
//
14+
// x = atomic.AddUint64(&x, 1)
15+
//
16+
// which are not atomic.
17+
package atomic

go/analysis/passes/ifaceassert/doc.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package ifaceassert defines an Analyzer that flags
6+
// impossible interface-interface type assertions.
7+
//
8+
// # Analyzer ifaceassert
9+
//
10+
// ifaceassert: detect impossible interface-to-interface type assertions
11+
//
12+
// This checker flags type assertions v.(T) and corresponding type-switch cases
13+
// in which the static type V of v is an interface that cannot possibly implement
14+
// the target interface T. This occurs when V and T contain methods with the same
15+
// name but different signatures. Example:
16+
//
17+
// var v interface {
18+
// Read()
19+
// }
20+
// _ = v.(io.Reader)
21+
//
22+
// The Read method in v has a different signature than the Read method in
23+
// io.Reader, so this assertion cannot succeed.
24+
package ifaceassert

go/analysis/passes/ifaceassert/ifaceassert.go

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,25 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// Package ifaceassert defines an Analyzer that flags
6-
// impossible interface-interface type assertions.
75
package ifaceassert
86

97
import (
8+
_ "embed"
109
"go/ast"
1110
"go/types"
1211

1312
"golang.org/x/tools/go/analysis"
1413
"golang.org/x/tools/go/analysis/passes/inspect"
14+
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
1515
"golang.org/x/tools/go/ast/inspector"
1616
)
1717

18-
const Doc = `detect impossible interface-to-interface type assertions
19-
20-
This checker flags type assertions v.(T) and corresponding type-switch cases
21-
in which the static type V of v is an interface that cannot possibly implement
22-
the target interface T. This occurs when V and T contain methods with the same
23-
name but different signatures. Example:
24-
25-
var v interface {
26-
Read()
27-
}
28-
_ = v.(io.Reader)
29-
30-
The Read method in v has a different signature than the Read method in
31-
io.Reader, so this assertion cannot succeed.
32-
`
18+
//go:embed doc.go
19+
var doc string
3320

3421
var Analyzer = &analysis.Analyzer{
3522
Name: "ifaceassert",
36-
Doc: Doc,
23+
Doc: analysisutil.MustExtractDoc(doc, "ifaceassert"),
3724
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert",
3825
Requires: []*analysis.Analyzer{inspect.Analyzer},
3926
Run: run,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package analysisutil
6+
7+
import (
8+
"fmt"
9+
"go/parser"
10+
"go/token"
11+
"strings"
12+
)
13+
14+
// MustExtractDoc is like [ExtractDoc] but it panics on error.
15+
//
16+
// To use, define a doc.go file such as:
17+
//
18+
// // Package halting defines an analyzer of program termination.
19+
// //
20+
// // # Analyzer halting
21+
// //
22+
// // halting: reports whether execution will halt.
23+
// //
24+
// // The halting analyzer reports a diagnostic for functions
25+
// // that run forever. To suppress the diagnostics, try inserting
26+
// // a 'break' statement into each loop.
27+
// package halting
28+
//
29+
// import _ "embed"
30+
//
31+
// //go:embed doc.go
32+
// var doc string
33+
//
34+
// And declare your analyzer as:
35+
//
36+
// var Analyzer = &analysis.Analyzer{
37+
// Name: "halting",
38+
// Doc: analysisutil.MustExtractDoc(doc, "halting"),
39+
// ...
40+
// }
41+
func MustExtractDoc(content, name string) string {
42+
doc, err := ExtractDoc(content, name)
43+
if err != nil {
44+
panic(err)
45+
}
46+
return doc
47+
}
48+
49+
// ExtractDoc extracts a section of a package doc comment from the
50+
// provided contents of an analyzer package's doc.go file.
51+
//
52+
// A section is a portion of the comment between one heading and
53+
// the next, using this form:
54+
//
55+
// # Analyzer NAME
56+
//
57+
// NAME: SUMMARY
58+
//
59+
// Full description...
60+
//
61+
// where NAME matches the name argument, and SUMMARY is a brief
62+
// verb-phrase that describes the analyzer. The following lines, up
63+
// until the next heading or the end of the comment, contain the full
64+
// description. ExtractDoc returns the portion following the colon,
65+
// which is the form expected by Analyzer.Doc.
66+
//
67+
// Example:
68+
//
69+
// # Analyzer printf
70+
//
71+
// printf: checks consistency of calls to printf
72+
//
73+
// The printf analyzer checks consistency of calls to printf.
74+
// Here is the complete description...
75+
//
76+
// This notation allows a single doc comment to provide documentation
77+
// for multiple analyzers, each in its own section.
78+
// The HTML anchors generated for each heading are predictable.
79+
//
80+
// It returns an error if the content was not a valid Go source file
81+
// containing a package doc comment with a heading of the required
82+
// form.
83+
//
84+
// This machinery enables the package documentation (typically
85+
// accessible via the web at https://pkg.go.dev/) and the command
86+
// documentation (typically printed to a terminal) to be derived from
87+
// the same source and formatted appropriately.
88+
func ExtractDoc(content, name string) (string, error) {
89+
if content == "" {
90+
return "", fmt.Errorf("empty Go source file")
91+
}
92+
fset := token.NewFileSet()
93+
f, err := parser.ParseFile(fset, "", content, parser.ParseComments|parser.PackageClauseOnly)
94+
if err != nil {
95+
return "", fmt.Errorf("not a Go source file")
96+
}
97+
if f.Doc == nil {
98+
return "", fmt.Errorf("Go source file has no package doc comment")
99+
}
100+
for _, section := range strings.Split(f.Doc.Text(), "\n# ") {
101+
if body := strings.TrimPrefix(section, "Analyzer "+name); body != section &&
102+
body != "" &&
103+
body[0] == '\r' || body[0] == '\n' {
104+
body = strings.TrimSpace(body)
105+
rest := strings.TrimPrefix(body, name+":")
106+
if rest == body {
107+
return "", fmt.Errorf("'Analyzer %s' heading not followed by '%s: summary...' line", name, name)
108+
}
109+
return strings.TrimSpace(rest), nil
110+
}
111+
}
112+
return "", fmt.Errorf("package doc comment contains no 'Analyzer %s' heading", name)
113+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package analysisutil_test
6+
7+
import (
8+
"testing"
9+
10+
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
11+
)
12+
13+
func TestExtractDoc(t *testing.T) {
14+
const multi = `// Copyright
15+
16+
//+build tag
17+
18+
// Package foo
19+
//
20+
// # Irrelevant heading
21+
//
22+
// This is irrelevant doc.
23+
//
24+
// # Analyzer nocolon
25+
//
26+
// This one has the wrong form for this line.
27+
//
28+
// # Analyzer food
29+
//
30+
// food: reports dining opportunities
31+
//
32+
// This is the doc for analyzer 'food'.
33+
//
34+
// # Analyzer foo
35+
//
36+
// foo: reports diagnostics
37+
//
38+
// This is the doc for analyzer 'foo'.
39+
//
40+
// # Analyzer bar
41+
//
42+
// bar: reports drinking opportunities
43+
//
44+
// This is the doc for analyzer 'bar'.
45+
package blah
46+
47+
var x = syntax error
48+
`
49+
50+
for _, test := range []struct {
51+
content, name string
52+
want string // doc or "error: %w" string
53+
}{
54+
{"", "foo",
55+
"error: empty Go source file"},
56+
{"//foo", "foo",
57+
"error: not a Go source file"},
58+
{"//foo\npackage foo", "foo",
59+
"error: package doc comment contains no 'Analyzer foo' heading"},
60+
{multi, "foo",
61+
"reports diagnostics\n\nThis is the doc for analyzer 'foo'."},
62+
{multi, "bar",
63+
"reports drinking opportunities\n\nThis is the doc for analyzer 'bar'."},
64+
{multi, "food",
65+
"reports dining opportunities\n\nThis is the doc for analyzer 'food'."},
66+
{multi, "nope",
67+
"error: package doc comment contains no 'Analyzer nope' heading"},
68+
{multi, "nocolon",
69+
"error: 'Analyzer nocolon' heading not followed by 'nocolon: summary...' line"},
70+
} {
71+
got, err := analysisutil.ExtractDoc(test.content, test.name)
72+
if err != nil {
73+
got = "error: " + err.Error()
74+
}
75+
if test.want != got {
76+
t.Errorf("ExtractDoc(%q) returned <<%s>>, want <<%s>>, given input <<%s>>",
77+
test.name, got, test.want, test.content)
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)