Skip to content

Commit

Permalink
Merge pull request #277 from hey-api/chore/cleanup-compiler-api-objec…
Browse files Browse the repository at this point in the history
…t-generation
  • Loading branch information
jordanshatford authored Apr 6, 2024
2 parents 935bc6c + 8bfc914 commit c4b345b
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 152 deletions.
2 changes: 2 additions & 0 deletions packages/openapi-ts/rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export function handlebarsPlugin(): Plugin {
escapeDescription: true,
escapeNewline: true,
exactArray: true,
getDefaultPrintable: true,
hasDefault: true,
ifNotNullNotUndefined: true,
ifOperationDataOptional: true,
ifdef: true,
Expand Down
10 changes: 8 additions & 2 deletions packages/openapi-ts/src/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,27 @@ import { type PathOrFileDescriptor, writeFileSync } from 'node:fs';
import ts from 'typescript';

import * as module from './module';
import * as types from './types';
import { tsNodeToString } from './utils';

export class TypeScriptFile extends Array<ts.Node> {
public write(file: PathOrFileDescriptor) {
public write(file: PathOrFileDescriptor, seperator: string = '\n') {
const items = this.map(i => tsNodeToString(i));
writeFileSync(file, items.join('\n'));
writeFileSync(file, items.join(seperator));
}
}

export default {
export: {
all: module.createExportAllDeclaration,
asConst: module.createExportVariableAsConst,
named: module.createNamedExportDeclarations,
},
import: {
named: module.createNamedImportDeclarations,
},
types: {
array: types.createArrayType,
object: types.createObjectType,
},
};
24 changes: 23 additions & 1 deletion packages/openapi-ts/src/compiler/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ts from 'typescript';
import { ots } from './utils';

/**
* Create export all declaration. Example: `export * from './y'`
* Create export all declaration. Example: `export * from './y'`.
* @param module - module to export from.
* @returns ts.ExportDeclaration
*/
Expand Down Expand Up @@ -37,6 +37,28 @@ export const createNamedExportDeclarations = (
);
};

/**
* Create an export variable as const statement. Example: `export x = {} as const`.
* @param name - name of the variable.
* @param expr - expression for the variable.
* @returns ts.VariableStatement
*/
export const createExportVariableAsConst = (name: string, expr: ts.Expression): ts.VariableStatement =>
ts.factory.createVariableStatement(
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
ts.factory.createVariableDeclarationList(
[
ts.factory.createVariableDeclaration(
ts.factory.createIdentifier(name),
undefined,
undefined,
ts.factory.createAsExpression(expr, ts.factory.createTypeReferenceNode('const'))
),
],
ts.NodeFlags.Const
)
);

/**
* Create a named import declaration. Example: `import { X } from './y'`.
* @param items - the items to export.
Expand Down
54 changes: 54 additions & 0 deletions packages/openapi-ts/src/compiler/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import ts from 'typescript';

import { isType, ots } from './utils';

/**
* Convert an unknown value to an expression.
* @param value - the unknown value.
* @returns ts.Expression
*/
const toExpression = (value: unknown): ts.Expression | undefined => {
if (Array.isArray(value)) {
return createArrayType(value);
} else if (typeof value === 'object' && value !== null) {
return createObjectType(value);
} else if (typeof value === 'number') {
return ots.number(value);
} else if (typeof value === 'boolean') {
return ots.boolean(value);
} else if (typeof value === 'string') {
return ots.string(value);
} else if (value === null) {
return ts.factory.createNull();
}
};

/**
* Create Array type expression.
* @param arr - The array to create.
* @param multiLine - if the array should be multiline.
* @returns ts.ArrayLiteralExpression
*/
export const createArrayType = <T>(arr: T[], multiLine: boolean = false): ts.ArrayLiteralExpression =>
ts.factory.createArrayLiteralExpression(
arr.map(v => toExpression(v)).filter(isType<ts.Expression>),
// Multiline if the array contains objects, or if specified by the user.
(!Array.isArray(arr[0]) && typeof arr[0] === 'object') || multiLine
);

/**
* Create Object type expression.
* @param obj - the object to create.
* @param multiLine - if the object should be multiline.
* @returns ts.ObjectLiteralExpression
*/
export const createObjectType = <T extends object>(obj: T, multiLine: boolean = true): ts.ObjectLiteralExpression =>
ts.factory.createObjectLiteralExpression(
Object.entries(obj)
.map(([key, value]) => {
const initializer = toExpression(value);
return initializer ? ts.factory.createPropertyAssignment(key, initializer) : undefined;
})
.filter(isType<ts.PropertyAssignment>),
multiLine
);
27 changes: 26 additions & 1 deletion packages/openapi-ts/src/compiler/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export function tsNodeToString(node: ts.Node): string {

// ots for openapi-ts is helpers to reduce repetition of basic ts factory functions.
export const ots = {
// Create a boolean expression based on value.
boolean: (value: boolean) => (value ? ts.factory.createTrue() : ts.factory.createFalse()),
export: (name: string, isTypeOnly?: boolean) =>
ts.factory.createExportSpecifier(
isTypeOnly ?? false,
Expand All @@ -34,5 +36,28 @@ export const ots = {
undefined,
ts.factory.createIdentifier(encodeURIComponent(name))
),
string: (text: string) => ts.factory.createStringLiteral(encodeURIComponent(text), CONFIG.useSingleQuotes),
// Create a numeric expression, handling negative numbers.
number: (value: number) => {
if (value < 0) {
return ts.factory.createPrefixUnaryExpression(
ts.SyntaxKind.MinusToken,
ts.factory.createNumericLiteral(Math.abs(value))
);
} else {
return ts.factory.createNumericLiteral(value);
}
},
// Create a string literal. This handles strings that start with '`' or "'".
string: (value: string) => {
if (value.startsWith('`')) {
return ts.factory.createIdentifier(encodeURIComponent(value));
} else {
return ts.factory.createStringLiteral(
encodeURIComponent(value),
value.includes("'") ? false : CONFIG.useSingleQuotes
);
}
},
};

export const isType = <T>(value: T | undefined): value is T => value !== undefined;
22 changes: 7 additions & 15 deletions packages/openapi-ts/src/openApi/common/parser/getDefault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ import type { OperationParameter } from '../interfaces/client';
export const getDefault = (
definition: OpenApiSchema | OpenApiParameter,
model?: Model | OperationParameter
): string | undefined => {
if (definition.default === undefined) {
return undefined;
}

if (definition.default === null) {
return 'null';
): unknown | undefined => {
if (definition.default === undefined || definition.default === null) {
return definition.default;
}

const type = definition.type || typeof definition.default;
Expand All @@ -23,19 +19,15 @@ export const getDefault = (
case 'number':
if (model?.export === 'enum' && model.enum?.[definition.default as number]) {
const { value } = model.enum[definition.default as number];
return typeof value === 'string' ? `'${value}'` : String(value);
return value;
}
return String(definition.default);
return definition.default;
case 'string':
return `'${definition.default}'`;
return definition.default;
case 'array':
case 'boolean':
case 'object':
try {
return JSON.stringify(definition.default, null, 4);
} catch (e) {
// Ignore
}
return definition.default;
}
return undefined;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ data: {{#if parameters}}{{{nameOperationDataType @root this}}} & {{/if}}TConfig<
{{#if parameters}}

{{#each parameters}}
{{{name}}}{{{modelIsRequired this}}}: {{>type}}{{#if default}} = {{{default}}}{{/if}},
{{{name}}}{{{modelIsRequired this}}}: {{>type}}{{#hasDefault this}} = {{{getDefaultPrintable this}}}{{/hasDefault}},
{{/each}}
{{/if}}
{{/if}}
3 changes: 0 additions & 3 deletions packages/openapi-ts/src/utils/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ export const enumUnionType = (enums: Enum[]) =>
.join(' | ');

export const enumValue = (value?: string | number) => {
if (typeof value === 'number') {
return String(value);
}
if (typeof value === 'string') {
return `'${value.replace(/'/g, "\\'")}'`;
}
Expand Down
27 changes: 23 additions & 4 deletions packages/openapi-ts/src/utils/handlebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,24 @@ const dataDestructure = (config: Config, operation: Operation) => {
return '';
};

export const getDefaultPrintable = (p: OperationParameter | Model): string | undefined => {
if (p.default === undefined) {
return undefined;
}
return JSON.stringify(p.default, null, 4);
};

const dataParameters = (config: Config, parameters: OperationParameter[]) => {
if (config.experimental) {
let output = parameters
.filter(parameter => parameter.default !== undefined)
.filter(parameter => getDefaultPrintable(parameter) !== undefined)
.map(parameter => {
const key = parameter.prop;
const value = parameter.name;
if (key === value || escapeName(key) === key) {
return `${key}: ${parameter.default}`;
return `${key}: ${getDefaultPrintable(parameter)}`;
}
return `'${key}': ${parameter.default}`;
return `'${key}': ${getDefaultPrintable(parameter)}`;
});
if (parameters.every(parameter => parameter.in === 'query')) {
output = [...output, '...query'];
Expand All @@ -164,7 +171,7 @@ export const modelIsRequired = (config: Config, model: Model) => {
if (config.useOptions) {
return model.isRequired ? '' : '?';
}
return !model.isRequired && !model.default ? '?' : '';
return !model.isRequired && !getDefaultPrintable(model) ? '?' : '';
};

const nameOperationDataType = (service: Service, operation: Service['operations'][number]) => {
Expand Down Expand Up @@ -267,6 +274,18 @@ export const registerHandlebarHelpers = (config: Config, client: Client): void =
return options.inverse(this);
});

Handlebars.registerHelper(
'hasDefault',
function (this: unknown, p: OperationParameter, options: Handlebars.HelperOptions) {
if (getDefaultPrintable(p) !== undefined) {
return options.fn(this);
}
return options.inverse(this);
}
);

Handlebars.registerHelper('getDefaultPrintable', getDefaultPrintable);

Handlebars.registerHelper('ifdef', function (this: unknown, ...args): string {
const options = args.pop();
if (!args.every(value => !value)) {
Expand Down
Loading

0 comments on commit c4b345b

Please sign in to comment.