Skip to content

Commit

Permalink
feat: Add groupFieldKinds, symbol type, remove void and partial/readonly
Browse files Browse the repository at this point in the history
Add groupFieldKinds
Add symbol type
Remove deprecated void type
Add support for partial/readonly/both members on records
  • Loading branch information
runeh authored and simenandre committed Mar 25, 2021
1 parent baaaa02 commit 742ce62
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 30 deletions.
193 changes: 181 additions & 12 deletions src/__tests__/main_alt.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { format, resolveConfig } from 'prettier';
import { Project, SourceFile } from 'ts-morph';
import { generateRuntypes } from '../main_alt';
import { generateRuntypes, groupFieldKinds } from '../main_alt';

async function fmt(source: string) {
const config = await resolveConfig(__filename);
Expand All @@ -22,7 +22,6 @@ describe('runtype generation', () => {

{
name: 'personRt',
export: false,
type: {
kind: 'record',
fields: [
Expand All @@ -42,8 +41,8 @@ describe('runtype generation', () => {
{ name: 'someNever', type: { kind: 'never' } },
{ name: 'someNumber', type: { kind: 'number' } },
{ name: 'someString', type: { kind: 'string' } },
{ name: 'someSymbol', type: { kind: 'symbol' } },
{ name: 'someUnknown', type: { kind: 'unknown' } },
{ name: 'someVoid', type: { kind: 'void' } },
{
name: 'someLiteral1',
type: { kind: 'literal', value: 'string' },
Expand Down Expand Up @@ -88,11 +87,10 @@ describe('runtype generation', () => {
type: {
kind: 'record',
fields: [
{ name: 'name', readonly: true, type: { kind: 'string' } },
{ name: 'age', readonly: true, type: { kind: 'number' } },
{ name: 'name', type: { kind: 'string' } },
{ name: 'age', type: { kind: 'number' } },
{
name: 'medals',
readonly: true,
type: {
kind: 'union',
types: [
Expand All @@ -113,17 +111,19 @@ describe('runtype generation', () => {
const raw = file.getText();
const formatted = await fmt(raw);
expect(formatted).toMatchInlineSnapshot(`
"const personRt = rt.Record({
name: rt.String,
age: rt.Number,
});
"const personRt = rt
.Record({
name: rt.String,
age: rt.Number,
})
.asReadonly();
export const smokeTest = rt.Record({
someBoolean: rt.Boolean,
someNever: rt.Never,
someNumber: rt.Number,
someString: rt.String,
someSymbol: rt.Symbol,
someUnknown: rt.Unknown,
someVoid: rt.Void,
someLiteral1: rt.Literal('string'),
someLiteral2: rt.Literal(1337),
someLiteral3: rt.Literal(true),
Expand Down Expand Up @@ -155,6 +155,176 @@ describe('runtype generation', () => {
`);
});

describe('objects', () => {
it('groupFieldKinds', () => {
const res = groupFieldKinds([
{ name: 'field_1', type: { kind: 'string' } },
{ name: 'field_2', type: { kind: 'string' }, nullable: true },
{ name: 'field_3', type: { kind: 'string' }, readonly: true },
{
name: 'field_4',
type: { kind: 'string' },
nullable: true,
readonly: true,
},
]);

const [default_, nullable, readonly, both] = res;
expect(default_.fields.map((e) => e.name)).toEqual(['field_1']);
expect(nullable.fields.map((e) => e.name)).toEqual(['field_2']);
expect(readonly.fields.map((e) => e.name)).toEqual(['field_3']);
expect(both.fields.map((e) => e.name)).toEqual(['field_4']);
});

it('default modifiers', async () => {
generateRuntypes(file, {
name: 'test',
type: {
kind: 'record',
fields: [{ name: 'name', type: { kind: 'string' } }],
},
});

const raw = file.getText();
const formatted = await fmt(raw);
expect(formatted).toMatchInlineSnapshot(`
"const test = rt.Record({
name: rt.String,
});
"
`);
});

it('readonly modifiers', async () => {
generateRuntypes(file, {
name: 'test',
type: {
kind: 'record',
fields: [{ name: 'name', readonly: true, type: { kind: 'string' } }],
},
});

const raw = file.getText();
const formatted = await fmt(raw);
expect(formatted).toMatchInlineSnapshot(`
"const test = rt
.Record({
name: rt.String,
})
.asReadonly();
"
`);
});

it('nullable modifiers', async () => {
generateRuntypes(file, {
name: 'test',
type: {
kind: 'record',
fields: [{ name: 'name', nullable: true, type: { kind: 'string' } }],
},
});

const raw = file.getText();
const formatted = await fmt(raw);
expect(formatted).toMatchInlineSnapshot(`
"const test = rt
.Record({
name: rt.String,
})
.asPartial();
"
`);
});

it('both modifiers', async () => {
generateRuntypes(file, {
name: 'test',
type: {
kind: 'record',
fields: [
{
name: 'name',
nullable: true,
readonly: true,
type: { kind: 'string' },
},
],
},
});

const raw = file.getText();
const formatted = await fmt(raw);
expect(formatted).toMatchInlineSnapshot(`
"const test = rt
.Record({
name: rt.String,
})
.asPartial()
.asReadonly();
"
`);
});

it('all groups', async () => {
generateRuntypes(file, {
name: 'test',
type: {
kind: 'record',
fields: [
{
name: 'field_1',
type: { kind: 'string' },
},
{
name: 'field_2',
nullable: true,
type: { kind: 'string' },
},
{
name: 'field_3',
readonly: true,
type: { kind: 'string' },
},
{
name: 'field_4',
nullable: true,
readonly: true,
type: { kind: 'string' },
},
],
},
});

const raw = file.getText();
const formatted = await fmt(raw);
expect(formatted).toMatchInlineSnapshot(`
"const test = rt.intersect(
rt.Record({
field_1: rt.String,
}),
rt
.Record({
field_2: rt.String,
})
.asPartial(),
rt
.Record({
field_3: rt.String,
})
.asReadonly(),
rt
.Record({
field_4: rt.String,
})
.asPartial()
.asReadonly(),
);
"
`);
});
});

it.todo('Array');
it.todo('Boolean');
it.todo('Brand');
Expand All @@ -170,5 +340,4 @@ describe('runtype generation', () => {
it.todo('Tuple');
it.todo('Union');
it.todo('Unknown');
it.todo('Void');
});
72 changes: 60 additions & 12 deletions src/main_alt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DictionaryType,
LiteralType,
NamedType,
RecordField,
RecordType,
RootType,
UnionType,
Expand Down Expand Up @@ -40,8 +41,8 @@ const writers: Record<
never: simpleWriter('rt.Never'),
number: simpleWriter('rt.Number'),
string: simpleWriter('rt.String'),
symbol: simpleWriter('rt.Symbol'),
unknown: simpleWriter('rt.Unknown'),
void: simpleWriter('rt.Void'),
array: writeArrayType,
record: writeRecordType,
union: writeUnionType,
Expand Down Expand Up @@ -97,27 +98,74 @@ function writeUnionType(w: CodeBlockWriter, node: UnionType) {
w.writeLine('rt.Union(');
for (const type of node.types) {
writeAnyType(w, type);
w.write(', ');
w.write(',\n');
}
w.write(') ');
w.writeLine(') ');
}

function writeIntersectionType(w: CodeBlockWriter, node: UnionType) {
w.writeLine('rt.Intersect(');
for (const type of node.types) {
writeAnyType(w, type);
w.write(', ');
w.write(',\n');
}
w.write(') ');
w.writeLine(') ');
}

/**
* public for testing
*
* Used to evaluate if `Record` type include `readonly` and/or `nullable`
* @private
* @param fields
*/
export function groupFieldKinds(
fields: readonly RecordField[],
): {
readonly: boolean;
nullable: boolean;
fields: RecordField[];
}[] {
return [
{
readonly: false,
nullable: false,
fields: fields.filter((e) => !e.readonly && !e.nullable),
},
{
readonly: false,
nullable: true,
fields: fields.filter((e) => !e.readonly && e.nullable),
},
{
readonly: true,
nullable: false,
fields: fields.filter((e) => e.readonly && !e.nullable),
},
{
readonly: true,
nullable: true,
fields: fields.filter((e) => e.readonly && e.nullable),
},
].filter((e) => e.fields.length > 0);
}

function writeRecordType(w: CodeBlockWriter, node: RecordType) {
w.writeLine('rt.Record({');
for (const field of node.fields) {
w.write(field.name);
w.write(': ');
writeAnyType(w, field.type);
w.write(',');
const fieldKinds = groupFieldKinds(node.fields);
const hasMultiple = fieldKinds.length > 1;
w.conditionalWriteLine(hasMultiple, 'rt.intersect(');
for (const fieldKind of fieldKinds) {
w.writeLine('rt.Record({');
for (const field of fieldKind.fields) {
w.write(field.name);
w.write(': ');
writeAnyType(w, field.type);
w.write(',\n');
}
w.write('})');
w.conditionalWrite(fieldKind.nullable ?? false, '.asPartial()');
w.conditionalWrite(fieldKind.readonly ?? false, '.asReadonly()');
w.conditionalWriteLine(hasMultiple, ',');
}
w.write('})');
w.conditionalWriteLine(hasMultiple, ')');
}
11 changes: 5 additions & 6 deletions src/types_alt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const simpleTypeRt = rt.Record({
rt.Literal('never'),
rt.Literal('number'),
rt.Literal('string'),
rt.Literal('symbol'),
rt.Literal('unknown'),
rt.Literal('void'),
),
});

Expand Down Expand Up @@ -157,10 +157,9 @@ const anyTypeRt = rt.Union(

export type AnyType = rt.Static<typeof anyTypeRt>;

export const rootType = rt.Record({
name: rt.String,
export: rt.Boolean,
type: anyTypeRt,
});
export const rootType = rt.Intersect(
rt.Record({ name: rt.String, type: anyTypeRt }),
rt.Record({ export: rt.Boolean }).asPartial(),
);

export type RootType = rt.Static<typeof rootType>;

0 comments on commit 742ce62

Please sign in to comment.