Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: broken encoding #532

Merged
merged 1 commit into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dry-socks-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hey-api/openapi-ts": patch
---

fix: broken encoding
63 changes: 7 additions & 56 deletions packages/openapi-ts/src/compiler/classes.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,15 @@
import ts from 'typescript';

import { createTypeNode } from './typedef';
import { toExpression } from './types';
import {
type AccessLevel,
type FunctionParameter,
toAccessLevelModifiers,
toExpression,
toParameterDeclarations,
} from './types';
import { addLeadingJSDocComment, Comments, isType } from './utils';

type AccessLevel = 'public' | 'protected' | 'private';

export type FunctionParameter = {
accessLevel?: AccessLevel;
default?: any;
isReadOnly?: boolean;
isRequired?: boolean;
name: string;
type: any | ts.TypeNode;
};

/**
* Convert AccessLevel to proper TypeScript compiler API modifier.
* @param access - the access level.
* @returns ts.ModifierLike[]
*/
const toAccessLevelModifiers = (access?: AccessLevel): ts.ModifierLike[] => {
const keyword =
access === 'public'
? ts.SyntaxKind.PublicKeyword
: access === 'protected'
? ts.SyntaxKind.ProtectedKeyword
: access === 'private'
? ts.SyntaxKind.PrivateKeyword
: undefined;
const modifiers: ts.ModifierLike[] = [];
if (keyword) {
modifiers.push(ts.factory.createModifier(keyword));
}
return modifiers;
};

/**
* Convert parameters to the declaration array expected by compiler API.
* @param parameters - the parameters to conver to declarations
* @returns ts.ParameterDeclaration[]
*/
export const toParameterDeclarations = (parameters: FunctionParameter[]) =>
parameters.map((p) => {
const modifiers = toAccessLevelModifiers(p.accessLevel);
if (p.isReadOnly) {
modifiers.push(ts.factory.createModifier(ts.SyntaxKind.ReadonlyKeyword));
}
return ts.factory.createParameterDeclaration(
modifiers,
undefined,
ts.factory.createIdentifier(p.name),
p.isRequired !== undefined && !p.isRequired
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
: undefined,
p.type !== undefined ? createTypeNode(p.type) : undefined,
p.default !== undefined ? toExpression({ value: p.default }) : undefined,
);
});

/**
* Create a class constructor declaration.
* @param accessLevel - the access level of the constructor.
Expand Down
14 changes: 8 additions & 6 deletions packages/openapi-ts/src/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import * as types from './types';
import { stringToTsNodes, tsNodeToString } from './utils';

export type { FunctionParameter } from './classes';
export type { Property } from './typedef';
export type { FunctionParameter } from './types';
export type { Comments } from './utils';
export type { ClassElement, Node, TypeNode } from 'typescript';

Expand Down Expand Up @@ -52,11 +52,11 @@
}
}

public add(...nodes: Array<ts.Node | string>): void {
public add(...nodes: Array<ts.Node | string>) {
this._items = [...this._items, ...nodes];
}

public addImport(...params: Parameters<typeof compiler.import.named>): void {
public addImport(...params: Parameters<typeof compiler.import.named>) {
this._imports = [...this._imports, compiler.import.named(...params)];
}

Expand Down Expand Up @@ -94,13 +94,15 @@
if (this._imports.length) {
output = [
...output,
this._imports.map((v) => tsNodeToString(v)).join('\n'),
this._imports.map((node) => tsNodeToString({ node })).join('\n'),
];
}
output = [
...output,
...this._items.map((v) =>
typeof v === 'string' ? v : tsNodeToString(v),
...this._items.map((node) =>
typeof node === 'string'
? node

Check warning on line 104 in packages/openapi-ts/src/compiler/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/index.ts#L104

Added line #L104 was not covered by tests
: tsNodeToString({ node, unescape: true }),
),
];
return output.join(seperator);
Expand Down
57 changes: 31 additions & 26 deletions packages/openapi-ts/src/compiler/module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import ts from 'typescript';

import { addLeadingJSDocComment, type Comments, ots } from './utils';
import {
addLeadingJSDocComment,
type Comments,
type ImportItemObject,
ots,
} from './utils';

/**
* Create export all declaration. Example: `export * from './y'`.
Expand All @@ -15,9 +20,7 @@ export const createExportAllDeclaration = (module: string) =>
ots.string(module),
);

type ImportItem =
| { name: string; isTypeOnly?: boolean; alias?: string }
| string;
type ImportItem = ImportItemObject | string;

/**
* Create a named export declaration. Example: `export { X } from './y'`.
Expand Down Expand Up @@ -103,28 +106,30 @@ export const createNamedImportDeclarations = (
items: Array<ImportItem> | ImportItem,
module: string,
): ts.ImportDeclaration => {
items = Array.isArray(items) ? items : [items];
const isAllTypes = items.every((i) => typeof i === 'object' && i.isTypeOnly);
return ts.factory.createImportDeclaration(
const importedTypes = Array.isArray(items) ? items : [items];
const isTypeOnly = !importedTypes.some(
(item) => typeof item !== 'object' || !item.isTypeOnly,
);
const elements = importedTypes.map((item) => {
const importedType: ImportItemObject =
typeof item === 'string' ? { name: item } : item;
return ots.import({
alias: importedType.alias,
isTypeOnly: isTypeOnly ? false : Boolean(importedType.isTypeOnly),
name: importedType.name,
});
});
const namedBindings = ts.factory.createNamedImports(elements);
const importClause = ts.factory.createImportClause(
isTypeOnly,
undefined,
ts.factory.createImportClause(
isAllTypes,
undefined,
ts.factory.createNamedImports(
items.map((item) => {
const {
name,
isTypeOnly = undefined,
alias = undefined,
} = typeof item === 'string' ? { name: item } : item;
return ots.import(
name,
isAllTypes ? false : Boolean(isTypeOnly),
alias,
);
}),
),
),
ots.string(module),
namedBindings,
);
const moduleSpecifier = ots.string(module);
const statement = ts.factory.createImportDeclaration(
undefined,
importClause,
moduleSpecifier,
);
return statement;
};
2 changes: 1 addition & 1 deletion packages/openapi-ts/src/compiler/typedef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@
const node = createTypeInterfaceNode([
{
isRequired: true,
name: `[key: ${tsNodeToString(keyNode)}]`,
name: `[key: ${tsNodeToString({ node: keyNode, unescape: true })}]`,

Check warning on line 173 in packages/openapi-ts/src/compiler/typedef.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/typedef.ts#L173

Added line #L173 was not covered by tests
type: valueNode,
},
]);
Expand Down
58 changes: 57 additions & 1 deletion packages/openapi-ts/src/compiler/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import ts from 'typescript';

import { type FunctionParameter, toParameterDeclarations } from './classes';
import { createTypeNode } from './typedef';
import { addLeadingJSDocComment, type Comments, isType, ots } from './utils';

export type AccessLevel = 'public' | 'protected' | 'private';

export type FunctionParameter = {
accessLevel?: AccessLevel;
default?: any;
isReadOnly?: boolean;
isRequired?: boolean;
name: string;
type: any | ts.TypeNode;
};

/**
* Convert an unknown value to an expression.
* @param identifiers - list of keys that are treated as identifiers.
Expand Down Expand Up @@ -48,6 +58,52 @@
}
};

/**
* Convert AccessLevel to proper TypeScript compiler API modifier.
* @param access - the access level.
* @returns ts.ModifierLike[]
*/
export const toAccessLevelModifiers = (
access?: AccessLevel,
): ts.ModifierLike[] => {
const keyword =
access === 'public'
? ts.SyntaxKind.PublicKeyword
: access === 'protected'
? ts.SyntaxKind.ProtectedKeyword
: access === 'private'
? ts.SyntaxKind.PrivateKeyword
: undefined;
const modifiers: ts.ModifierLike[] = [];
if (keyword) {
modifiers.push(ts.factory.createModifier(keyword));
}
return modifiers;
};

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

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L67-L82

Added lines #L67 - L82 were not covered by tests

/**
* Convert parameters to the declaration array expected by compiler API.
* @param parameters - the parameters to conver to declarations
* @returns ts.ParameterDeclaration[]
*/
export const toParameterDeclarations = (parameters: FunctionParameter[]) =>
parameters.map((p) => {
const modifiers = toAccessLevelModifiers(p.accessLevel);
if (p.isReadOnly) {
modifiers.push(ts.factory.createModifier(ts.SyntaxKind.ReadonlyKeyword));
}
return ts.factory.createParameterDeclaration(
modifiers,
undefined,
ts.factory.createIdentifier(p.name),
p.isRequired !== undefined && !p.isRequired
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
: undefined,
p.type !== undefined ? createTypeNode(p.type) : undefined,
p.default !== undefined ? toExpression({ value: p.default }) : undefined,
);
});

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

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/types.ts#L90-L105

Added lines #L90 - L105 were not covered by tests

/**
* Create Function type expression.
*/
Expand Down
Loading