diff --git a/README.md b/README.md index 5d2ff77..0ed6b87 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ interface SimpleType { `guts` is a library, not a command line utility. This is to allow configuration with code, and also helps with package resolution. -See the [simple example](./example/simple) for a basic usage of the library. A larger example can be found in the [Coder repository](https://github.com/coder/coder/blob/a632a841d4f5666c2c1690801f88cd1a1fcffc00/scripts/apitypings/main.go). +See the [simple example](./example/simple) for a basic usage of the library. A larger example can be found in the [Coder repository](https://github.com/coder/coder/blob/main/scripts/apitypings/main.go). ```go // Step 1: Create a new Golang parser @@ -87,6 +87,14 @@ And the output is: export type EnumString = "bar" | "baz" | "foo" | "qux"; ``` +# Alternative solutions + +The guts package was created to offer a more flexible, programmatic alternative to existing Go-to-TypeScript code generation tools out there. + +The other solutions out there function as command-line utilities with yaml configurability. `guts` is a library, giving it a much more flexible and dynamic configuration that static generators can’t easily support. + +Unlike many of its counterparts, guts leverages the official TypeScript compiler under the hood, ensuring that the generated TypeScript definitions are semantically correct, syntactically valid, and aligned with the latest language features. + # Helpful notes diff --git a/convert.go b/convert.go index 64151f3..09325b7 100644 --- a/convert.go +++ b/convert.go @@ -180,7 +180,14 @@ func (p *GoParser) ToTypescript() (*Typescript, error) { if err != nil { return nil, fmt.Errorf("node %q: %w", key, err) } - typescript.typescriptNodes[key] = &newNode + + // If the Node is nil, then it serves no purpose and can be + // removed from the typescriptNodes map. + if newNode.Node == nil { + delete(typescript.typescriptNodes, key) + } else { + typescript.typescriptNodes[key] = &newNode + } } return typescript, nil @@ -494,26 +501,41 @@ func (ts *Typescript) parse(obj types.Object) error { // TODO: Are any enums var declarations? This is also codersdk.Me. return nil // Maybe we should treat these like consts? case *types.Const: - // Names are very likely enums - named, ok := obj.Type().(*types.Named) - if !ok { - // It could be a raw const value to generate. - if _, ok := obj.Type().(*types.Basic); ok { - cnst, err := ts.constantDeclaration(obj) - if err != nil { - return xerrors.Errorf("basic const %q: %w", objectIdentifier.Ref(), err) + type constMethods interface { + Obj() *types.TypeName + Underlying() types.Type + } + + var use constMethods + { // TODO: This block could be cleaned up + // Names & aliases are very likely enums + named, namedOk := obj.Type().(*types.Named) + aliased, aliasOk := obj.Type().(*types.Alias) + + if !namedOk && !aliasOk { + // It could be a raw const value to generate. + if _, ok := obj.Type().(*types.Basic); ok { + cnst, err := ts.constantDeclaration(obj) + if err != nil { + return xerrors.Errorf("basic const %q: %w", objectIdentifier.Ref(), err) + } + return ts.setNode(objectIdentifier.Ref(), typescriptNode{ + Node: cnst, + }) } - return ts.setNode(objectIdentifier.Ref(), typescriptNode{ - Node: cnst, - }) + return xerrors.Errorf("const %q is not a named type", objectIdentifier.Ref()) + } + if namedOk { + use = named + } else { + use = aliased } - return xerrors.Errorf("const %q is not a named type", objectIdentifier.Ref()) } // Treat it as an enum. - enumObjName := ts.parsed.Identifier(named.Obj()) + enumObjName := ts.parsed.Identifier(use.Obj()) - switch named.Underlying().(type) { + switch use.Underlying().(type) { case *types.Basic: default: return xerrors.Errorf("const %q is not a basic type, enums only support basic", objectIdentifier.Ref()) diff --git a/go.mod b/go.mod index a3ca2d8..cdc5839 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd github.com/fatih/structtag v1.2.0 github.com/stretchr/testify v1.10.0 - golang.org/x/tools v0.33.0 + golang.org/x/tools v0.34.0 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da ) @@ -18,8 +18,8 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/sync v0.14.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/sync v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 5021f2c..0ed6cfb 100644 --- a/go.sum +++ b/go.sum @@ -30,14 +30,14 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/node.go b/node.go index 0bd0658..3b19a07 100644 --- a/node.go +++ b/node.go @@ -29,6 +29,11 @@ func (t typescriptNode) applyMutations() (typescriptNode, error) { func (t *typescriptNode) AddEnum(member *bindings.EnumMember) { t.mutations = append(t.mutations, func(v bindings.Node) (bindings.Node, error) { + if v == nil { + // Just delete the enum if the reference type cannot be found. + return nil, nil + } + alias, ok := v.(*bindings.Alias) if ok { // Switch to an enum diff --git a/testdata/enums/enums.go b/testdata/enums/enums.go index 82c7771..39cda22 100644 --- a/testdata/enums/enums.go +++ b/testdata/enums/enums.go @@ -1,5 +1,7 @@ package enums +import "time" + type ( EnumString string EnumSliceType []EnumString @@ -26,3 +28,9 @@ const ( AudienceTenant Audience = "tenant" AudienceTeam Audience = "team" ) + +// EmptyEnum references `time.Duration`, so the constant is considered an enum. +// However, 'time.Duration' is not a referenced type, so the enum does not exist +// in the output. +// For now, this kind of constant is ignored. +const EmptyEnum = 30 * time.Second diff --git a/testdata/enumtypes/enumtypes.go b/testdata/enumtypes/enumtypes.go index d13ec85..460e13b 100644 --- a/testdata/enumtypes/enumtypes.go +++ b/testdata/enumtypes/enumtypes.go @@ -26,3 +26,12 @@ const ( AudienceTenant Audience = "tenant" AudienceTeam Audience = "team" ) + +type EnumAlias = string + +const ( + EnumAliasString EnumAlias = "string" + EnumAliasNumber EnumAlias = "number" + EnumAliasBoolean EnumAlias = "bool" + EnumAliasListString EnumAlias = "list(string)" +) diff --git a/testdata/enumtypes/enumtypes.ts b/testdata/enumtypes/enumtypes.ts index cfe2902..2771035 100644 --- a/testdata/enumtypes/enumtypes.ts +++ b/testdata/enumtypes/enumtypes.ts @@ -5,6 +5,11 @@ export type Audience = "team" | "tenant" | "world"; export const Audiences: Audience[] = ["team", "tenant", "world"]; +// From enumtypes/enumtypes.go +export type EnumAlias = "bool" | "list(string)" | "number" | "string"; + +export const EnumAliases: EnumAlias[] = ["bool", "list(string)", "number", "string"]; + // From enumtypes/enumtypes.go export type EnumInt = 10 | 5;