Skip to content

Commit

Permalink
Feat: output type definitions and allow custom names (#30)
Browse files Browse the repository at this point in the history
Fixes #28
  • Loading branch information
runeh authored Apr 10, 2021
1 parent 5596de4 commit bbad2d3
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 4 deletions.
94 changes: 93 additions & 1 deletion src/__tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ describe('runtype generation', () => {
const personRt = rt.Record({ name: rt.String, age: rt.Number }).asReadonly();
type PersonRt = rt.Static<typeof personRt>;
export const smokeTest = rt.Record({
someBoolean: rt.Boolean,
someNever: rt.Never,
Expand Down Expand Up @@ -135,6 +137,8 @@ describe('runtype generation', () => {
}),
emptyObject: rt.Record({}),
});
export type SmokeTest = rt.Static<typeof smokeTest>;
"
`);
});
Expand Down Expand Up @@ -172,6 +176,8 @@ describe('runtype generation', () => {
"import * as rt from \\"runtypes\\";
const test = rt.Record({ name: rt.String });
type Test = rt.Static<typeof test>;
"
`);
});
Expand All @@ -188,6 +194,8 @@ describe('runtype generation', () => {
"import * as rt from \\"runtypes\\";
const test = rt.Record({ name: rt.String }).asReadonly();
type Test = rt.Static<typeof test>;
"
`);
});
Expand All @@ -204,6 +212,8 @@ describe('runtype generation', () => {
"import * as rt from \\"runtypes\\";
const test = rt.Record({ name: rt.String }).asPartial();
type Test = rt.Static<typeof test>;
"
`);
});
Expand All @@ -227,6 +237,8 @@ describe('runtype generation', () => {
"import * as rt from \\"runtypes\\";
const test = rt.Record({ name: rt.String }).asPartial().asReadonly();
type Test = rt.Static<typeof test>;
"
`);
});
Expand Down Expand Up @@ -269,6 +281,8 @@ describe('runtype generation', () => {
rt.Record({ field_3: rt.String }).asReadonly(),
rt.Record({ field_4: rt.String }).asPartial().asReadonly()
);
type Test = rt.Static<typeof test>;
"
`);
});
Expand All @@ -293,14 +307,18 @@ describe('runtype generation', () => {
const person = rt
.Record({ id: rt.String, name: rt.String, age: rt.String })
.asReadonly();
type Person = rt.Static<typeof person>;
"
`);

const sourceUnformatted = generateRuntypes(root, { format: false });
expect(sourceUnformatted).toMatchInlineSnapshot(`
"import * as rt from \\"runtypes\\";
const person=rt.Record({id:rt.String,name:rt.String,age:rt.String,}).asReadonly();"
const person=rt.Record({id:rt.String,name:rt.String,age:rt.String,}).asReadonly();
type Person=rt.Static<typeof person>;"
`);
});

Expand All @@ -320,6 +338,80 @@ describe('runtype generation', () => {
expect(source).not.toMatch(/;/);
});

describe('output types', () => {
it('can omit the types', () => {
const source = generateRuntypes(
[
{
name: 'thing',
type: {
kind: 'record',
fields: [{ name: 'tag', type: { kind: 'string' } }],
},
},
{
name: 'things',
type: { kind: 'array', type: { kind: 'named', name: 'thing' } },
},
{ name: 'name', type: { kind: 'string' } },
],
{ includeTypes: false },
);

expect(source).toMatchInlineSnapshot(`
"import * as rt from \\"runtypes\\";
const thing = rt.Record({ tag: rt.String });
const things = rt.Array(thing);
const name = rt.String;
"
`);
});

it('can pass in name formatter', () => {
const source = generateRuntypes(
[
{
name: 'thing',
type: {
kind: 'record',
fields: [{ name: 'tag', type: { kind: 'string' } }],
},
},
{
name: 'things',
export: true,
type: { kind: 'array', type: { kind: 'named', name: 'thing' } },
},
{ name: 'name', type: { kind: 'string' } },
],
{
formatRuntypeName: (e) => `${e}Runtype`,
formatTypeName: (e) => `${e}Type`,
},
);

expect(source).toMatchInlineSnapshot(`
"import * as rt from \\"runtypes\\";
const thingRuntype = rt.Record({ tag: rt.String });
type thingType = rt.Static<typeof thingRuntype>;
export const thingsRuntype = rt.Array(thing);
export type thingsType = rt.Static<typeof thingsRuntype>;
const nameRuntype = rt.String;
type nameType = rt.Static<typeof nameRuntype>;
"
`);
});
});

it.todo('Array');
it.todo('Boolean');
it.todo('Brand');
Expand Down
28 changes: 25 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,23 @@ function makeWriter(): CodeWriter {
};
}

type NameFunction = (originalName: string) => string;

export interface GenerateOptions {
format?: boolean;
formatOptions?: PrettierOptions;
includeImport?: boolean;
includeTypes?: boolean;
formatRuntypeName?: NameFunction;
formatTypeName?: NameFunction;
}

const defaultOptions: GenerateOptions = {
format: true,
includeImport: true,
includeTypes: true,
formatRuntypeName: (e) => e[0].toLowerCase() + e.slice(1),
formatTypeName: (e) => e[0].toUpperCase() + e.slice(1),
};

export function generateRuntypes(
Expand All @@ -73,19 +81,33 @@ export function generateRuntypes(
allOptions.includeImport,
'import * as rt from "runtypes";\n\n',
);
roots.forEach((root) => writeRootType(writer, root));
roots.forEach((root) => writeRootType(allOptions, writer, root));

const source = writer.getSource();
return allOptions.format
? format(source, { parser: 'typescript', ...allOptions.formatOptions })
: source.trim();
}

function writeRootType(w: CodeWriter, node: RootType) {
function writeRootType(
options: GenerateOptions,
w: CodeWriter,
node: RootType,
) {
const { formatRuntypeName, formatTypeName, includeTypes } = options;
const runtypeName = formatRuntypeName(node.name);
const typeName = formatTypeName(node.name);
w.conditionalWrite(Boolean(node.export), 'export ');
w.write(`const ${node.name}=`);
w.write(`const ${runtypeName}=`);
writeAnyType(w, node.type);
w.write(';\n\n');

w.conditionalWrite(Boolean(node.export) && includeTypes, 'export ');
w.conditionalWrite(
includeTypes,
`type ${typeName}=rt.Static<typeof ${runtypeName}>;`,
);
w.write('\n\n');
}

// fixme: use mapped type so `node` is typed more narrowly maybe
Expand Down

0 comments on commit bbad2d3

Please sign in to comment.