sesamy-cli/pkg/typescript/builder.go
Kevin Franklin Kim ab48d6326a
wip: need added
2024-05-17 17:36:32 +02:00

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"
}
}