package main import ( "context" "fmt" "go/types" "io" "os" "reflect" "strings" "golang.org/x/tools/go/packages" "golang.org/x/xerrors" "cdr.dev/slog" "cdr.dev/slog/sloggers/sloghuman" ) func main() { ctx := context.Background() log := slog.Make(sloghuman.Sink(os.Stderr)) code, err := GenerateFromDirectory(ctx, os.Args[1], os.Args[2:]...) if err != nil { log.Fatal(ctx, "generate", slog.Error(err)) } _, _ = fmt.Print(code) } // GenerateFromDirectory will return all the typescript code blocks for a directory func GenerateFromDirectory(ctx context.Context, directory string, typeNames ...string) (string, error) { g := Generator{} err := g.parsePackage(ctx, directory) if err != nil { return "", xerrors.Errorf("parse package %q: %w", directory, err) } str, err := g.generate(typeNames...) if err != nil { return "", xerrors.Errorf("parse package %q: %w", directory, err) } return str, nil } type Generator struct { // Package we are scanning. pkg *packages.Package } // parsePackage takes a list of patterns such as a directory, and parses them. func (g *Generator) parsePackage(ctx context.Context, patterns ...string) error { cfg := &packages.Config{ // Just accept the fact we need these flags for what we want. Feel free to add // more, it'll just increase the time it takes to parse. Mode: packages.NeedTypes | packages.NeedName | packages.NeedTypesInfo | packages.NeedTypesSizes | packages.NeedSyntax, Tests: false, Context: ctx, } pkgs, err := packages.Load(cfg, patterns...) if err != nil { return xerrors.Errorf("load package: %w", err) } // Only support 1 package for now. We can expand it if we need later, we // just need to hook up multiple packages in the generator. if len(pkgs) != 1 { return xerrors.Errorf("expected 1 package, found %d", len(pkgs)) } g.pkg = pkgs[0] return nil } func (g *Generator) generate(typeNames ...string) (string, error) { sb := strings.Builder{} _, _ = fmt.Fprint(&sb, "Copy the following code into the audit.AuditableResources table\n\n") for _, typName := range typeNames { obj := g.pkg.Types.Scope().Lookup(typName) if obj == nil || obj.Type() == nil { return "", xerrors.Errorf("type doesn't exist %q", typName) } switch obj := obj.(type) { case *types.TypeName: named, ok := obj.Type().(*types.Named) if !ok { panic("all typenames should be named types") } switch typ := named.Underlying().(type) { case *types.Struct: g.writeStruct(&sb, typ, typName) default: return "", xerrors.Errorf("invalid type %T", obj) } default: return "", xerrors.Errorf("invalid type %T", obj) } } return sb.String(), nil } func (*Generator) writeStruct(w io.Writer, st *types.Struct, name string) { _, _ = fmt.Fprintf(w, "\t&database.%s{}: {\n", name) for i := 0; i < st.NumFields(); i++ { _, _ = fmt.Fprintf(w, "\t\t\"%s\": ActionIgnore, // TODO: why\n", reflect.StructTag(st.Tag(i)).Get("json")) } _, _ = fmt.Fprint(w, "\t},\n") }