Skip to content

Commit ee71acd

Browse files
committed
feat: Switch packages for typescript generation code
1 parent 8b54ea8 commit ee71acd

File tree

4 files changed

+185
-3
lines changed

4 files changed

+185
-3
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ require (
107107
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
108108
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
109109
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
110+
golang.org/x/tools v0.1.10
110111
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f
111112
google.golang.org/api v0.75.0
112113
google.golang.org/protobuf v1.28.0

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2157,6 +2157,7 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
21572157
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
21582158
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
21592159
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
2160+
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
21602161
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
21612162
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
21622163
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

scripts/apitypings/main.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package main
22

33
import (
4+
"context"
45
"fmt"
56
"go/ast"
67
"go/parser"
78
"go/token"
8-
"log"
99
"os"
1010
"path/filepath"
1111
"sort"
1212
"strings"
1313

14+
"cdr.dev/slog"
15+
"cdr.dev/slog/sloggers/sloghuman"
16+
1417
"golang.org/x/xerrors"
1518
)
1619

@@ -19,9 +22,23 @@ const (
1922
)
2023

2124
func main() {
22-
err := run()
25+
ctx := context.Background()
26+
log := slog.Make(sloghuman.Sink(os.Stderr))
27+
g := Generator{log: log}
28+
err := g.parsePackage(ctx, "./codersdk")
29+
if err != nil {
30+
panic(err)
31+
}
32+
//err = g.generate("ProvisionerJob")
33+
err = g.generateAll()
34+
if err != nil {
35+
panic(err)
36+
}
37+
return
38+
39+
err = run()
2340
if err != nil {
24-
log.Fatal(err)
41+
log.Fatal(ctx, err.Error())
2542
}
2643
}
2744

@@ -180,6 +197,7 @@ func getIdent(e ast.Expr) (*ast.Ident, string, error) {
180197
}
181198

182199
func toTsType(fieldType string) string {
200+
fmt.Println(fieldType)
183201
switch fieldType {
184202
case "bool":
185203
return "boolean"

scripts/apitypings/main2.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"go/types"
7+
"reflect"
8+
"strings"
9+
10+
"golang.org/x/tools/go/packages"
11+
"golang.org/x/xerrors"
12+
13+
"cdr.dev/slog"
14+
)
15+
16+
type Generator struct {
17+
pkg *packages.Package // Package we are scanning.
18+
log slog.Logger
19+
}
20+
21+
// parsePackage takes a list of patterns such as a directory, and parses them.
22+
// All parsed packages will accumulate "foundTypes".
23+
func (g *Generator) parsePackage(ctx context.Context, patterns ...string) error {
24+
cfg := &packages.Config{
25+
Mode: packages.NeedTypes | packages.NeedName | packages.NeedTypesInfo |
26+
packages.NeedTypesSizes | packages.NeedSyntax,
27+
Tests: false,
28+
Context: ctx,
29+
}
30+
31+
pkgs, err := packages.Load(cfg, patterns...)
32+
if err != nil {
33+
return xerrors.Errorf("load package: %w", err)
34+
}
35+
36+
if len(pkgs) != 1 {
37+
return xerrors.Errorf("expected 1 package, found %d", len(pkgs))
38+
}
39+
40+
g.pkg = pkgs[0]
41+
return nil
42+
}
43+
44+
// generateAll will generate for all types found in the pkg
45+
func (g *Generator) generateAll() error {
46+
for _, n := range g.pkg.Types.Scope().Names() {
47+
err := g.generate(n)
48+
if err != nil {
49+
return xerrors.Errorf("generate %q: %w", n, err)
50+
}
51+
}
52+
return nil
53+
}
54+
55+
// generate generates the typescript for a singular Go type.
56+
func (g *Generator) generate(typeName string) error {
57+
obj := g.pkg.Types.Scope().Lookup(typeName)
58+
if obj == nil || obj.Type() == nil {
59+
return xerrors.Errorf("pkg is missing type %q", typeName)
60+
}
61+
62+
st, ok := obj.Type().Underlying().(*types.Struct)
63+
if !ok {
64+
return nil
65+
//return xerrors.Errorf("only generate for structs, found %q", obj.Type().String())
66+
}
67+
68+
return g.buildStruct(obj, st)
69+
}
70+
71+
// buildStruct just prints the typescript def for a type.
72+
// TODO: Write to a buffer instead
73+
func (g *Generator) buildStruct(obj types.Object, st *types.Struct) error {
74+
var s strings.Builder
75+
s.WriteString("export interface " + obj.Name() + "{\n")
76+
for i := 0; i < st.NumFields(); i++ {
77+
field := st.Field(i)
78+
tag := reflect.StructTag(st.Tag(i))
79+
jsonName := tag.Get("json")
80+
arr := strings.Split(jsonName, ",")
81+
jsonName = arr[0]
82+
if jsonName == "" {
83+
jsonName = field.Name()
84+
}
85+
86+
ts, err := g.typescriptType(field.Type())
87+
if err != nil {
88+
return xerrors.Errorf("typescript type: %w", err)
89+
}
90+
s.WriteString(fmt.Sprintf("\treadonly %s: %s\n", jsonName, ts))
91+
}
92+
s.WriteString("}")
93+
fmt.Println(s.String())
94+
return nil
95+
}
96+
97+
// typescriptType this function returns a typescript type for a given
98+
// golang type.
99+
// Eg:
100+
// []byte returns "string"
101+
func (g *Generator) typescriptType(ty types.Type) (string, error) {
102+
switch ty.(type) {
103+
case *types.Basic:
104+
bs := ty.(*types.Basic)
105+
// All basic literals (string, bool, int, etc).
106+
// TODO: Actually ensure the golang names are ok, otherwise,
107+
// we want to put another switch to capture these types
108+
// and rename to typescript.
109+
return bs.Name(), nil
110+
case *types.Struct:
111+
// TODO: This kinda sucks right now. It just dumps the struct def
112+
return ty.String(), nil
113+
case *types.Map:
114+
// TODO: Typescript dictionary??? Object?
115+
return "map", nil
116+
case *types.Slice, *types.Array:
117+
type hasElem interface {
118+
Elem() types.Type
119+
}
120+
121+
arr := ty.(hasElem)
122+
// All byte arrays should be strings in typescript?
123+
if arr.Elem().String() == "byte" {
124+
return "string", nil
125+
}
126+
127+
// Array of underlying type.
128+
underlying, err := g.typescriptType(arr.Elem())
129+
if err != nil {
130+
return "", xerrors.Errorf("array: %w", err)
131+
}
132+
return underlying + "[]", nil
133+
case *types.Named:
134+
// Named is a named type like
135+
// type EnumExample string
136+
// Use the underlying type
137+
n := ty.(*types.Named)
138+
name := n.Obj().Name()
139+
// If we have the type, just put the name because it will be defined
140+
// elsewhere in the typescript gen.
141+
if obj := g.pkg.Types.Scope().Lookup(n.String()); obj != nil {
142+
return name, nil
143+
}
144+
145+
// If it's a struct, just use the name for now.
146+
if _, ok := ty.Underlying().(*types.Struct); ok {
147+
return name, nil
148+
}
149+
150+
// Defer to the underlying type.
151+
return g.typescriptType(ty.Underlying())
152+
case *types.Pointer:
153+
// Dereference pointers.
154+
// TODO: Nullable fields?
155+
pt := ty.(*types.Pointer)
156+
return g.typescriptType(pt.Elem())
157+
}
158+
159+
// These are all the other types we need to support.
160+
// time.Time, uuid, etc.
161+
return "", xerrors.Errorf("unknown type: %s", ty.String())
162+
}

0 commit comments

Comments
 (0)