2022-09-14 10:15:09 -04:00
package codegen
import (
"bytes"
"fmt"
"go/format"
"go/parser"
"go/token"
"os"
"path/filepath"
2022-11-10 15:36:40 -05:00
"regexp"
2022-09-14 10:15:09 -04:00
"strings"
2022-12-22 07:20:02 -05:00
"github.com/dave/dst"
2023-01-17 11:58:08 +01:00
"github.com/dave/dst/decorator"
2022-12-22 07:20:02 -05:00
"github.com/dave/dst/dstutil"
2022-09-14 10:15:09 -04:00
"golang.org/x/tools/imports"
)
type genGoFile struct {
path string
2023-01-17 11:58:08 +01:00
walker dstutil . ApplyFunc
2022-09-14 10:15:09 -04:00
in [] byte
}
func postprocessGoFile ( cfg genGoFile ) ([] byte , error ) {
fname := filepath . Base ( cfg . path )
buf := new ( bytes . Buffer )
fset := token . NewFileSet ()
2023-01-17 11:58:08 +01:00
gf , err := decorator . ParseFile ( fset , fname , string ( cfg . in ), parser . ParseComments )
2022-09-14 10:15:09 -04:00
if err != nil {
return nil , fmt . Errorf ( "error parsing generated file: %w" , err )
}
if cfg . walker != nil {
2023-01-17 11:58:08 +01:00
dstutil . Apply ( gf , cfg . walker , nil )
2022-09-14 10:15:09 -04:00
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
}
2022-11-10 15:36:40 -05:00
type prefixmod struct {
2022-11-15 08:48:31 -05:00
prefix string
replace string
2022-11-10 15:36:40 -05:00
rxp * regexp . Regexp
rxpsuff * regexp . Regexp
}
2023-01-17 11:58:08 +01:00
// PrefixDropper returns a dstutil.ApplyFunc that removes the provided prefix
2022-11-10 15:36:40 -05:00
// string when it appears as a leading sequence in type names, var names, and
// comments in a generated Go file.
2023-01-17 11:58:08 +01:00
func PrefixDropper ( prefix string ) dstutil . ApplyFunc {
2022-11-10 15:36:40 -05:00
return ( & prefixmod {
2022-11-15 08:48:31 -05:00
prefix : prefix ,
rxpsuff : regexp . MustCompile ( fmt . Sprintf ( `%s([a-zA-Z_]+)` , prefix )),
rxp : regexp . MustCompile ( fmt . Sprintf ( `%s([\s.,;-])` , prefix )),
}). applyfunc
}
2023-01-17 11:58:08 +01:00
// PrefixReplacer returns a dstutil.ApplyFunc that removes the provided prefix
2022-11-15 08:48:31 -05:00
// 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.
2023-01-17 11:58:08 +01:00
func PrefixReplacer ( prefix , replace string ) dstutil . ApplyFunc {
2022-11-15 08:48:31 -05:00
return ( & prefixmod {
prefix : prefix ,
replace : replace ,
2022-11-10 15:36:40 -05:00
rxpsuff : regexp . MustCompile ( fmt . Sprintf ( `%s([a-zA-Z_]+)` , prefix )),
rxp : regexp . MustCompile ( fmt . Sprintf ( `%s([\s.,;-])` , prefix )),
}). applyfunc
}
2023-01-17 11:58:08 +01:00
func depoint ( e dst . Expr ) dst . Expr {
if star , is := e .( * dst . StarExpr ); is {
2022-11-10 15:36:40 -05:00
return star . X
}
return e
}
2023-01-17 11:58:08 +01:00
func ( d prefixmod ) applyfunc ( c * dstutil . Cursor ) bool {
2022-11-10 15:36:40 -05:00
n := c . Node ()
switch x := n .( type ) {
2023-01-17 11:58:08 +01:00
case * dst . ValueSpec :
2022-11-10 15:36:40 -05:00
d . handleExpr ( x . Type )
for _ , id := range x . Names {
d . do ( id )
}
2023-01-17 11:58:08 +01:00
case * dst . TypeSpec :
2022-11-10 15:36:40 -05:00
// Always do typespecs
d . do ( x . Name )
2023-01-17 11:58:08 +01:00
case * dst . Field :
2022-11-10 15:36:40 -05:00
// Don't rename struct fields. We just want to rename type declarations, and
// field value specifications that reference those types.
d . handleExpr ( x . Type )
2023-01-17 11:58:08 +01:00
case * dst . File :
2023-01-31 04:50:08 -05:00
for _ , def := range x . Decls {
comments := def . Decorations (). Start . All ()
def . Decorations (). Start . Clear ()
2023-01-17 11:58:08 +01:00
// 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
2023-01-31 04:50:08 -05:00
def . Decorations (). Start . Append ( d . rxpsuff . ReplaceAllString ( c , "$1" ))
2023-01-17 11:58:08 +01:00
if d . replace != "" {
2023-01-31 04:50:08 -05:00
def . Decorations (). Start . Append ( d . rxp . ReplaceAllString ( c , d . replace + "$1" ))
2023-01-17 11:58:08 +01:00
}
}
2022-11-10 15:36:40 -05:00
}
}
}
return true
}
2023-01-17 11:58:08 +01:00
func ( d prefixmod ) handleExpr ( e dst . Expr ) {
2022-11-10 15:36:40 -05:00
// Deref a StarExpr, if there is one
expr := depoint ( e )
switch x := expr .( type ) {
2023-01-17 11:58:08 +01:00
case * dst . Ident :
2022-11-10 15:36:40 -05:00
d . do ( x )
2023-01-17 11:58:08 +01:00
case * dst . ArrayType :
if id , is := depoint ( x . Elt ).( * dst . Ident ); is {
2022-11-10 15:36:40 -05:00
d . do ( id )
}
2023-01-17 11:58:08 +01:00
case * dst . MapType :
if id , is := depoint ( x . Key ).( * dst . Ident ); is {
2022-11-10 15:36:40 -05:00
d . do ( id )
}
2023-01-17 11:58:08 +01:00
if id , is := depoint ( x . Value ).( * dst . Ident ); is {
2022-11-10 15:36:40 -05:00
d . do ( id )
}
}
}
2023-01-17 11:58:08 +01:00
func ( d prefixmod ) do ( n * dst . Ident ) {
2022-11-15 08:48:31 -05:00
if n . Name != d . prefix {
n . Name = strings . TrimPrefix ( n . Name , d . prefix )
} else if d . replace != "" {
n . Name = d . replace
2022-11-10 15:36:40 -05:00
}
}