Skip to content

Commit 7663a40

Browse files
adonovangopherbot
authored andcommitted
internal/cmd/deadcode: add -generated flag
-generated=false will suppress output of unreachable functions in generated Go source files (as determined by ast.IsGenerated). Change-Id: Iab5aa9fbc497a9bcb6a10124e2fe7ab892ad1936 Reviewed-on: https://go-review.googlesource.com/c/tools/+/524946 Reviewed-by: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Alan Donovan <adonovan@google.com> Run-TryBot: Alan Donovan <adonovan@google.com>
1 parent ea5e7c6 commit 7663a40

File tree

1 file changed

+64
-7
lines changed

1 file changed

+64
-7
lines changed

internal/cmd/deadcode/deadcode.go

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
_ "embed"
99
"flag"
1010
"fmt"
11+
"go/ast"
1112
"go/token"
1213
"io"
1314
"log"
@@ -32,10 +33,11 @@ var (
3233
testFlag = flag.Bool("test", false, "include implicit test packages and executables")
3334
tagsFlag = flag.String("tags", "", "comma-separated list of extra build tags (see: go help buildconstraint)")
3435

35-
filterFlag = flag.String("filter", "<module>", "report only packages matching this regular expression (default: module of first package)")
36-
lineFlag = flag.Bool("line", false, "show output in a line-oriented format")
37-
cpuProfile = flag.String("cpuprofile", "", "write CPU profile to this file")
38-
memProfile = flag.String("memprofile", "", "write memory profile to this file")
36+
filterFlag = flag.String("filter", "<module>", "report only packages matching this regular expression (default: module of first package)")
37+
generatedFlag = flag.Bool("generated", true, "report dead functions in generated Go files")
38+
lineFlag = flag.Bool("line", false, "show output in a line-oriented format")
39+
cpuProfile = flag.String("cpuprofile", "", "write CPU profile to this file")
40+
memProfile = flag.String("memprofile", "", "write memory profile to this file")
3941
)
4042

4143
func usage() {
@@ -104,6 +106,18 @@ func main() {
104106
log.Fatalf("packages contain errors")
105107
}
106108

109+
// (Optionally) gather names of generated files.
110+
generated := make(map[string]bool)
111+
if !*generatedFlag {
112+
packages.Visit(initial, nil, func(p *packages.Package) {
113+
for _, file := range p.Syntax {
114+
if isGenerated(file) {
115+
generated[p.Fset.File(file.Pos()).Name()] = true
116+
}
117+
}
118+
})
119+
}
120+
107121
// If -filter is unset, use first module (if available).
108122
if *filterFlag == "<module>" {
109123
if mod := initial[0].Module; mod != nil && mod.Path != "" {
@@ -176,6 +190,13 @@ func main() {
176190
}
177191

178192
posn := prog.Fset.Position(fn.Pos())
193+
194+
// If -generated=false, skip functions declared in generated Go files.
195+
// (Functions called by them may still be reported as dead.)
196+
if generated[posn.Filename] {
197+
continue
198+
}
199+
179200
if !reachablePosn[posn] {
180201
reachablePosn[posn] = true // suppress dups with same pos
181202

@@ -220,9 +241,6 @@ func main() {
220241
return xposn.Line < yposn.Line
221242
})
222243

223-
// TODO(adonovan): add an option to skip (or indicate)
224-
// dead functions in generated files (see ast.IsGenerated).
225-
226244
if *lineFlag {
227245
// line-oriented output
228246
for _, fn := range fns {
@@ -238,3 +256,42 @@ func main() {
238256
}
239257
}
240258
}
259+
260+
// TODO(adonovan): use go1.21's ast.IsGenerated.
261+
262+
// isGenerated reports whether the file was generated by a program,
263+
// not handwritten, by detecting the special comment described
264+
// at https://go.dev/s/generatedcode.
265+
//
266+
// The syntax tree must have been parsed with the ParseComments flag.
267+
// Example:
268+
//
269+
// f, err := parser.ParseFile(fset, filename, src, parser.ParseComments|parser.PackageClauseOnly)
270+
// if err != nil { ... }
271+
// gen := ast.IsGenerated(f)
272+
func isGenerated(file *ast.File) bool {
273+
_, ok := generator(file)
274+
return ok
275+
}
276+
277+
func generator(file *ast.File) (string, bool) {
278+
for _, group := range file.Comments {
279+
for _, comment := range group.List {
280+
if comment.Pos() > file.Package {
281+
break // after package declaration
282+
}
283+
// opt: check Contains first to avoid unnecessary array allocation in Split.
284+
const prefix = "// Code generated "
285+
if strings.Contains(comment.Text, prefix) {
286+
for _, line := range strings.Split(comment.Text, "\n") {
287+
if rest, ok := strings.CutPrefix(line, prefix); ok {
288+
if gen, ok := strings.CutSuffix(rest, " DO NOT EDIT."); ok {
289+
return gen, true
290+
}
291+
}
292+
}
293+
}
294+
}
295+
}
296+
return "", false
297+
}

0 commit comments

Comments
 (0)