mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 18:34:52 -06:00
* Update ast to dst * Sort imports * Update thema * Update pkg/codegen/util_go.go Co-authored-by: sam boyer <sdboyer@grafana.com> * Move DecoderCompactor into ApplyFuncs * Remove unnecessary file * Use dst decorator * Downgrade parca-dev library Co-authored-by: sam boyer <sdboyer@grafana.com>
247 lines
6.2 KiB
Go
247 lines
6.2 KiB
Go
package codegen
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/format"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/dave/dst"
|
|
"github.com/dave/dst/decorator"
|
|
"github.com/dave/dst/dstutil"
|
|
"golang.org/x/tools/imports"
|
|
)
|
|
|
|
type genGoFile struct {
|
|
path string
|
|
walker dstutil.ApplyFunc
|
|
in []byte
|
|
}
|
|
|
|
func postprocessGoFile(cfg genGoFile) ([]byte, error) {
|
|
fname := filepath.Base(cfg.path)
|
|
buf := new(bytes.Buffer)
|
|
fset := token.NewFileSet()
|
|
gf, err := decorator.ParseFile(fset, fname, string(cfg.in), parser.ParseComments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing generated file: %w", err)
|
|
}
|
|
|
|
if cfg.walker != nil {
|
|
dstutil.Apply(gf, cfg.walker, nil)
|
|
|
|
err = format.Node(buf, fset, gf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error formatting Go AST: %w", err)
|
|
}
|
|
} else {
|
|
buf = bytes.NewBuffer(cfg.in)
|
|
}
|
|
|
|
byt, err := imports.Process(fname, buf.Bytes(), nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("goimports processing failed: %w", err)
|
|
}
|
|
|
|
// Compare imports before and after; warn about performance if some were added
|
|
gfa, _ := parser.ParseFile(fset, fname, string(byt), parser.ParseComments)
|
|
imap := make(map[string]bool)
|
|
for _, im := range gf.Imports {
|
|
imap[im.Path.Value] = true
|
|
}
|
|
var added []string
|
|
for _, im := range gfa.Imports {
|
|
if !imap[im.Path.Value] {
|
|
added = append(added, im.Path.Value)
|
|
}
|
|
}
|
|
|
|
if len(added) != 0 {
|
|
// TODO improve the guidance in this error if/when we better abstract over imports to generate
|
|
fmt.Fprintf(os.Stderr, "The following imports were added by goimports while generating %s: \n\t%s\nRelying on goimports to find imports significantly slows down code generation. Consider adding these to the relevant template.\n", cfg.path, strings.Join(added, "\n\t"))
|
|
}
|
|
|
|
return byt, nil
|
|
}
|
|
|
|
type prefixmod struct {
|
|
prefix string
|
|
replace string
|
|
rxp *regexp.Regexp
|
|
rxpsuff *regexp.Regexp
|
|
}
|
|
|
|
// PrefixDropper returns a dstutil.ApplyFunc that removes the provided prefix
|
|
// string when it appears as a leading sequence in type names, var names, and
|
|
// comments in a generated Go file.
|
|
func PrefixDropper(prefix string) dstutil.ApplyFunc {
|
|
return (&prefixmod{
|
|
prefix: prefix,
|
|
rxpsuff: regexp.MustCompile(fmt.Sprintf(`%s([a-zA-Z_]+)`, prefix)),
|
|
rxp: regexp.MustCompile(fmt.Sprintf(`%s([\s.,;-])`, prefix)),
|
|
}).applyfunc
|
|
}
|
|
|
|
// PrefixReplacer returns a dstutil.ApplyFunc that removes the provided prefix
|
|
// string when it appears as a leading sequence in type names, var names, and
|
|
// comments in a generated Go file.
|
|
//
|
|
// When an exact match for prefix is found, the provided replace string
|
|
// is substituted.
|
|
func PrefixReplacer(prefix, replace string) dstutil.ApplyFunc {
|
|
return (&prefixmod{
|
|
prefix: prefix,
|
|
replace: replace,
|
|
rxpsuff: regexp.MustCompile(fmt.Sprintf(`%s([a-zA-Z_]+)`, prefix)),
|
|
rxp: regexp.MustCompile(fmt.Sprintf(`%s([\s.,;-])`, prefix)),
|
|
}).applyfunc
|
|
}
|
|
|
|
func depoint(e dst.Expr) dst.Expr {
|
|
if star, is := e.(*dst.StarExpr); is {
|
|
return star.X
|
|
}
|
|
return e
|
|
}
|
|
|
|
func (d prefixmod) applyfunc(c *dstutil.Cursor) bool {
|
|
n := c.Node()
|
|
|
|
switch x := n.(type) {
|
|
case *dst.ValueSpec:
|
|
d.handleExpr(x.Type)
|
|
for _, id := range x.Names {
|
|
d.do(id)
|
|
}
|
|
case *dst.TypeSpec:
|
|
// Always do typespecs
|
|
d.do(x.Name)
|
|
case *dst.Field:
|
|
// Don't rename struct fields. We just want to rename type declarations, and
|
|
// field value specifications that reference those types.
|
|
d.handleExpr(x.Type)
|
|
case *dst.File:
|
|
for _, decl := range x.Decls {
|
|
comments := decl.Decorations().Start.All()
|
|
decl.Decorations().Start.Clear()
|
|
// For any reason, sometimes it retrieves the comment duplicated 🤷
|
|
commentMap := make(map[string]bool)
|
|
for _, c := range comments {
|
|
if _, ok := commentMap[c]; !ok {
|
|
commentMap[c] = true
|
|
decl.Decorations().Start.Append(d.rxpsuff.ReplaceAllString(c, "$1"))
|
|
if d.replace != "" {
|
|
decl.Decorations().Start.Append(d.rxp.ReplaceAllString(c, d.replace+"$1"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (d prefixmod) handleExpr(e dst.Expr) {
|
|
// Deref a StarExpr, if there is one
|
|
expr := depoint(e)
|
|
switch x := expr.(type) {
|
|
case *dst.Ident:
|
|
d.do(x)
|
|
case *dst.ArrayType:
|
|
if id, is := depoint(x.Elt).(*dst.Ident); is {
|
|
d.do(id)
|
|
}
|
|
case *dst.MapType:
|
|
if id, is := depoint(x.Key).(*dst.Ident); is {
|
|
d.do(id)
|
|
}
|
|
if id, is := depoint(x.Value).(*dst.Ident); is {
|
|
d.do(id)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d prefixmod) do(n *dst.Ident) {
|
|
if n.Name != d.prefix {
|
|
n.Name = strings.TrimPrefix(n.Name, d.prefix)
|
|
} else if d.replace != "" {
|
|
n.Name = d.replace
|
|
}
|
|
}
|
|
|
|
func isSingleTypeDecl(gd *dst.GenDecl) bool {
|
|
if gd.Tok == token.TYPE && len(gd.Specs) == 1 {
|
|
_, is := gd.Specs[0].(*dst.TypeSpec)
|
|
return is
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isAdditionalPropertiesStruct(tspec *dst.TypeSpec) (dst.Expr, bool) {
|
|
strct, is := tspec.Type.(*dst.StructType)
|
|
if is && len(strct.Fields.List) == 1 && strct.Fields.List[0].Names[0].Name == "AdditionalProperties" {
|
|
return strct.Fields.List[0].Type, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func DecoderCompactor() dstutil.ApplyFunc {
|
|
return func(c *dstutil.Cursor) bool {
|
|
f, is := c.Node().(*dst.File)
|
|
if !is {
|
|
return false
|
|
}
|
|
|
|
compact := make(map[string]bool)
|
|
// walk the file decls
|
|
for _, decl := range f.Decls {
|
|
if fd, is := decl.(*dst.FuncDecl); is {
|
|
compact[ddepoint(fd.Recv.List[0].Type).(*dst.Ident).Name] = true
|
|
}
|
|
}
|
|
if len(compact) == 0 {
|
|
return false
|
|
}
|
|
|
|
replace := make(map[string]dst.Expr)
|
|
// Walk again, looking for types we found
|
|
for _, decl := range f.Decls {
|
|
if gd, is := decl.(*dst.GenDecl); is && isSingleTypeDecl(gd) {
|
|
if tspec := gd.Specs[0].(*dst.TypeSpec); compact[tspec.Name.Name] {
|
|
if expr, is := isAdditionalPropertiesStruct(tspec); is {
|
|
replace[tspec.Name.Name] = expr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dstutil.Apply(f, func(c *dstutil.Cursor) bool {
|
|
switch x := c.Node().(type) {
|
|
case *dst.FuncDecl:
|
|
c.Delete()
|
|
case *dst.GenDecl:
|
|
if isSingleTypeDecl(x) && compact[x.Specs[0].(*dst.TypeSpec).Name.Name] {
|
|
c.Delete()
|
|
}
|
|
case *dst.Field:
|
|
if id, is := ddepoint(x.Type).(*dst.Ident); is {
|
|
if expr, has := replace[id.Name]; has {
|
|
x.Type = expr
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}, nil)
|
|
return false
|
|
}
|
|
}
|
|
func ddepoint(e dst.Expr) dst.Expr {
|
|
if star, is := e.(*dst.StarExpr); is {
|
|
return star.X
|
|
}
|
|
return e
|
|
}
|