From e133a02ffaa7c01e9fe92bca2c8734abb37745bc Mon Sep 17 00:00:00 2001 From: Jordan Shatford Date: Mon, 8 Apr 2024 10:40:07 +1000 Subject: [PATCH 1/2] chore(compiler): improve typescript file class --- packages/openapi-ts/src/compiler/index.ts | 22 +++++++-- packages/openapi-ts/src/utils/write/index.ts | 18 +++---- packages/openapi-ts/src/utils/write/models.ts | 2 +- .../openapi-ts/src/utils/write/schemas.ts | 2 +- .../openapi-ts/src/utils/write/services.ts | 49 ++++++++----------- 5 files changed, 50 insertions(+), 43 deletions(-) diff --git a/packages/openapi-ts/src/compiler/index.ts b/packages/openapi-ts/src/compiler/index.ts index b085a4b17..f08868748 100644 --- a/packages/openapi-ts/src/compiler/index.ts +++ b/packages/openapi-ts/src/compiler/index.ts @@ -6,9 +6,21 @@ import * as module from './module'; import * as types from './types'; import { tsNodeToString } from './utils'; -export class TypeScriptFile extends Array { - public override toString(seperator: string = '\n') { - return this.map(v => tsNodeToString(v)).join(seperator); +export class TypeScriptFile { + private _imports: Array = []; + private _items: Array = []; + + public addImport(...params: Parameters): void { + this._imports.push(compiler.import.named(...params)); + } + + public add(...nodes: Array): void { + this._items.push(...nodes); + } + + public toString(seperator: string = '\n') { + const importsString = this._imports.map(v => tsNodeToString(v)).join('\n'); + return [importsString, ...this._items.map(v => tsNodeToString(v))].join(seperator); } public write(file: PathOrFileDescriptor, seperator: string = '\n') { @@ -16,7 +28,7 @@ export class TypeScriptFile extends Array { } } -export default { +const compiler = { export: { all: module.createExportAllDeclaration, asConst: module.createExportVariableAsConst, @@ -30,3 +42,5 @@ export default { object: types.createObjectType, }, }; + +export default compiler; diff --git a/packages/openapi-ts/src/utils/write/index.ts b/packages/openapi-ts/src/utils/write/index.ts index 3bcf19ff1..86033ea73 100644 --- a/packages/openapi-ts/src/utils/write/index.ts +++ b/packages/openapi-ts/src/utils/write/index.ts @@ -14,31 +14,31 @@ import type { Config } from '../../types/config'; export const writeClientIndex = async (client: Client, outputPath: string, config: Config): Promise => { const file = new TypeScriptFile(); if (config.name) { - file.push(compiler.export.named([config.name], `./${config.name}`)); + file.add(compiler.export.named([config.name], `./${config.name}`)); } if (config.exportCore) { - file.push(compiler.export.named('ApiError', './core/ApiError')); + file.add(compiler.export.named('ApiError', './core/ApiError')); if (config.serviceResponse === 'response') { - file.push(compiler.export.named({ isTypeOnly: true, name: 'ApiResult' }, './core/ApiResult')); + file.add(compiler.export.named({ isTypeOnly: true, name: 'ApiResult' }, './core/ApiResult')); } if (config.name) { - file.push(compiler.export.named('BaseHttpRequest', './core/BaseHttpRequest')); + file.add(compiler.export.named('BaseHttpRequest', './core/BaseHttpRequest')); } if (config.client !== 'angular') { - file.push(compiler.export.named(['CancelablePromise', 'CancelError'], './core/CancelablePromise')); + file.add(compiler.export.named(['CancelablePromise', 'CancelError'], './core/CancelablePromise')); } - file.push(compiler.export.named(['OpenAPI', { isTypeOnly: true, name: 'OpenAPIConfig' }], './core/OpenAPI')); + file.add(compiler.export.named(['OpenAPI', { isTypeOnly: true, name: 'OpenAPIConfig' }], './core/OpenAPI')); } if (client.models.length) { if (config.exportModels) { - file.push(compiler.export.all('./models')); + file.add(compiler.export.all('./models')); } if (config.exportSchemas) { - file.push(compiler.export.all('./schemas')); + file.add(compiler.export.all('./schemas')); } } if (client.services.length && config.exportServices) { - file.push(compiler.export.all('./services')); + file.add(compiler.export.all('./services')); } file.write(path.resolve(outputPath, 'index.ts')); }; diff --git a/packages/openapi-ts/src/utils/write/models.ts b/packages/openapi-ts/src/utils/write/models.ts index 83011d1ae..20ec76a48 100644 --- a/packages/openapi-ts/src/utils/write/models.ts +++ b/packages/openapi-ts/src/utils/write/models.ts @@ -183,7 +183,7 @@ export const writeClientModels = async ( const file = new TypeScriptFile(); for (const model of client.models) { const nodes = processModel(config, client, model); - file.push(...nodes); + file.add(...nodes); } file.write(path.resolve(outputPath, 'models.ts'), '\n\n'); }; diff --git a/packages/openapi-ts/src/utils/write/schemas.ts b/packages/openapi-ts/src/utils/write/schemas.ts index 82fbbfdc3..74f201f64 100644 --- a/packages/openapi-ts/src/utils/write/schemas.ts +++ b/packages/openapi-ts/src/utils/write/schemas.ts @@ -293,7 +293,7 @@ export const writeClientSchemas = async ( const file = new TypeScriptFile(); for (const model of client.models) { const result = exportSchema(config, model); - file.push(result); + file.add(result); } file.write(path.resolve(outputPath, 'schemas.ts'), '\n\n'); }; diff --git a/packages/openapi-ts/src/utils/write/services.ts b/packages/openapi-ts/src/utils/write/services.ts index 4b6260fc8..78bbe5e7b 100644 --- a/packages/openapi-ts/src/utils/write/services.ts +++ b/packages/openapi-ts/src/utils/write/services.ts @@ -1,7 +1,7 @@ import { writeFileSync } from 'node:fs'; import path from 'node:path'; -import compiler, { TypeScriptFile } from '../../compiler'; +import { TypeScriptFile } from '../../compiler'; import type { Client } from '../../types/client'; import type { Config } from '../../types/config'; import { operationDataType, type Templates } from '../handlebars'; @@ -42,52 +42,45 @@ export const writeClientServices = async ( // Import required packages and core files. if (config.client === 'angular') { - file.push(compiler.import.named('Injectable', '@angular/core')); + file.addImport('Injectable', '@angular/core'); if (config.name === undefined) { - file.push(compiler.import.named('HttpClient', '@angular/common/http')); + file.addImport('HttpClient', '@angular/common/http'); } - file.push(compiler.import.named({ isTypeOnly: true, name: 'Observable' }, 'rxjs')); + file.addImport({ isTypeOnly: true, name: 'Observable' }, 'rxjs'); } else { - file.push(compiler.import.named({ isTypeOnly: true, name: 'CancelablePromise' }, './core/CancelablePromise')); + file.addImport({ isTypeOnly: true, name: 'CancelablePromise' }, './core/CancelablePromise'); } if (config.serviceResponse === 'response') { - file.push(compiler.import.named({ isTypeOnly: true, name: 'ApiResult' }, './core/ApiResult')); + file.addImport({ isTypeOnly: true, name: 'ApiResult' }, './core/ApiResult'); } if (config.name) { - file.push( - compiler.import.named( - { isTypeOnly: config.client !== 'angular', name: 'BaseHttpRequest' }, - './core/BaseHttpRequest' - ) - ); + file.addImport({ isTypeOnly: config.client !== 'angular', name: 'BaseHttpRequest' }, './core/BaseHttpRequest'); } else { if (config.useOptions) { if (config.serviceResponse === 'generics') { - file.push(compiler.import.named(['mergeOpenApiConfig', 'OpenAPI'], './core/OpenAPI')); - file.push(compiler.import.named({ alias: '__request', name: 'request' }, './core/request')); - file.push( - compiler.import.named( - [ - { isTypeOnly: true, name: 'TApiResponse' }, - { isTypeOnly: true, name: 'TConfig' }, - { isTypeOnly: true, name: 'TResult' }, - ], - './core/types' - ) + file.addImport(['mergeOpenApiConfig', 'OpenAPI'], './core/OpenAPI'); + file.addImport({ alias: '__request', name: 'request' }, './core/request'); + file.addImport( + [ + { isTypeOnly: true, name: 'TApiResponse' }, + { isTypeOnly: true, name: 'TConfig' }, + { isTypeOnly: true, name: 'TResult' }, + ], + './core/types' ); } else { - file.push(compiler.import.named('OpenAPI', './core/OpenAPI')); - file.push(compiler.import.named({ alias: '__request', name: 'request' }, './core/request')); + file.addImport('OpenAPI', './core/OpenAPI'); + file.addImport({ alias: '__request', name: 'request' }, './core/request'); } } else { - file.push(compiler.import.named('OpenAPI', './core/OpenAPI')); - file.push(compiler.import.named({ alias: '__request', name: 'request' }, './core/request')); + file.addImport('OpenAPI', './core/OpenAPI'); + file.addImport({ alias: '__request', name: 'request' }, './core/request'); } } // Import all models required by the services. const models = imports.filter(unique).map(imp => ({ isTypeOnly: true, name: imp })); - file.push(compiler.import.named(models, './models')); + file.addImport(models, './models'); const data = [file.toString(), ...operationTypes, ...results].join('\n\n'); From 955856f38b10a5c13b7f05a4ab78443ad770e3ed Mon Sep 17 00:00:00 2001 From: Jordan Shatford Date: Mon, 8 Apr 2024 10:46:38 +1000 Subject: [PATCH 2/2] chore: address PR comments --- packages/openapi-ts/src/compiler/index.ts | 4 +-- .../openapi-ts/src/utils/write/services.ts | 31 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/openapi-ts/src/compiler/index.ts b/packages/openapi-ts/src/compiler/index.ts index f08868748..73c2133ab 100644 --- a/packages/openapi-ts/src/compiler/index.ts +++ b/packages/openapi-ts/src/compiler/index.ts @@ -10,7 +10,7 @@ export class TypeScriptFile { private _imports: Array = []; private _items: Array = []; - public addImport(...params: Parameters): void { + public addNamedImport(...params: Parameters): void { this._imports.push(compiler.import.named(...params)); } @@ -28,7 +28,7 @@ export class TypeScriptFile { } } -const compiler = { +export const compiler = { export: { all: module.createExportAllDeclaration, asConst: module.createExportVariableAsConst, diff --git a/packages/openapi-ts/src/utils/write/services.ts b/packages/openapi-ts/src/utils/write/services.ts index 78bbe5e7b..1b1a032eb 100644 --- a/packages/openapi-ts/src/utils/write/services.ts +++ b/packages/openapi-ts/src/utils/write/services.ts @@ -42,25 +42,28 @@ export const writeClientServices = async ( // Import required packages and core files. if (config.client === 'angular') { - file.addImport('Injectable', '@angular/core'); + file.addNamedImport('Injectable', '@angular/core'); if (config.name === undefined) { - file.addImport('HttpClient', '@angular/common/http'); + file.addNamedImport('HttpClient', '@angular/common/http'); } - file.addImport({ isTypeOnly: true, name: 'Observable' }, 'rxjs'); + file.addNamedImport({ isTypeOnly: true, name: 'Observable' }, 'rxjs'); } else { - file.addImport({ isTypeOnly: true, name: 'CancelablePromise' }, './core/CancelablePromise'); + file.addNamedImport({ isTypeOnly: true, name: 'CancelablePromise' }, './core/CancelablePromise'); } if (config.serviceResponse === 'response') { - file.addImport({ isTypeOnly: true, name: 'ApiResult' }, './core/ApiResult'); + file.addNamedImport({ isTypeOnly: true, name: 'ApiResult' }, './core/ApiResult'); } if (config.name) { - file.addImport({ isTypeOnly: config.client !== 'angular', name: 'BaseHttpRequest' }, './core/BaseHttpRequest'); + file.addNamedImport( + { isTypeOnly: config.client !== 'angular', name: 'BaseHttpRequest' }, + './core/BaseHttpRequest' + ); } else { if (config.useOptions) { if (config.serviceResponse === 'generics') { - file.addImport(['mergeOpenApiConfig', 'OpenAPI'], './core/OpenAPI'); - file.addImport({ alias: '__request', name: 'request' }, './core/request'); - file.addImport( + file.addNamedImport(['mergeOpenApiConfig', 'OpenAPI'], './core/OpenAPI'); + file.addNamedImport({ alias: '__request', name: 'request' }, './core/request'); + file.addNamedImport( [ { isTypeOnly: true, name: 'TApiResponse' }, { isTypeOnly: true, name: 'TConfig' }, @@ -69,18 +72,18 @@ export const writeClientServices = async ( './core/types' ); } else { - file.addImport('OpenAPI', './core/OpenAPI'); - file.addImport({ alias: '__request', name: 'request' }, './core/request'); + file.addNamedImport('OpenAPI', './core/OpenAPI'); + file.addNamedImport({ alias: '__request', name: 'request' }, './core/request'); } } else { - file.addImport('OpenAPI', './core/OpenAPI'); - file.addImport({ alias: '__request', name: 'request' }, './core/request'); + file.addNamedImport('OpenAPI', './core/OpenAPI'); + file.addNamedImport({ alias: '__request', name: 'request' }, './core/request'); } } // Import all models required by the services. const models = imports.filter(unique).map(imp => ({ isTypeOnly: true, name: imp })); - file.addImport(models, './models'); + file.addNamedImport(models, './models'); const data = [file.toString(), ...operationTypes, ...results].join('\n\n');