codegen: Introduce TS codegen veneer (#54816)
* Split all named types out into defs, etc. * Use latest cuetsy, refactor generators accordingly * Return AST type from plugin TS generator * Near-complete checkin of TS veneer code generator * First full completed pass * Improve the attribute name * Defer use of the dashboard veneer type to follow-up * Remove dummy index, prettier on veneer * Fix merge errors in gen.go * Add match field to SpecialValueMap * Fix backend lint errors
This commit is contained in:
+41
-52
@@ -17,14 +17,15 @@ import (
|
||||
"github.com/deepmap/oapi-codegen/pkg/codegen"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/grafana/cuetsy"
|
||||
tsast "github.com/grafana/cuetsy/ts/ast"
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
"github.com/grafana/thema"
|
||||
"github.com/grafana/thema/encoding/openapi"
|
||||
)
|
||||
|
||||
// ExtractedLineage contains the results of statically analyzing a Grafana
|
||||
// CoremodelDeclaration contains the results of statically analyzing a Grafana
|
||||
// directory for a Thema lineage.
|
||||
type ExtractedLineage struct {
|
||||
type CoremodelDeclaration struct {
|
||||
Lineage thema.Lineage
|
||||
// Absolute path to the coremodel's coremodel.cue file.
|
||||
LineagePath string
|
||||
@@ -48,12 +49,12 @@ type ExtractedLineage struct {
|
||||
// This loading approach is intended primarily for use with code generators, or
|
||||
// other use cases external to grafana-server backend. For code within
|
||||
// grafana-server, prefer lineage loaders provided in e.g. pkg/coremodel/*.
|
||||
func ExtractLineage(path string, lib thema.Library) (*ExtractedLineage, error) {
|
||||
func ExtractLineage(path string, lib thema.Library) (*CoremodelDeclaration, error) {
|
||||
if !filepath.IsAbs(path) {
|
||||
return nil, fmt.Errorf("must provide an absolute path, got %q", path)
|
||||
}
|
||||
|
||||
ec := &ExtractedLineage{
|
||||
ec := &CoremodelDeclaration{
|
||||
LineagePath: path,
|
||||
}
|
||||
|
||||
@@ -107,14 +108,14 @@ func ExtractLineage(path string, lib thema.Library) (*ExtractedLineage, error) {
|
||||
}
|
||||
|
||||
// toTemplateObj extracts creates a struct with all the useful strings for template generation.
|
||||
func (ls *ExtractedLineage) toTemplateObj() tplVars {
|
||||
lin := ls.Lineage
|
||||
func (cd *CoremodelDeclaration) toTemplateObj() tplVars {
|
||||
lin := cd.Lineage
|
||||
sch := thema.SchemaP(lin, thema.LatestVersion(lin))
|
||||
|
||||
return tplVars{
|
||||
Name: lin.Name(),
|
||||
LineagePath: ls.RelativePath,
|
||||
PkgPath: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", filepath.Dir(ls.RelativePath))),
|
||||
LineagePath: cd.RelativePath,
|
||||
PkgPath: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", filepath.Dir(cd.RelativePath))),
|
||||
TitleName: strings.Title(lin.Name()), // nolint
|
||||
LatestSeqv: sch.Version()[0],
|
||||
LatestSchv: sch.Version()[1],
|
||||
@@ -139,13 +140,22 @@ var nonAPITypes = map[string]bool{
|
||||
"pluginmeta": true,
|
||||
}
|
||||
|
||||
// PathVersion returns the string path element to use for the latest schema.
|
||||
// "x" if not yet canonical, otherwise, "v<major>"
|
||||
func (cd *CoremodelDeclaration) PathVersion() string {
|
||||
if !cd.IsCanonical {
|
||||
return "x"
|
||||
}
|
||||
return fmt.Sprintf("v%v", thema.LatestVersion(cd.Lineage)[0])
|
||||
}
|
||||
|
||||
// GenerateGoCoremodel generates a standard Go model struct and coremodel
|
||||
// implementation from a coremodel CUE declaration.
|
||||
//
|
||||
// The provided path must be a directory. Generated code files will be written
|
||||
// to that path. The final element of the path must match the Lineage.Name().
|
||||
func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error) {
|
||||
lin, lib := ls.Lineage, ls.Lineage.Library()
|
||||
func (cd *CoremodelDeclaration) GenerateGoCoremodel(path string) (WriteDiffer, error) {
|
||||
lin, lib := cd.Lineage, cd.Lineage.Library()
|
||||
_, name := filepath.Split(path)
|
||||
if name != lin.Name() {
|
||||
return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", lin.Name(), path)
|
||||
@@ -190,7 +200,7 @@ func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err = tmpls.Lookup("autogen_header.tmpl").Execute(buf, tvars_autogen_header{
|
||||
LineagePath: ls.RelativePath,
|
||||
LineagePath: cd.RelativePath,
|
||||
GeneratorPath: "pkg/framework/coremodel/gen.go", // FIXME hardcoding is not OK
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("error executing header template: %w", err)
|
||||
@@ -198,7 +208,7 @@ func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error
|
||||
|
||||
fmt.Fprint(buf, "\n", gostr)
|
||||
|
||||
vars := ls.toTemplateObj()
|
||||
vars := cd.toTemplateObj()
|
||||
err = tmpls.Lookup("addenda.tmpl").Execute(buf, vars)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -228,59 +238,38 @@ type tplVars struct {
|
||||
IsComposed bool
|
||||
}
|
||||
|
||||
func (ls *ExtractedLineage) GenerateTypescriptCoremodel(path string) (WriteDiffer, error) {
|
||||
_, name := filepath.Split(path)
|
||||
if name != ls.Lineage.Name() {
|
||||
return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", ls.Lineage.Name(), path)
|
||||
}
|
||||
func (cd *CoremodelDeclaration) GenerateTypescriptCoremodel() (*tsast.File, error) {
|
||||
schv := thema.SchemaP(cd.Lineage, thema.LatestVersion(cd.Lineage)).UnwrapCUE()
|
||||
|
||||
schv := thema.SchemaP(ls.Lineage, thema.LatestVersion(ls.Lineage)).UnwrapCUE()
|
||||
|
||||
parts, err := cuetsy.GenerateAST(schv, cuetsy.Config{})
|
||||
tf, err := cuetsy.GenerateAST(schv, cuetsy.Config{
|
||||
Export: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cuetsy parts gen failed: %w", err)
|
||||
return nil, fmt.Errorf("cuetsy tf gen failed: %w", err)
|
||||
}
|
||||
|
||||
top, err := cuetsy.GenerateSingleAST(strings.Title(ls.Lineage.Name()), schv, cuetsy.TypeInterface)
|
||||
top, err := cuetsy.GenerateSingleAST(strings.Title(cd.Lineage.Name()), schv, cuetsy.TypeInterface)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cuetsy top gen failed: %s", cerrors.Details(err, nil))
|
||||
}
|
||||
|
||||
// TODO until cuetsy can toposort its outputs, put the top/parent type at the bottom of the file.
|
||||
parts.Nodes = append(parts.Nodes, top.T)
|
||||
if top.D != nil {
|
||||
parts.Nodes = append(parts.Nodes, top.D)
|
||||
}
|
||||
|
||||
var strb strings.Builder
|
||||
var str string
|
||||
fpath := ls.Lineage.Name() + ".gen.ts"
|
||||
if err := tmpls.Lookup("autogen_header.tmpl").Execute(&strb, tvars_autogen_header{
|
||||
LineagePath: ls.RelativePath,
|
||||
buf := new(bytes.Buffer)
|
||||
if err := tmpls.Lookup("autogen_header.tmpl").Execute(buf, tvars_autogen_header{
|
||||
LineagePath: cd.RelativePath,
|
||||
GeneratorPath: "pkg/framework/coremodel/gen.go", // FIXME hardcoding is not OK
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("error executing header template: %w", err)
|
||||
}
|
||||
|
||||
if !ls.IsCanonical {
|
||||
fpath = fmt.Sprintf("%s_experimental.gen.ts", ls.Lineage.Name())
|
||||
strb.WriteString(`
|
||||
// This model is a WIP and not yet canonical. Consequently, its members are
|
||||
// not exported to exclude it from grafana-schema's public API surface.
|
||||
|
||||
`)
|
||||
strb.WriteString(fmt.Sprint(parts))
|
||||
// TODO replace this regexp with cuetsy config for whether members are exported
|
||||
re := regexp.MustCompile(`(?m)^export `)
|
||||
str = re.ReplaceAllLiteralString(strb.String(), "")
|
||||
} else {
|
||||
strb.WriteString(fmt.Sprint(parts))
|
||||
str = strb.String()
|
||||
tf.Doc = &tsast.Comment{
|
||||
Text: buf.String(),
|
||||
}
|
||||
|
||||
wd := NewWriteDiffer()
|
||||
wd[filepath.Join(path, fpath)] = []byte(str)
|
||||
return wd, nil
|
||||
// TODO until cuetsy can toposort its outputs, put the top/parent type at the bottom of the file.
|
||||
tf.Nodes = append(tf.Nodes, top.T)
|
||||
if top.D != nil {
|
||||
tf.Nodes = append(tf.Nodes, top.D)
|
||||
}
|
||||
return tf, nil
|
||||
}
|
||||
|
||||
type prefixDropper struct {
|
||||
@@ -319,7 +308,7 @@ func (d prefixDropper) Visit(n ast.Node) ast.Visitor {
|
||||
// GenerateCoremodelRegistry produces Go files that define a registry with
|
||||
// references to all the Go code that is expected to be generated from the
|
||||
// provided lineages.
|
||||
func GenerateCoremodelRegistry(path string, ecl []*ExtractedLineage) (WriteDiffer, error) {
|
||||
func GenerateCoremodelRegistry(path string, ecl []*CoremodelDeclaration) (WriteDiffer, error) {
|
||||
var cml []tplVars
|
||||
for _, ec := range ecl {
|
||||
cml = append(cml, ec.toTemplateObj())
|
||||
|
||||
Reference in New Issue
Block a user