diff --git a/config/mutations.go b/config/mutations.go index 541790f..266f25a 100644 --- a/config/mutations.go +++ b/config/mutations.go @@ -269,3 +269,39 @@ func (v *nullUnionVisitor) Visit(node bindings.Node) walk.Visitor { return v } + +// NotNullMaps assumes all maps will not be null. +// Example: +// GolangType: map[string]string +// TsType: Record | null --> Record +func NotNullMaps(ts *guts.Typescript) { + ts.ForEach(func(key string, node bindings.Node) { + walk.Walk(¬NullMaps{}, node) + }) +} + +type notNullMaps struct{} + +func (v *notNullMaps) Visit(node bindings.Node) walk.Visitor { + if union, ok := node.(*bindings.UnionType); ok && len(union.Types) == 2 { + hasNull := slices.ContainsFunc(union.Types, func(t bindings.ExpressionType) bool { + _, isNull := t.(*bindings.Null) + return isNull + }) + + var record bindings.ExpressionType + index := slices.IndexFunc(union.Types, func(t bindings.ExpressionType) bool { + ref, isRef := t.(*bindings.ReferenceType) + if !isRef { + return false + } + return ref.Name.Name == "Record" + }) + if hasNull && index != -1 { + record = union.Types[index] + union.Types = []bindings.ExpressionType{record} + } + } + + return v +} diff --git a/convert.go b/convert.go index 3190964..7adac60 100644 --- a/convert.go +++ b/convert.go @@ -801,7 +801,8 @@ func (ts *Typescript) typescriptType(ty types.Type) (parsedType, error) { return parsedType{}, xerrors.Errorf("simplify generics in map: %w", err) } parsed := parsedType{ - Value: RecordReference(keyType.Value, valueType.Value), + // Golang `map` can be marshaled to `null` in json. + Value: bindings.Union(RecordReference(keyType.Value, valueType.Value), &bindings.Null{}), TypeParameters: tp, RaisedComments: append(keyType.RaisedComments, valueType.RaisedComments...), } diff --git a/convert_test.go b/convert_test.go index c964197..fd2ab7b 100644 --- a/convert_test.go +++ b/convert_test.go @@ -117,3 +117,27 @@ func TestGeneration(t *testing.T) { }) } } + +func TestNotNullMaps(t *testing.T) { + gen, err := guts.NewGolangParser() + require.NoError(t, err, "new convert") + + dir := filepath.Join(".", "testdata", "maps") + err = gen.IncludeGenerate("./" + dir) + require.NoErrorf(t, err, "include %q", dir) + + gen.IncludeCustomDeclaration(config.StandardMappings()) + + ts, err := gen.ToTypescript() + require.NoError(t, err, "to typescript") + + ts.ApplyMutations( + config.NotNullMaps, + ) + + output, err := ts.Serialize() + require.NoErrorf(t, err, "generate %q", dir) + + // Not perfect, this asserts if the record is a nullable type. + require.Contains(t, output, "SimpleMap: Record;", "no nullable Record") +} diff --git a/testdata/anyreference/anyreference.ts b/testdata/anyreference/anyreference.ts index 92c9a12..1a4f9e4 100644 --- a/testdata/anyreference/anyreference.ts +++ b/testdata/anyreference/anyreference.ts @@ -2,7 +2,7 @@ // From anyreference/anyreference.go export interface Example { - readonly Value: Record; + readonly Value: Record | null; } // From anyreference/anyreference.go diff --git a/testdata/genericmap/genericmap.go b/testdata/genericmap/genericmap.go index dc80d1f..7079716 100644 --- a/testdata/genericmap/genericmap.go +++ b/testdata/genericmap/genericmap.go @@ -17,12 +17,10 @@ type Custom interface { Foo | Buzz } -// Not yet supported -//type FooBuzzMap[R Custom] struct { -// Something map[string]R `json:"something"` -//} +type FooBuzzMap[R Custom] struct { + Something map[string]R `json:"something"` +} -// Not yet supported -//type FooBuzzAnonymousUnion[R Foo | Buzz] struct { -// Something []R `json:"something"` -//} +type FooBuzzAnonymousUnion[R Foo | Buzz] struct { + Something []R `json:"something"` +} diff --git a/testdata/genericmap/genericmap.ts b/testdata/genericmap/genericmap.ts index fcd89ed..f3ac6a1 100644 --- a/testdata/genericmap/genericmap.ts +++ b/testdata/genericmap/genericmap.ts @@ -18,3 +18,13 @@ export interface Foo { export interface FooBuzz { readonly something: readonly R[]; } + +// From codersdk/genericmap.go +export interface FooBuzzAnonymousUnion { + readonly something: readonly R[]; +} + +// From codersdk/genericmap.go +export interface FooBuzzMap { + readonly something: Record | null; +} diff --git a/testdata/maps/maps.ts b/testdata/maps/maps.ts index b7207ee..8aed9a3 100644 --- a/testdata/maps/maps.ts +++ b/testdata/maps/maps.ts @@ -2,7 +2,7 @@ // From maps/map.go export interface Bar { - readonly SimpleMap: Record; - readonly NumberMap: Record; - readonly GenericMap: Record; + readonly SimpleMap: Record | null; + readonly NumberMap: Record | null; + readonly GenericMap: Record | null; }