From: Alan Donovan Date: Sun, 23 Apr 2023 17:45:08 +0000 (-0400) Subject: go/ast: add IsGenerated(*File) predicate X-Git-Tag: go1.21rc1~782 X-Git-Url: http://www.git.cypherpunks.ru/?a=commitdiff_plain;h=a1284d0185110b82b34200a13700fecacfa200fa;p=gostls13.git 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 TryBot-Result: Gopher Robot Reviewed-by: Robert Findley Reviewed-by: Dmitri Shuralyov --- diff --git a/api/next/28089.txt b/api/next/28089.txt new file mode 100644 index 0000000000..1b55914fa4 --- /dev/null +++ b/api/next/28089.txt @@ -0,0 +1 @@ +pkg go/ast, func IsGenerated(*File) bool #28089 diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index b509ef1a70..c439052610 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -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 +} diff --git a/src/go/ast/issues_test.go b/src/go/ast/issues_test.go index 788c5578b8..28d6a30fbb 100644 --- a/src/go/ast/issues_test.go +++ b/src/go/ast/issues_test.go @@ -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) + } + } + +}