Skip to content

Commit

Permalink
feat(parser): generate enums into their own file
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlubos committed Apr 12, 2024
1 parent c96c3ec commit 0ec14af
Show file tree
Hide file tree
Showing 29 changed files with 1,274 additions and 594 deletions.
5 changes: 5 additions & 0 deletions .changeset/modern-dots-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hey-api/openapi-ts": minor
---

fix: generate enums into their own file
15 changes: 11 additions & 4 deletions packages/openapi-ts/src/compiler/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type PathOrFileDescriptor, writeFileSync } from 'node:fs';
import { writeFileSync } from 'node:fs';

import ts from 'typescript';

Expand All @@ -15,6 +15,7 @@ export class TypeScriptFile {
private _headers: Array<string> = [];
private _imports: Array<ts.Node> = [];
private _items: Array<ts.Node | string> = [];
private _path: string = '';

public add(...nodes: Array<ts.Node | string>): void {
this._items = [...this._items, ...nodes];
Expand All @@ -31,6 +32,11 @@ export class TypeScriptFile {
this._imports = [...this._imports, compiler.import.named(...params)];
}

public setPath(path: string) {
this._path = path;
return this;
}

public toString(seperator: string = '\n') {
let output: string[] = [];
if (this._headers.length) {
Expand All @@ -43,11 +49,12 @@ export class TypeScriptFile {
return output.join(seperator);
}

public write(file: PathOrFileDescriptor, seperator: string = '\n') {
if (!this._items.length) {
public write(seperator = '\n') {
// TODO: throw if path is not set. do not throw if items are empty
if (!this._items.length || !this._path) {
return;
}
writeFileSync(file, this.toString(seperator));
writeFileSync(this._path, this.toString(seperator));
}
}

Expand Down
97 changes: 52 additions & 45 deletions packages/openapi-ts/src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { addLeadingComment, type Comments, isType, ots } from './utils';
*/
export const toExpression = (value: unknown, unescape = false): ts.Expression | undefined => {
if (Array.isArray(value)) {
return createArrayType(value);
return createArrayType({ arr: value });

Check warning on line 12 in packages/openapi-ts/src/compiler/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L12

Added line #L12 was not covered by tests
}

if (typeof value === 'object' && value !== null) {
return createObjectType(value);
return createObjectType({ obj: value });

Check warning on line 16 in packages/openapi-ts/src/compiler/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L16

Added line #L16 was not covered by tests
}

if (typeof value === 'number') {
Expand All @@ -39,7 +39,13 @@ export const toExpression = (value: unknown, unescape = false): ts.Expression |
* @param multiLine - if the array should be multiline.
* @returns ts.ArrayLiteralExpression
*/
export const createArrayType = <T>(arr: T[], multiLine: boolean = false): ts.ArrayLiteralExpression =>
export const createArrayType = <T>({
arr,
multiLine = false,
}: {
arr: T[];
multiLine?: boolean;
}): ts.ArrayLiteralExpression =>

Check warning on line 48 in packages/openapi-ts/src/compiler/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L43-L48

Added lines #L43 - L48 were not covered by tests
ts.factory.createArrayLiteralExpression(
arr.map(v => toExpression(v)).filter(isType<ts.Expression>),
// Multiline if the array contains objects, or if specified by the user.
Expand All @@ -48,59 +54,60 @@ export const createArrayType = <T>(arr: T[], multiLine: boolean = false): ts.Arr

/**
* Create Object type expression.
* @param obj - the object to create.
* @param options - options to use when creating type.
* @returns ts.ObjectLiteralExpression
*/
export const createObjectType = <T extends object>(
obj: T,
options: {
multiLine?: boolean;
unescape?: boolean;
comments?: Record<string | number, Comments>;
} = {
comments: {},
multiLine: true,
unescape: false,
}
): ts.ObjectLiteralExpression => {
const expression = ts.factory.createObjectLiteralExpression(
Object.entries(obj)
.map(([key, value]) => {
const initializer = toExpression(value, options.unescape);
if (!initializer) {
return undefined;
}
const c = options.comments?.[key];
if (key.match(/\W/g) && !key.startsWith("'") && !key.endsWith("'")) {
key = `'${key}'`;
}
const assignment = ts.factory.createPropertyAssignment(key, initializer);
if (c?.length) {
addLeadingComment(assignment, c);
}
return assignment;
})
.filter(isType<ts.PropertyAssignment>),
options.multiLine
);
export const createObjectType = <T extends object>({
comments = {},
multiLine = true,
obj,
unescape = false,
}: {
obj: T;
multiLine?: boolean;
unescape?: boolean;
comments?: Record<string | number, Comments>;
}): ts.ObjectLiteralExpression => {
const properties = Object.entries(obj)
.map(([key, value]) => {
const initializer = toExpression(value, unescape);
if (!initializer) {
return undefined;
}

Check warning on line 76 in packages/openapi-ts/src/compiler/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L75-L76

Added lines #L75 - L76 were not covered by tests
if (key.match(/\W/g) && !key.startsWith("'") && !key.endsWith("'")) {
key = `'${key}'`;
}

Check warning on line 79 in packages/openapi-ts/src/compiler/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L78-L79

Added lines #L78 - L79 were not covered by tests
const assignment = ts.factory.createPropertyAssignment(key, initializer);
const c = comments?.[key];
if (c?.length) {
addLeadingComment(assignment, c);
}

Check warning on line 84 in packages/openapi-ts/src/compiler/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L83-L84

Added lines #L83 - L84 were not covered by tests
return assignment;
})
.filter(isType<ts.PropertyAssignment>);
const expression = ts.factory.createObjectLiteralExpression(properties, multiLine);
return expression;
};

/**
* Create enum declaration. Example `export enum T = { X, Y };`
* @param name - the name of the enum.
* @param obj - the object representing the enum.
* @param comment - comment to add to enum.
* @param leadingComment - leading comment to add to enum.
* @param comments - comments to add to each property of enum.
* @returns
*/
export const createEnumDeclaration = <T extends object>(
name: string,
obj: T,
comment: Comments = [],
comments: Record<string | number, Comments> = {}
): ts.EnumDeclaration => {
export const createEnumDeclaration = <T extends object>({
name,
obj,
leadingComment = [],
comments = {},
}: {
name: string;
obj: T;
leadingComment: Comments;
comments: Record<string | number, Comments>;
}): ts.EnumDeclaration => {

Check warning on line 110 in packages/openapi-ts/src/compiler/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L101-L110

Added lines #L101 - L110 were not covered by tests
const declaration = ts.factory.createEnumDeclaration(
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
ts.factory.createIdentifier(name),
Expand All @@ -114,8 +121,8 @@ export const createEnumDeclaration = <T extends object>(
return assignment;
})
);
if (comment.length) {
addLeadingComment(declaration, comment);
if (leadingComment.length) {
addLeadingComment(declaration, leadingComment);

Check warning on line 125 in packages/openapi-ts/src/compiler/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L124-L125

Added lines #L124 - L125 were not covered by tests
}
return declaration;
};
13 changes: 4 additions & 9 deletions packages/openapi-ts/src/utils/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,12 @@ export const enumKey = (value?: string | number, customName?: string) => {
*/
export const enumName = (client: Client, name?: string) => {
if (!name) {
return name;
return null;

Check warning on line 46 in packages/openapi-ts/src/utils/enum.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/utils/enum.ts#L46

Added line #L46 was not covered by tests
}
const escapedName = unescapeName(name).replace(/[-_]([a-z])/gi, ($0, $1: string) => $1.toLocaleUpperCase());
let result = `${escapedName.charAt(0).toLocaleUpperCase() + escapedName.slice(1)}Enum`;
let index = 1;
while (client.enumNames.includes(result)) {
if (result.endsWith(index.toString())) {
result = result.slice(0, result.length - index.toString().length);
}
index += 1;
result = result + index.toString();
const result = `${escapedName.charAt(0).toLocaleUpperCase() + escapedName.slice(1)}Enum`;
if (client.enumNames.includes(result)) {
return null;

Check warning on line 51 in packages/openapi-ts/src/utils/enum.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/utils/enum.ts#L49-L51

Added lines #L49 - L51 were not covered by tests
}
client.enumNames = [...client.enumNames, result];
return result;
Expand Down
18 changes: 9 additions & 9 deletions packages/openapi-ts/src/utils/write/__tests__/core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ import path from 'node:path';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { setConfig } from '../../config';
import { writeClientCore } from '../core';
import { writeCore } from '../core';
import { mockTemplates } from './mocks';
import { openApi } from './models';

vi.mock('node:fs');

describe('writeClientCore', () => {
let templates: Parameters<typeof writeClientCore>[3];
describe('writeCore', () => {
let templates: Parameters<typeof writeCore>[3];
beforeEach(() => {
templates = mockTemplates;
});

it('writes to filesystem', async () => {
const client: Parameters<typeof writeClientCore>[2] = {
const client: Parameters<typeof writeCore>[2] = {
enumNames: [],
models: [],
server: 'http://localhost:8080',
Expand Down Expand Up @@ -46,7 +46,7 @@ describe('writeClientCore', () => {
useOptions: true,
});

await writeClientCore(openApi, '/', client, templates);
await writeCore(openApi, '/', client, templates);

expect(writeFileSync).toHaveBeenCalledWith(path.resolve('/', '/OpenAPI.ts'), 'settings');
expect(writeFileSync).toHaveBeenCalledWith(path.resolve('/', '/ApiError.ts'), 'apiError');
Expand All @@ -57,7 +57,7 @@ describe('writeClientCore', () => {
});

it('uses client server value for base', async () => {
const client: Parameters<typeof writeClientCore>[2] = {
const client: Parameters<typeof writeCore>[2] = {
enumNames: [],
models: [],
server: 'http://localhost:8080',
Expand Down Expand Up @@ -86,7 +86,7 @@ describe('writeClientCore', () => {
useOptions: true,
});

await writeClientCore(openApi, '/', client, templates);
await writeCore(openApi, '/', client, templates);

expect(templates.core.settings).toHaveBeenCalledWith({
$config: config,
Expand All @@ -97,7 +97,7 @@ describe('writeClientCore', () => {
});

it('uses custom value for base', async () => {
const client: Parameters<typeof writeClientCore>[2] = {
const client: Parameters<typeof writeCore>[2] = {
enumNames: [],
models: [],
server: 'http://localhost:8080',
Expand Down Expand Up @@ -127,7 +127,7 @@ describe('writeClientCore', () => {
useOptions: true,
});

await writeClientCore(openApi, '/', client, templates);
await writeCore(openApi, '/', client, templates);

expect(templates.core.settings).toHaveBeenCalledWith({
$config: config,
Expand Down
8 changes: 4 additions & 4 deletions packages/openapi-ts/src/utils/write/__tests__/models.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import path from 'node:path';
import { describe, expect, it, vi } from 'vitest';

import { setConfig } from '../../config';
import { writeClientModels } from '../models';
import { writeTypesAndEnums } from '../models';
import { openApi } from './models';

vi.mock('node:fs');

describe('writeClientModels', () => {
describe('writeTypesAndEnums', () => {
it('writes to filesystem', async () => {
setConfig({
client: 'fetch',
Expand All @@ -32,7 +32,7 @@ describe('writeClientModels', () => {
useOptions: true,
});

const client: Parameters<typeof writeClientModels>[2] = {
const client: Parameters<typeof writeTypesAndEnums>[2] = {
enumNames: [],
models: [
{
Expand All @@ -59,7 +59,7 @@ describe('writeClientModels', () => {
version: 'v1',
};

await writeClientModels(openApi, '/', client);
await writeTypesAndEnums(openApi, '/', client);

expect(writeFileSync).toHaveBeenCalledWith(path.resolve('/', '/models.ts'), expect.anything());
});
Expand Down
28 changes: 25 additions & 3 deletions packages/openapi-ts/src/utils/write/__tests__/schemas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,35 @@ import path from 'node:path';

import { describe, expect, it, vi } from 'vitest';

import { writeClientSchemas } from '../schemas';
import { setConfig } from '../../config';
import { writeSchemas } from '../schemas';
import { openApi } from './models';

vi.mock('node:fs');

describe('writeClientSchemas', () => {
describe('writeSchemas', () => {
it('writes to filesystem', async () => {
setConfig({
client: 'fetch',
debug: false,
dryRun: false,
enums: 'javascript',
exportCore: true,
exportModels: true,
exportServices: true,
format: false,
input: '',
lint: false,
name: 'AppClient',
operationId: true,
output: '',
postfixServices: '',
schemas: true,
serviceResponse: 'body',
useDateType: false,
useOptions: true,
});

if ('openapi' in openApi) {
openApi.components = {
schemas: {
Expand All @@ -20,7 +42,7 @@ describe('writeClientSchemas', () => {
};
}

await writeClientSchemas(openApi, '/');
await writeSchemas(openApi, '/');

expect(writeFileSync).toHaveBeenCalledWith(path.resolve('/', '/schemas.ts'), expect.anything());
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { writeFileSync } from 'node:fs';
import { describe, expect, it, vi } from 'vitest';

import { setConfig } from '../../config';
import { writeClientServices } from '../services';
import { writeServices } from '../services';
import { mockTemplates } from './mocks';
import { openApi } from './models';

vi.mock('node:fs');

describe('writeClientServices', () => {
describe('writeServices', () => {
it('writes to filesystem', async () => {
setConfig({
client: 'fetch',
Expand All @@ -31,7 +31,7 @@ describe('writeClientServices', () => {
useOptions: false,
});

const client: Parameters<typeof writeClientServices>[2] = {
const client: Parameters<typeof writeServices>[2] = {
enumNames: [],
models: [],
server: 'http://localhost:8080',
Expand All @@ -46,7 +46,7 @@ describe('writeClientServices', () => {
version: 'v1',
};

await writeClientServices(openApi, '/', client, mockTemplates);
await writeServices(openApi, '/', client, mockTemplates);

expect(writeFileSync).toHaveBeenCalled();
});
Expand Down
Loading

0 comments on commit 0ec14af

Please sign in to comment.