Skip to content

Commit 0e36824

Browse files
committed
Switch struct to a template
1 parent 02aa9ce commit 0e36824

File tree

1 file changed

+59
-11
lines changed

1 file changed

+59
-11
lines changed

scripts/apitypings/main.go

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"go/types"
@@ -10,6 +11,7 @@ import (
1011
"regexp"
1112
"sort"
1213
"strings"
14+
"text/template"
1315

1416
"github.com/fatih/structtag"
1517
"golang.org/x/tools/go/packages"
@@ -320,11 +322,33 @@ func (g *Generator) buildUnion(obj types.Object, st *types.Union) (string, error
320322
return s.String(), nil
321323
}
322324

325+
type structTemplateState struct {
326+
PosLine string
327+
Name string
328+
Fields []string
329+
Extends string
330+
AboveLine string
331+
}
332+
333+
const structTemplate = `{{ .PosLine -}}
334+
{{ if .AboveLine }}{{ .AboveLine }}
335+
{{ end }}export interface {{ .Name }}{{ if .Extends }} extends {{ .Extends }}{{ end }}{
336+
{{- range .Fields }}
337+
{{ . -}}
338+
{{- end }}
339+
}
340+
`
341+
323342
// buildStruct just prints the typescript def for a type.
324343
func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, error) {
325-
var s strings.Builder
326-
_, _ = s.WriteString(g.posLine(obj))
327-
_, _ = s.WriteString(fmt.Sprintf("export interface %s ", obj.Name()))
344+
state := structTemplateState{}
345+
tpl, err := template.New("struct").Parse(structTemplate)
346+
if err != nil {
347+
return "", xerrors.Errorf("parse struct template: %w", err)
348+
}
349+
350+
state.PosLine = g.posLine(obj)
351+
state.Name = obj.Name()
328352

329353
// Handle named embedded structs in the codersdk package via extension.
330354
var extends []string
@@ -340,10 +364,9 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
340364
}
341365
}
342366
if len(extends) > 0 {
343-
_, _ = s.WriteString(fmt.Sprintf("extends %s ", strings.Join(extends, ", ")))
367+
state.Extends = strings.Join(extends, ", ")
344368
}
345369

346-
_, _ = s.WriteString("{\n")
347370
// For each field in the struct, we print 1 line of the typescript interface
348371
for i := 0; i < st.NumFields(); i++ {
349372
if extendedFields[i] {
@@ -403,21 +426,29 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
403426
}
404427

405428
if tsType.AboveTypeLine != "" {
406-
_, _ = s.WriteString(tsType.AboveTypeLine)
407-
_, _ = s.WriteRune('\n')
429+
state.AboveLine = tsType.AboveTypeLine
408430
}
409431
optional := ""
410432
if jsonOptional || tsType.Optional {
411433
optional = "?"
412434
}
413-
_, _ = s.WriteString(fmt.Sprintf("%sreadonly %s%s: %s\n", indent, jsonName, optional, tsType.ValueType))
435+
state.Fields = append(state.Fields, fmt.Sprintf("%sreadonly %s%s: %s", indent, jsonName, optional, tsType.ValueType))
414436
}
415-
_, _ = s.WriteString("}\n")
416-
return s.String(), nil
437+
438+
data := bytes.NewBuffer(make([]byte, 0))
439+
err = tpl.Execute(data, state)
440+
if err != nil {
441+
return "", xerrors.Errorf("execute struct template: %w", err)
442+
}
443+
return data.String(), nil
417444
}
418445

419446
type TypescriptType struct {
420-
ValueType string
447+
// GenericMapping gives a unique character for mapping the value type
448+
// to a generic. This is only useful if you can use generic syntax.
449+
// This is optional, as the ValueType will have the correct constraints.
450+
GenericMapping string
451+
ValueType string
421452
// AboveTypeLine lets you put whatever text you want above the typescript
422453
// type line.
423454
AboveTypeLine string
@@ -566,6 +597,23 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
566597
AboveTypeLine: indentedComment("eslint-disable-next-line")}, nil
567598
}
568599
return TypescriptType{}, xerrors.New("only empty interface types are supported")
600+
case *types.TypeParam:
601+
_, ok := ty.Underlying().(*types.Interface)
602+
if !ok {
603+
// If it's not an interface, it is likely a usage of generics that
604+
// we have not hit yet. Feel free to add support for it.
605+
return TypescriptType{}, xerrors.New("type param must be an interface")
606+
}
607+
608+
generic := ty.Constraint()
609+
// This is kinda a hack, but we want just the end of the name.
610+
name := strings.TrimSuffix("github.com/coder/coder/codersdk.", generic.String())
611+
return TypescriptType{
612+
GenericMapping: ty.Obj().Name(),
613+
ValueType: name,
614+
AboveTypeLine: "",
615+
Optional: false,
616+
}, nil
569617
}
570618

571619
// These are all the other types we need to support.

0 commit comments

Comments
 (0)