Skip to content

Commit a1284d0

Browse files
committed
go/ast: add IsGenerated(*File) predicate
See https://go.dev/s/generatedcode for spec. Fixes #28089 Change-Id: Ic9bb138bdd180f136f9e8e74e187319acca5dbac Reviewed-on: https://go-review.googlesource.com/c/go/+/487935 Run-TryBot: Alan Donovan <adonovan@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
1 parent 22d94df commit a1284d0

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

api/next/28089.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg go/ast, func IsGenerated(*File) bool #28089

src/go/ast/ast.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,3 +1073,40 @@ type Package struct {
10731073

10741074
func (p *Package) Pos() token.Pos { return token.NoPos }
10751075
func (p *Package) End() token.Pos { return token.NoPos }
1076+
1077+
// IsGenerated reports whether the file was generated by a program,
1078+
// not handwritten, by detecting the special comment described
1079+
// at https://go.dev/s/generatedcode.
1080+
//
1081+
// The syntax tree must have been parsed with the ParseComments flag.
1082+
// Example:
1083+
//
1084+
// f, err := parser.ParseFile(fset, filename, src, parser.ParseComments|parser.PackageClauseOnly)
1085+
// if err != nil { ... }
1086+
// gen := ast.IsGenerated(f)
1087+
func IsGenerated(file *File) bool {
1088+
_, ok := generator(file)
1089+
return ok
1090+
}
1091+
1092+
func generator(file *File) (string, bool) {
1093+
for _, group := range file.Comments {
1094+
for _, comment := range group.List {
1095+
if comment.Pos() > file.Package {
1096+
break // after package declaration
1097+
}
1098+
// opt: check Contains first to avoid unnecessary array allocation in Split.
1099+
const prefix = "// Code generated "
1100+
if strings.Contains(comment.Text, prefix) {
1101+
for _, line := range strings.Split(comment.Text, "\n") {
1102+
if rest, ok := strings.CutPrefix(line, prefix); ok {
1103+
if gen, ok := strings.CutSuffix(rest, " DO NOT EDIT."); ok {
1104+
return gen, true
1105+
}
1106+
}
1107+
}
1108+
}
1109+
}
1110+
}
1111+
return "", false
1112+
}

src/go/ast/issues_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,100 @@ func TestIssue33649(t *testing.T) {
4040
}
4141
}
4242
}
43+
44+
// TestIssue28089 exercises the IsGenerated function.
45+
func TestIssue28089(t *testing.T) {
46+
for i, test := range []struct {
47+
src string
48+
want bool
49+
}{
50+
// No file comments.
51+
{`package p`, false},
52+
// Irrelevant file comments.
53+
{`// Package p doc.
54+
package p`, false},
55+
// Special comment misplaced after package decl.
56+
{`// Package p doc.
57+
package p
58+
// Code generated by gen. DO NOT EDIT.
59+
`, false},
60+
// Special comment appears inside string literal.
61+
{`// Package p doc.
62+
package p
63+
const c = "` + "`" + `
64+
// Code generated by gen. DO NOT EDIT.
65+
` + "`" + `
66+
`, false},
67+
// Special comment appears properly.
68+
{`// Copyright 2019 The Go Authors. All rights reserved.
69+
// Use of this source code is governed by a BSD-style
70+
// license that can be found in the LICENSE file.
71+
72+
// Package p doc comment goes here.
73+
//
74+
// Code generated by gen. DO NOT EDIT.
75+
package p
76+
77+
... `, true},
78+
// Special comment is indented.
79+
//
80+
// Strictly, the indent should cause IsGenerated to
81+
// yield false, but we cannot detect the indent
82+
// without either source text or a token.File.
83+
// In other words, the function signature cannot
84+
// implement the spec. Let's brush this under the
85+
// rug since well-formatted code has no indent.
86+
{`// Package p doc comment goes here.
87+
//
88+
// Code generated by gen. DO NOT EDIT.
89+
package p
90+
91+
... `, true},
92+
// Special comment has unwanted spaces after "DO NOT EDIT."
93+
{`// Copyright 2019 The Go Authors. All rights reserved.
94+
// Use of this source code is governed by a BSD-style
95+
// license that can be found in the LICENSE file.
96+
97+
// Package p doc comment goes here.
98+
//
99+
// Code generated by gen. DO NOT EDIT.
100+
package p
101+
102+
... `, false},
103+
// Special comment has rogue interior space.
104+
{`// Code generated by gen. DO NOT EDIT.
105+
package p
106+
`, false},
107+
// Special comment lacks the middle portion.
108+
{`// Code generated DO NOT EDIT.
109+
package p
110+
`, false},
111+
// Special comment (incl. "//") appears within a /* block */ comment,
112+
// an obscure corner case of the spec.
113+
{`/* start of a general comment
114+
115+
// Code generated by tool; DO NOT EDIT.
116+
117+
end of a general comment */
118+
119+
// +build !dev
120+
121+
// Package comment.
122+
package p
123+
124+
// Does match even though it's inside general comment (/*-style).
125+
`, true},
126+
} {
127+
fset := token.NewFileSet()
128+
f, err := parser.ParseFile(fset, "", test.src, parser.PackageClauseOnly|parser.ParseComments)
129+
if f == nil {
130+
t.Fatalf("parse %d failed to return AST: %v", i, err)
131+
}
132+
133+
got := ast.IsGenerated(f)
134+
if got != test.want {
135+
t.Errorf("%d: IsGenerated on <<%s>> returned %t", i, test.src, got)
136+
}
137+
}
138+
139+
}

0 commit comments

Comments
 (0)