1
0
mirror of https://github.com/golang/go synced 2024-11-11 17:51:49 -07:00

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>
This commit is contained in:
Alan Donovan 2023-04-23 13:45:08 -04:00
parent 22d94dfdc8
commit a1284d0185
3 changed files with 135 additions and 0 deletions

1
api/next/28089.txt Normal file
View File

@ -0,0 +1 @@
pkg go/ast, func IsGenerated(*File) bool #28089

View File

@ -1073,3 +1073,40 @@ type Package struct {
func (p *Package) Pos() token.Pos { return token.NoPos }
func (p *Package) End() token.Pos { return token.NoPos }
// IsGenerated reports whether the file was generated by a program,
// not handwritten, by detecting the special comment described
// at https://go.dev/s/generatedcode.
//
// The syntax tree must have been parsed with the ParseComments flag.
// Example:
//
// f, err := parser.ParseFile(fset, filename, src, parser.ParseComments|parser.PackageClauseOnly)
// if err != nil { ... }
// gen := ast.IsGenerated(f)
func IsGenerated(file *File) bool {
_, ok := generator(file)
return ok
}
func generator(file *File) (string, bool) {
for _, group := range file.Comments {
for _, comment := range group.List {
if comment.Pos() > file.Package {
break // after package declaration
}
// opt: check Contains first to avoid unnecessary array allocation in Split.
const prefix = "// Code generated "
if strings.Contains(comment.Text, prefix) {
for _, line := range strings.Split(comment.Text, "\n") {
if rest, ok := strings.CutPrefix(line, prefix); ok {
if gen, ok := strings.CutSuffix(rest, " DO NOT EDIT."); ok {
return gen, true
}
}
}
}
}
}
return "", false
}

View File

@ -40,3 +40,100 @@ func TestIssue33649(t *testing.T) {
}
}
}
// TestIssue28089 exercises the IsGenerated function.
func TestIssue28089(t *testing.T) {
for i, test := range []struct {
src string
want bool
}{
// No file comments.
{`package p`, false},
// Irrelevant file comments.
{`// Package p doc.
package p`, false},
// Special comment misplaced after package decl.
{`// Package p doc.
package p
// Code generated by gen. DO NOT EDIT.
`, false},
// Special comment appears inside string literal.
{`// Package p doc.
package p
const c = "` + "`" + `
// Code generated by gen. DO NOT EDIT.
` + "`" + `
`, false},
// Special comment appears properly.
{`// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package p doc comment goes here.
//
// Code generated by gen. DO NOT EDIT.
package p
... `, true},
// Special comment is indented.
//
// Strictly, the indent should cause IsGenerated to
// yield false, but we cannot detect the indent
// without either source text or a token.File.
// In other words, the function signature cannot
// implement the spec. Let's brush this under the
// rug since well-formatted code has no indent.
{`// Package p doc comment goes here.
//
// Code generated by gen. DO NOT EDIT.
package p
... `, true},
// Special comment has unwanted spaces after "DO NOT EDIT."
{`// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package p doc comment goes here.
//
// Code generated by gen. DO NOT EDIT.
package p
... `, false},
// Special comment has rogue interior space.
{`// Code generated by gen. DO NOT EDIT.
package p
`, false},
// Special comment lacks the middle portion.
{`// Code generated DO NOT EDIT.
package p
`, false},
// Special comment (incl. "//") appears within a /* block */ comment,
// an obscure corner case of the spec.
{`/* start of a general comment
// Code generated by tool; DO NOT EDIT.
end of a general comment */
// +build !dev
// Package comment.
package p
// Does match even though it's inside general comment (/*-style).
`, true},
} {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", test.src, parser.PackageClauseOnly|parser.ParseComments)
if f == nil {
t.Fatalf("parse %d failed to return AST: %v", i, err)
}
got := ast.IsGenerated(f)
if got != test.want {
t.Errorf("%d: IsGenerated on <<%s>> returned %t", i, test.src, got)
}
}
}