From 6543c9981298dd82dd1026dac513928207b1519e Mon Sep 17 00:00:00 2001 From: Rune Finstad Halvorsen Date: Thu, 25 Mar 2021 13:30:42 +0100 Subject: [PATCH] feat: Try alternate writer for for alt_serializer --- src/__tests__/main_alt.test.ts | 35 +++----------- src/main_alt.ts | 88 ++++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 65 deletions(-) diff --git a/src/__tests__/main_alt.test.ts b/src/__tests__/main_alt.test.ts index a143b94..939329f 100644 --- a/src/__tests__/main_alt.test.ts +++ b/src/__tests__/main_alt.test.ts @@ -1,5 +1,4 @@ import { format, resolveConfig } from 'prettier'; -import { Project, SourceFile } from 'ts-morph'; import { generateRuntypes, groupFieldKinds } from '../main_alt'; async function fmt(source: string) { @@ -8,18 +7,8 @@ async function fmt(source: string) { } describe('runtype generation', () => { - let project: Project; - let file: SourceFile; - - beforeEach(() => { - project = new Project(); - file = project.createSourceFile('./test.ts'); - }); - it('smoke test', async () => { - generateRuntypes( - file, - + const raw = generateRuntypes( { name: 'personRt', type: { @@ -108,7 +97,6 @@ describe('runtype generation', () => { }, }, ); - const raw = file.getText(); const formatted = await fmt(raw); expect(formatted).toMatchInlineSnapshot(` "const personRt = rt @@ -117,6 +105,7 @@ describe('runtype generation', () => { age: rt.Number, }) .asReadonly(); + export const smokeTest = rt.Record({ someBoolean: rt.Boolean, someNever: rt.Never, @@ -177,15 +166,13 @@ describe('runtype generation', () => { }); it('default modifiers', async () => { - generateRuntypes(file, { + const raw = generateRuntypes({ 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({ @@ -196,15 +183,13 @@ describe('runtype generation', () => { }); it('readonly modifiers', async () => { - generateRuntypes(file, { + const raw = generateRuntypes({ 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 @@ -217,15 +202,13 @@ describe('runtype generation', () => { }); it('nullable modifiers', async () => { - generateRuntypes(file, { + const raw = generateRuntypes({ 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 @@ -238,7 +221,7 @@ describe('runtype generation', () => { }); it('both modifiers', async () => { - generateRuntypes(file, { + const raw = generateRuntypes({ name: 'test', type: { kind: 'record', @@ -252,8 +235,6 @@ describe('runtype generation', () => { ], }, }); - - const raw = file.getText(); const formatted = await fmt(raw); expect(formatted).toMatchInlineSnapshot(` "const test = rt @@ -267,7 +248,7 @@ describe('runtype generation', () => { }); it('all groups', async () => { - generateRuntypes(file, { + const raw = generateRuntypes({ name: 'test', type: { kind: 'record', @@ -295,8 +276,6 @@ describe('runtype generation', () => { ], }, }); - - const raw = file.getText(); const formatted = await fmt(raw); expect(formatted).toMatchInlineSnapshot(` "const test = rt.intersect( diff --git a/src/main_alt.ts b/src/main_alt.ts index 289ccdc..3908231 100644 --- a/src/main_alt.ts +++ b/src/main_alt.ts @@ -1,10 +1,3 @@ -import { - CodeBlockWriter, - OptionalKind, - SourceFile, - VariableDeclarationKind, - VariableStatementStructure, -} from 'ts-morph'; import { AnyType, ArrayType, @@ -17,24 +10,45 @@ import { UnionType, } from './types_alt'; -export function generateRuntypes(file: SourceFile, ...roots: RootType[]): void { - file.addVariableStatements( - roots.map>((root) => { - return { - isExported: root.export, - declarationKind: VariableDeclarationKind.Const, - declarations: [ - { name: root.name, initializer: (w) => writeAnyType(w, root.type) }, - ], - }; - }), - ); +interface CodeWriter { + getSource(): string; + write(data: string): void; + conditionalWrite(cond: boolean, data: string): void; +} + +function makeWriter(): CodeWriter { + const chunks: string[] = []; + return { + getSource() { + return chunks.join(''); + }, + write(data) { + chunks.push(data); + }, + conditionalWrite(cond, data) { + if (cond) { + chunks.push(data); + } + }, + }; +} + +export function generateRuntypes(...roots: RootType[]): string { + const writer = makeWriter(); + for (const root of roots) { + writer.conditionalWrite(Boolean(root.export), 'export '); + writer.write(`const ${root.name} = `); + writeAnyType(writer, root.type); + writer.write(';\n\n'); + } + + return writer.getSource(); } // fixme: use mapped type so `node` is typed more narrowly maybe const writers: Record< AnyType['kind'], - (writer: CodeBlockWriter, node: AnyType) => void + (writer: CodeWriter, node: AnyType) => void > = { boolean: simpleWriter('rt.Boolean'), function: simpleWriter('rt.Function'), @@ -52,26 +66,26 @@ const writers: Record< dictionary: writeDictionaryType, }; -function simpleWriter(value: string): (writer: CodeBlockWriter) => void { +function simpleWriter(value: string): (writer: CodeWriter) => void { return (writer) => writer.write(value); } -function writeDictionaryType(w: CodeBlockWriter, node: DictionaryType) { +function writeDictionaryType(w: CodeWriter, node: DictionaryType) { w.write('rt.Dictionary('); writeAnyType(w, node.valueType); w.write(')'); } -function writeNamedType(w: CodeBlockWriter, node: NamedType) { +function writeNamedType(w: CodeWriter, node: NamedType) { w.write(node.name); } -function writeAnyType(w: CodeBlockWriter, node: AnyType) { +function writeAnyType(w: CodeWriter, node: AnyType) { const writer = writers[node.kind]; writer(w, node); } -function writeLiteralType(w: CodeBlockWriter, node: LiteralType) { +function writeLiteralType(w: CodeWriter, node: LiteralType) { const { value } = node; w.write('rt.Literal('); if (value === undefined) { @@ -87,29 +101,29 @@ function writeLiteralType(w: CodeBlockWriter, node: LiteralType) { w.write(')'); } -function writeArrayType(w: CodeBlockWriter, node: ArrayType) { +function writeArrayType(w: CodeWriter, node: ArrayType) { w.write('rt.Array('); writeAnyType(w, node.type); w.write(')'); w.conditionalWrite(node.readonly, '.asReadonly()'); } -function writeUnionType(w: CodeBlockWriter, node: UnionType) { - w.writeLine('rt.Union('); +function writeUnionType(w: CodeWriter, node: UnionType) { + w.write('\nrt.Union('); for (const type of node.types) { writeAnyType(w, type); w.write(',\n'); } - w.writeLine(') '); + w.write('\n) '); } -function writeIntersectionType(w: CodeBlockWriter, node: UnionType) { - w.writeLine('rt.Intersect('); +function writeIntersectionType(w: CodeWriter, node: UnionType) { + w.write('\nrt.Intersect('); for (const type of node.types) { writeAnyType(w, type); w.write(',\n'); } - w.writeLine(') '); + w.write('\n) '); } /** @@ -150,12 +164,12 @@ export function groupFieldKinds( ].filter((e) => e.fields.length > 0); } -function writeRecordType(w: CodeBlockWriter, node: RecordType) { +function writeRecordType(w: CodeWriter, node: RecordType) { const fieldKinds = groupFieldKinds(node.fields); const hasMultiple = fieldKinds.length > 1; - w.conditionalWriteLine(hasMultiple, 'rt.intersect('); + w.conditionalWrite(hasMultiple, '\nrt.intersect('); for (const fieldKind of fieldKinds) { - w.writeLine('rt.Record({'); + w.write('rt.Record({\n'); for (const field of fieldKind.fields) { w.write(field.name); w.write(': '); @@ -165,7 +179,7 @@ function writeRecordType(w: CodeBlockWriter, node: RecordType) { w.write('})'); w.conditionalWrite(fieldKind.nullable ?? false, '.asPartial()'); w.conditionalWrite(fieldKind.readonly ?? false, '.asReadonly()'); - w.conditionalWriteLine(hasMultiple, ','); + w.conditionalWrite(hasMultiple, '\n,'); } - w.conditionalWriteLine(hasMultiple, ')'); + w.conditionalWrite(hasMultiple, '\n)'); }