mirror of
https://github.com/foomo/sesamy-cli.git
synced 2025-10-16 12:35:36 +00:00
246 lines
6.3 KiB
Go
246 lines
6.3 KiB
Go
package typescript
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/types"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/foomo/sesamy-cli/internal"
|
|
"github.com/stoewer/go-strcase"
|
|
"golang.org/x/exp/maps"
|
|
)
|
|
|
|
type Builder struct {
|
|
scanner *internal.Scanner
|
|
packageNameReplacer *strings.Replacer
|
|
}
|
|
|
|
func NewBuilder(scanner *internal.Scanner) *Builder {
|
|
return &Builder{
|
|
scanner: scanner,
|
|
packageNameReplacer: strings.NewReplacer(".", "_", "/", "_", "-", "_"),
|
|
}
|
|
}
|
|
|
|
// func (b *Builder) AddStruct(name string, s *types.Struct) {
|
|
// b.structs[name] = s
|
|
// }
|
|
//
|
|
// func (b *Builder) AddStructs(v map[string]*types.Struct) {
|
|
// maps.Copy(b.structs, v)
|
|
// }
|
|
//
|
|
// func (b *Builder) AddImport(pkg string, names ...string) {
|
|
// b.imports[pkg] = append(b.imports[pkg], names...)
|
|
// }
|
|
|
|
type Imports map[string][]string
|
|
|
|
func (i Imports) String() string {
|
|
var ret string
|
|
keys := maps.Keys(i)
|
|
sort.Strings(keys)
|
|
for _, name := range keys {
|
|
vals := i[name]
|
|
slices.Sort(vals)
|
|
ret += fmt.Sprintf("import { %s } from '%s';\n", strings.Join(vals, ", "), name)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
type Package struct {
|
|
pkg *internal.ScannerPackage
|
|
packageNameReplacer *strings.Replacer
|
|
Filename string
|
|
headBuilder *SBuilder
|
|
importsBuilder *SBuilder
|
|
strucstBuilder *SBuilder
|
|
}
|
|
|
|
type SBuilder struct {
|
|
*strings.Builder
|
|
}
|
|
|
|
func NewSBuilder() *SBuilder {
|
|
return &SBuilder{
|
|
Builder: &strings.Builder{},
|
|
}
|
|
}
|
|
|
|
func (s SBuilder) NL() {
|
|
s.WriteString("\n")
|
|
}
|
|
|
|
func (s SBuilder) Commentf(indent int, format string, args ...interface{}) {
|
|
prefix := strings.Repeat("\t", indent)
|
|
s.WriteString(fmt.Sprintf(prefix+"// "+format+"\n", args...))
|
|
}
|
|
|
|
func (s SBuilder) Codef(indent int, format string, args ...interface{}) {
|
|
prefix := strings.Repeat("\t", indent)
|
|
s.WriteString(fmt.Sprintf(prefix+format+"\n", args...))
|
|
}
|
|
|
|
func NewPackage(pkg *internal.ScannerPackage, packageNameReplacer *strings.Replacer) *Package {
|
|
return &Package{
|
|
pkg: pkg,
|
|
Filename: packageNameReplacer.Replace(pkg.PkgPath) + ".ts",
|
|
packageNameReplacer: packageNameReplacer,
|
|
headBuilder: NewSBuilder(),
|
|
importsBuilder: NewSBuilder(),
|
|
strucstBuilder: NewSBuilder(),
|
|
}
|
|
}
|
|
|
|
func (b *Package) EventPackage(ctx context.Context) (string, error) {
|
|
// render header
|
|
b.headBuilder.Commentf(0, `Code generated by sesamy. DO NOT EDIT.`)
|
|
|
|
// render imports
|
|
for name := range b.pkg.Imports {
|
|
name = b.packageNameReplacer.Replace(name)
|
|
b.importsBuilder.Codef(0, `import * as %s from './%s.ts';`, name, name)
|
|
}
|
|
b.importsBuilder.Codef(0, `import { collect } from '@foomo/sesamy';`)
|
|
|
|
// iterate scope types
|
|
names := maps.Keys(b.pkg.Scope)
|
|
slices.Sort(names)
|
|
for _, name := range names {
|
|
scope := b.pkg.Scope[name]
|
|
switch t := scope.Underlying().(type) {
|
|
case *types.Struct:
|
|
b.strucstBuilder.Codef(0, "export interface %s {", name)
|
|
for i := range t.NumFields() {
|
|
tag, err := internal.ParseTag(t.Tag(i))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if tag != "" {
|
|
b.strucstBuilder.Codef(1, "%s: %s;", tag, b.typeType(t.Field(i).Type()))
|
|
}
|
|
}
|
|
b.strucstBuilder.Codef(0, "};")
|
|
b.strucstBuilder.NL()
|
|
|
|
b.strucstBuilder.Codef(0, "export const %s = (params: %s) => {", strcase.LowerCamelCase(name), name)
|
|
b.strucstBuilder.Codef(1, "collect({ name:'%s', params });", strcase.SnakeCase(name))
|
|
b.strucstBuilder.Codef(0, "};")
|
|
b.strucstBuilder.NL()
|
|
}
|
|
}
|
|
|
|
s := &strings.Builder{}
|
|
s.WriteString(b.headBuilder.String())
|
|
s.WriteString(b.importsBuilder.String())
|
|
s.WriteString("\n")
|
|
s.WriteString(b.strucstBuilder.String())
|
|
return s.String(), nil
|
|
}
|
|
|
|
func (b *Package) DependencyPackage(ctx context.Context) (string, error) {
|
|
// render header
|
|
b.headBuilder.Commentf(0, `Code generated by sesamy. DO NOT EDIT.`)
|
|
|
|
// render imports
|
|
for name := range b.pkg.Imports {
|
|
name = b.packageNameReplacer.Replace(name)
|
|
b.importsBuilder.Codef(0, `import * as %s from './%s.ts';`, name, name)
|
|
}
|
|
|
|
// iterate scope types
|
|
names := maps.Keys(b.pkg.Scope)
|
|
slices.Sort(names)
|
|
for _, name := range names {
|
|
scope := b.pkg.Scope[name]
|
|
|
|
switch scope.Underlying().(type) {
|
|
case *types.Basic:
|
|
// lookup scope type with underlying types e.g. const ScopeTypeCustom ScopeType = "custom"
|
|
if u := b.pkg.Scope.LookupUnderlyingTypeName(name); len(u) > 0 {
|
|
uNames := maps.Keys(u)
|
|
sort.Strings(uNames)
|
|
b.strucstBuilder.Codef(0, "export enum %s {", name)
|
|
for _, uName := range uNames {
|
|
if valueSpec, ok := b.pkg.Values.Lookup(uName).Values[0].(*ast.BasicLit); ok {
|
|
b.strucstBuilder.Codef(1, "%s = %s,", strings.TrimPrefix(uName, name), valueSpec.Value)
|
|
}
|
|
}
|
|
b.strucstBuilder.Codef(0, "};")
|
|
}
|
|
}
|
|
}
|
|
|
|
s := &strings.Builder{}
|
|
s.WriteString(b.headBuilder.String())
|
|
s.WriteString(b.importsBuilder.String())
|
|
s.WriteString("\n")
|
|
s.WriteString(b.strucstBuilder.String())
|
|
return s.String(), nil
|
|
}
|
|
|
|
func (b *Builder) Package(ctx context.Context, paths []string) (map[string]string, error) {
|
|
var err error
|
|
ret := make(map[string]string)
|
|
for name, scannerPackage := range b.scanner.Packages {
|
|
var s string
|
|
pkg := NewPackage(scannerPackage, b.packageNameReplacer)
|
|
if slices.Contains(paths, scannerPackage.PkgPath) {
|
|
s, err = pkg.EventPackage(ctx)
|
|
} else {
|
|
s, err = pkg.DependencyPackage(ctx)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret[name] = s
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// func (b *Builder) String(pkgs []*internal.Package) (map[string]string, error) {
|
|
// ret := make(map[string]string)
|
|
// for _, pkg := range pkgs {
|
|
// s, err := b.Package(pkg)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// ret[b.packageNameReplacer.Replace(pkg.Path)] = s
|
|
// }
|
|
// return ret, nil
|
|
// }
|
|
|
|
func (b *Package) typeType(value types.Type) string {
|
|
switch t := value.(type) {
|
|
case *types.Basic:
|
|
return b.basicInfoType(t.Info())
|
|
case *types.Named:
|
|
return b.namedType(t) + "." + t.Obj().Name()
|
|
case *types.Slice:
|
|
return "Array<" + b.typeType(t.Elem()) + ">"
|
|
default:
|
|
return "any"
|
|
}
|
|
}
|
|
|
|
func (b *Package) namedType(t *types.Named) string {
|
|
return b.packageNameReplacer.Replace(t.Obj().Pkg().Path())
|
|
}
|
|
|
|
func (b *Package) basicInfoType(c types.BasicInfo) string {
|
|
switch {
|
|
case c&types.IsNumeric != 0:
|
|
return "number"
|
|
case c&types.IsString != 0:
|
|
return "string"
|
|
case c&types.IsBoolean != 0:
|
|
return "boolean"
|
|
default:
|
|
return "any"
|
|
}
|
|
}
|