From 6ca3a71cc70c73962bef835104cb33d169cd9566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=23=E4=BA=91=E6=B7=A1=E7=84=B6?= Date: Sat, 15 Apr 2023 02:13:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(writer):=20=E5=A2=9E=E5=8A=A0=20components?= =?UTF-8?q?=20writer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/writers/BaseWriter.ts | 28 +++ src/writers/CommentsWriter.ts | 5 +- src/writers/ComponentsWriter.ts | 51 +++++ test/writers/CommentsWriter.test.ts | 6 +- test/writers/ComponentsWriter.test.ts | 274 ++++++++++++++++++++++++++ 5 files changed, 359 insertions(+), 5 deletions(-) create mode 100644 src/writers/BaseWriter.ts create mode 100644 src/writers/ComponentsWriter.ts create mode 100644 test/writers/ComponentsWriter.test.ts diff --git a/src/writers/BaseWriter.ts b/src/writers/BaseWriter.ts new file mode 100644 index 0000000..c5d8c6d --- /dev/null +++ b/src/writers/BaseWriter.ts @@ -0,0 +1,28 @@ +import { TypeAlias, TypeItem } from '../readers/types'; +import { WriterOptions } from './types'; +import prettier from 'prettier'; + +export class BaseWriter { + static defaults: WriterOptions = { + document: { components: [], paths: [] }, + prettier: { + singleQuote: true, + }, + }; + + options: WriterOptions; + constructor(options: WriterOptions) { + this.options = Object.assign({}, BaseWriter.defaults, options); + } + + protected isTypeAlias(type: TypeItem): type is TypeAlias { + return type.kind === 'alias'; + } + + protected format(text: string) { + return prettier.format(text, { + ...this.options.prettier, + parser: 'typescript', + }); + } +} diff --git a/src/writers/CommentsWriter.ts b/src/writers/CommentsWriter.ts index f655d29..f0a474b 100644 --- a/src/writers/CommentsWriter.ts +++ b/src/writers/CommentsWriter.ts @@ -3,7 +3,7 @@ import { isUndefined } from '../utils/type-is'; import { BaseWriter } from './BaseWriter'; export class CommentsWriter extends BaseWriter { - write(type: TypeItem) { + writeComments(type: TypeItem, trailingEndOfLine = false) { const orders: (keyof TypeComments)[] = ['title', 'description', 'format', 'default', 'example']; const mainLines = [ type.deprecated ? ' * @deprecated' : '', @@ -22,7 +22,8 @@ export class CommentsWriter extends BaseWriter { return ( '/**\n' + ////////////////////////////////////// mainLines.join('\n') + - '\n */' + '\n */' + + (trailingEndOfLine ? '\n' : '') ); } } diff --git a/src/writers/ComponentsWriter.ts b/src/writers/ComponentsWriter.ts new file mode 100644 index 0000000..6883363 --- /dev/null +++ b/src/writers/ComponentsWriter.ts @@ -0,0 +1,51 @@ +import { TypeItem, TypeOrigin } from '../readers/types'; +import { toTypePath } from '../utils/string'; +import { BaseWriter } from './BaseWriter'; +import { CommentsWriter } from './CommentsWriter'; + +export class ComponentsWriter extends CommentsWriter { + writeComponents() { + const textList: string[] = []; + this.options.document.components.forEach((type) => { + const comments = this.writeComments(type, true); + textList.push(`${comments}export type ${type.name} = ${this.writeType(type)};`); + }); + return this.format(textList.join('\n\n')); + } + + private writeType(type: TypeItem): string { + if (this.isTypeAlias(type)) return `${type.origin}${toTypePath(type.props)}`; + + switch (type.type) { + case 'number': + case 'string': + case 'boolean': + case 'never': + return this.writePrimitive(type); + + case 'object': + return this.writeObject(type); + + case 'array': + return this.writeArray(type); + } + } + + private writePrimitive(type: TypeOrigin) { + return `${type.type}`; + } + + private writeObject(type: TypeOrigin) { + const kvList = type.children!.map((t) => { + const c = this.writeComments(t, true); + const v = this.writeType(t); + return `${c}${t.name}: ${v};`; + }); + return '{' + kvList.join('\n') + '}'; + } + + private writeArray(type: TypeOrigin) { + const one = this.writeType(type.children!.at(0)!); + return `Array<${one}>`; + } +} diff --git a/test/writers/CommentsWriter.test.ts b/test/writers/CommentsWriter.test.ts index d257c5e..3763290 100644 --- a/test/writers/CommentsWriter.test.ts +++ b/test/writers/CommentsWriter.test.ts @@ -9,7 +9,7 @@ test('CommentsWriter', () => { }); expect( - writer.write({ + writer.writeComments({ kind: 'origin', name: 'A', type: 'string', @@ -18,7 +18,7 @@ test('CommentsWriter', () => { ).toMatchInlineSnapshot('""'); expect( - writer.write({ + writer.writeComments({ kind: 'origin', name: 'A', type: 'string', @@ -32,7 +32,7 @@ test('CommentsWriter', () => { `); expect( - writer.write({ + writer.writeComments({ kind: 'origin', name: 'A', type: 'string', diff --git a/test/writers/ComponentsWriter.test.ts b/test/writers/ComponentsWriter.test.ts new file mode 100644 index 0000000..cf0fd07 --- /dev/null +++ b/test/writers/ComponentsWriter.test.ts @@ -0,0 +1,274 @@ +import { ComponentsWriter } from '../../src/writers/ComponentsWriter'; + +test('empty components', () => { + const writer = new ComponentsWriter({ + document: { + components: [], + paths: [], + }, + }); + const text = writer.writeComponents(); + expect(text).toEqual(''); +}); + +test('alias', () => { + const writer = new ComponentsWriter({ + document: { + components: [ + { + kind: 'alias', + name: 'O', + target: 'P', + origin: 'Q', + props: [], + ref: '', + root: true, + description: 'd1', + }, + { + kind: 'alias', + name: 'O', + target: 'P', + origin: 'Q', + props: ['q1', 'q2'], + ref: '', + root: true, + description: 'd2', + }, + ], + paths: [], + }, + }); + const text = writer.writeComponents(); + expect(text).toMatchInlineSnapshot(` + "/** + * @description d1 + */ + export type O = Q; + + /** + * @description d2 + */ + export type O = Q['q1']['q2']; + " + `); +}); + +test('origin primitive', () => { + const writer = new ComponentsWriter({ + document: { + components: [ + { + kind: 'origin', + type: 'number', + name: 'N1', + required: true, + description: 'ddd1', + }, + { + kind: 'origin', + type: 'string', + name: 'S1', + required: true, + description: 'ddd2', + }, + { + kind: 'origin', + type: 'boolean', + name: 'B1', + required: true, + }, + { + kind: 'origin', + type: 'never', + name: 'N2', + required: true, + }, + ], + paths: [], + }, + }); + const text = writer.writeComponents(); + expect(text).toMatchInlineSnapshot(` + "/** + * @description ddd1 + */ + export type N1 = number; + + /** + * @description ddd2 + */ + export type S1 = string; + + export type B1 = boolean; + + export type N2 = never; + " + `); +}); + +test('origin object', () => { + const writer = new ComponentsWriter({ + document: { + components: [ + { + kind: 'origin', + type: 'object', + name: 'O1', + required: true, + description: 'ddd1', + children: [ + { + kind: 'origin', + type: 'string', + name: 'sss', + required: true, + description: 'ddd2', + }, + { + kind: 'alias', + name: 'ooo', + target: 'P', + origin: 'Q', + props: ['q1', 'q2'], + ref: '', + root: false, + description: 'ddd3', + }, + { + kind: 'origin', + type: 'object', + name: 'ppp', + required: true, + children: [ + { + kind: 'origin', + type: 'number', + name: 'nnn', + required: true, + }, + { + kind: 'alias', + name: 'qqq', + target: 'X', + origin: 'X', + props: [], + ref: '', + root: false, + }, + ], + }, + ], + }, + ], + paths: [], + }, + }); + const text = writer.writeComponents(); + expect(text).toMatchInlineSnapshot(` + "/** + * @description ddd1 + */ + export type O1 = { + /** + * @description ddd2 + */ + sss: string; + /** + * @description ddd3 + */ + ooo: Q['q1']['q2']; + ppp: { nnn: number; qqq: X }; + }; + " + `); +}); + +test('origin array', () => { + const writer = new ComponentsWriter({ + document: { + components: [ + { + kind: 'origin', + type: 'array', + name: 'A', + required: true, + description: 'ddd1', + children: [ + { + kind: 'origin', + type: 'object', + name: 'O1', + required: true, + description: 'ddd2', + children: [ + { + kind: 'origin', + type: 'string', + name: 'sss', + required: true, + }, + { + kind: 'alias', + name: 'ooo', + target: 'P', + origin: 'Q', + props: ['q1', 'q2'], + ref: '', + root: false, + }, + { + kind: 'origin', + type: 'object', + name: 'ppp', + required: true, + description: 'ddd3', + children: [ + { + kind: 'origin', + type: 'number', + name: 'nnn', + required: true, + description: 'ddd4', + }, + { + kind: 'alias', + name: 'qqq', + target: 'X', + origin: 'X', + props: [], + ref: '', + root: false, + }, + ], + }, + ], + }, + ], + }, + ], + paths: [], + }, + }); + const text = writer.writeComponents(); + expect(text).toMatchInlineSnapshot(` + "/** + * @description ddd1 + */ + export type A = Array<{ + sss: string; + ooo: Q['q1']['q2']; + /** + * @description ddd3 + */ + ppp: { + /** + * @description ddd4 + */ + nnn: number; + qqq: X; + }; + }>; + " + `); +});