Skip to content

chore: Add generics to typescript generator #4658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: Support generating generics in interfaces
  • Loading branch information
Emyrk committed Oct 19, 2022
commit 02aa9cea13c5685df721982f1b19d042ef5fb296
84 changes: 76 additions & 8 deletions scripts/apitypings/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ func main() {
// TypescriptTypes holds all the code blocks created.
type TypescriptTypes struct {
// Each entry is the type name, and it's typescript code block.
Types map[string]string
Enums map[string]string
Types map[string]string
Enums map[string]string
Generics map[string]string
}

// String just combines all the codeblocks.
Expand All @@ -50,16 +51,21 @@ func (t TypescriptTypes) String() string {

sortedTypes := make([]string, 0, len(t.Types))
sortedEnums := make([]string, 0, len(t.Enums))
sortedGenerics := make([]string, 0, len(t.Generics))

for k := range t.Types {
sortedTypes = append(sortedTypes, k)
}
for k := range t.Enums {
sortedEnums = append(sortedEnums, k)
}
for k := range t.Generics {
sortedGenerics = append(sortedGenerics, k)
}

sort.Strings(sortedTypes)
sort.Strings(sortedEnums)
sort.Strings(sortedGenerics)

for _, k := range sortedTypes {
v := t.Types[k]
Expand All @@ -73,6 +79,12 @@ func (t TypescriptTypes) String() string {
_, _ = s.WriteRune('\n')
}

for _, k := range sortedGenerics {
v := t.Generics[k]
_, _ = s.WriteString(v)
_, _ = s.WriteRune('\n')
}

return strings.TrimRight(s.String(), "\n")
}

Expand Down Expand Up @@ -129,6 +141,7 @@ func (g *Generator) parsePackage(ctx context.Context, patterns ...string) error
// generateAll will generate for all types found in the pkg
func (g *Generator) generateAll() (*TypescriptTypes, error) {
structs := make(map[string]string)
generics := make(map[string]string)
enums := make(map[string]types.Object)
enumConsts := make(map[string][]*types.Const)

Expand Down Expand Up @@ -170,12 +183,11 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) {
if !ok {
panic("all typename should be named types")
}
switch named.Underlying().(type) {
switch underNamed := named.Underlying().(type) {
case *types.Struct:
// type <Name> struct
// Structs are obvious.
st, _ := obj.Type().Underlying().(*types.Struct)
codeBlock, err := g.buildStruct(obj, st)
codeBlock, err := g.buildStruct(obj, underNamed)
if err != nil {
return nil, xerrors.Errorf("generate %q: %w", obj.Name(), err)
}
Expand Down Expand Up @@ -205,7 +217,35 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) {
str.WriteString(fmt.Sprintf("export type %s = %s\n", obj.Name(), ts.ValueType))
structs[obj.Name()] = str.String()
case *types.Array, *types.Slice:
// TODO: @emyrk if you need this, follow the same design as "*types.Map" case.
// TODO: @emyrk if you need this, follow the same design as "*types.Map" case.
case *types.Interface:
// Interfaces are used as generics. Non-generic interfaces are
// not supported.
if underNamed.NumEmbeddeds() == 1 {
union, ok := underNamed.EmbeddedType(0).(*types.Union)
if !ok {
// If the underlying is not a union, but has 1 type. It's
// just that one type.
union = types.NewUnion([]*types.Term{
// Set the tilde to true to support underlying.
// Doesn't actually affect our generation.
types.NewTerm(true, underNamed.EmbeddedType(0)),
})
}

block, err := g.buildUnion(obj, union)
if err != nil {
return nil, xerrors.Errorf("generate union %q: %w", obj.Name(), err)
}
generics[obj.Name()] = block
}
case *types.Signature:
// Ignore named functions.
default:
// If you hit this error, you added a new unsupported named type.
// The easiest way to solve this is add a new case above with
// your type and a TODO to implement it.
return nil, xerrors.Errorf("unsupported named type %q", underNamed.String())
}
case *types.Var:
// TODO: Are any enums var declarations? This is also codersdk.Me.
Expand Down Expand Up @@ -242,8 +282,9 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) {
}

return &TypescriptTypes{
Types: structs,
Enums: enumCodeBlocks,
Types: structs,
Enums: enumCodeBlocks,
Generics: generics,
}, nil
}

Expand All @@ -252,6 +293,33 @@ func (g *Generator) posLine(obj types.Object) string {
return fmt.Sprintf("// From %s\n", filepath.Join("codersdk", filepath.Base(file.Name())))
}

// buildStruct just prints the typescript def for a type.
func (g *Generator) buildUnion(obj types.Object, st *types.Union) (string, error) {
var s strings.Builder
_, _ = s.WriteString(g.posLine(obj))

allTypes := make([]string, 0, st.Len())
var optional bool
for i := 0; i < st.Len(); i++ {
term := st.Term(i)
scriptType, err := g.typescriptType(term.Type())
if err != nil {
return "", xerrors.Errorf("union %q for %q failed to get type: %w", st.String(), obj.Name(), err)
}
allTypes = append(allTypes, scriptType.ValueType)
optional = optional || scriptType.Optional
}

qMark := ""
if optional {
qMark = "?"
}

s.WriteString(fmt.Sprintf("export type %s%s = %s\n", obj.Name(), qMark, strings.Join(allTypes, " | ")))

return s.String(), nil
}

// buildStruct just prints the typescript def for a type.
func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, error) {
var s strings.Builder
Expand Down