8
8
_ "embed"
9
9
"flag"
10
10
"fmt"
11
+ "go/ast"
11
12
"go/token"
12
13
"io"
13
14
"log"
@@ -32,10 +33,11 @@ var (
32
33
testFlag = flag .Bool ("test" , false , "include implicit test packages and executables" )
33
34
tagsFlag = flag .String ("tags" , "" , "comma-separated list of extra build tags (see: go help buildconstraint)" )
34
35
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" )
39
41
)
40
42
41
43
func usage () {
@@ -104,6 +106,18 @@ func main() {
104
106
log .Fatalf ("packages contain errors" )
105
107
}
106
108
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
+
107
121
// If -filter is unset, use first module (if available).
108
122
if * filterFlag == "<module>" {
109
123
if mod := initial [0 ].Module ; mod != nil && mod .Path != "" {
@@ -176,6 +190,13 @@ func main() {
176
190
}
177
191
178
192
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
+
179
200
if ! reachablePosn [posn ] {
180
201
reachablePosn [posn ] = true // suppress dups with same pos
181
202
@@ -220,9 +241,6 @@ func main() {
220
241
return xposn .Line < yposn .Line
221
242
})
222
243
223
- // TODO(adonovan): add an option to skip (or indicate)
224
- // dead functions in generated files (see ast.IsGenerated).
225
-
226
244
if * lineFlag {
227
245
// line-oriented output
228
246
for _ , fn := range fns {
@@ -238,3 +256,42 @@ func main() {
238
256
}
239
257
}
240
258
}
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