Skip to content

Commit c0a74fe

Browse files
committed
Export type-predicates along when enumValues is true
1 parent d246a68 commit c0a74fe

File tree

2 files changed

+142
-2
lines changed

2 files changed

+142
-2
lines changed

packages/openapi-typescript/src/transform/schema-object.ts

+130-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,130 @@ function transformEnumSchemaObjectToUnion(schemaObject: EnumSchemaObject) {
8383
return tsUnion(isNullable ? enumType.concat(NULL) : enumType);
8484
}
8585

86+
const createTypePredicateHelper = ts.factory.createFunctionDeclaration(
87+
/* modifiers */ undefined,
88+
/* asteriskToken */ undefined,
89+
/* name */ ts.factory.createIdentifier("get_is"),
90+
/* typeParameters */ [
91+
ts.factory.createTypeParameterDeclaration(
92+
/* modifiers */ undefined,
93+
/* name */ ts.factory.createIdentifier("TItem"),
94+
/* constraint */ tsUnion([NUMBER, STRING]),
95+
/* defaultType */ undefined,
96+
),
97+
],
98+
/* parameters */ [
99+
ts.factory.createParameterDeclaration(
100+
/* modifiers */ undefined,
101+
/* dotDotDotToken */ undefined,
102+
/* name */ ts.factory.createIdentifier("items"),
103+
/* questionToken */ undefined,
104+
/* type */ ts.factory.createTypeOperatorNode(
105+
/* operator */ ts.SyntaxKind.ReadonlyKeyword,
106+
/* type */ ts.factory.createArrayTypeNode(
107+
/* elementType */ ts.factory.createTypeReferenceNode(
108+
/* typeName */ ts.factory.createIdentifier("TItem"),
109+
/* typeArguments */ undefined,
110+
),
111+
),
112+
),
113+
/* initializer */ undefined,
114+
),
115+
],
116+
/* type */ undefined,
117+
/* body */ ts.factory.createBlock(
118+
/* statements */ [
119+
ts.factory.createReturnStatement(
120+
ts.factory.createArrowFunction(
121+
/* modifiers */ undefined,
122+
/* typeParameters */ undefined,
123+
/* parameters */ [
124+
ts.factory.createParameterDeclaration(
125+
/* modifiers */ undefined,
126+
/* dotDotDotToken */ undefined,
127+
/* name */ ts.factory.createIdentifier("value"),
128+
/* questionToken */ undefined,
129+
/* type */ tsUnion([NULL, NUMBER, STRING, UNDEFINED]),
130+
/* initializer */ undefined,
131+
),
132+
],
133+
/* type */ ts.factory.createTypePredicateNode(
134+
/* assertsModifier */ undefined,
135+
/* parameterName */ ts.factory.createIdentifier("value"),
136+
/* type */ ts.factory.createTypeReferenceNode("TItem"),
137+
),
138+
/* equalsGreaterThanToken */ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
139+
/* body */ ts.factory.createBlock(
140+
/* statements */ [
141+
ts.factory.createReturnStatement(
142+
/* expression */ ts.factory.createBinaryExpression(
143+
/* left */ ts.factory.createBinaryExpression(
144+
/* left */ ts.factory.createBinaryExpression(
145+
/* left */ ts.factory.createIdentifier("value"),
146+
/* operator*/ ts.SyntaxKind.ExclamationEqualsEqualsToken,
147+
/* right */ ts.factory.createIdentifier("null"),
148+
),
149+
/* operator */ ts.SyntaxKind.AmpersandAmpersandToken,
150+
/* right */ ts.factory.createBinaryExpression(
151+
/* left */ ts.factory.createIdentifier("value"),
152+
/* operator */ ts.SyntaxKind.ExclamationEqualsEqualsToken,
153+
/* right */ ts.factory.createIdentifier("undefined"),
154+
),
155+
),
156+
/* operator */ ts.SyntaxKind.AmpersandAmpersandToken,
157+
/* right */ ts.factory.createCallExpression(
158+
/* expression */ ts.factory.createPropertyAccessExpression(
159+
/* expression */ ts.factory.createIdentifier("items"),
160+
/* name */ ts.factory.createIdentifier("includes"),
161+
),
162+
/* typeArguments */ undefined,
163+
/* argumentsArray */ [
164+
ts.factory.createAsExpression(
165+
/* expression */ ts.factory.createIdentifier("value"),
166+
/* type */ ts.factory.createTypeReferenceNode(
167+
/* typeName */ ts.factory.createIdentifier("TItem"),
168+
/* typeArguments */ undefined,
169+
),
170+
),
171+
],
172+
),
173+
),
174+
),
175+
],
176+
/* multiLine */ true,
177+
),
178+
),
179+
),
180+
],
181+
/* multiline */ true,
182+
),
183+
);
184+
185+
function getTypePredicate(constName: string, constTypeName: string, options: TransformNodeOptions) {
186+
if (!options.ctx.injectNodes.includes(createTypePredicateHelper)) {
187+
options.ctx.injectNodes.push(createTypePredicateHelper);
188+
}
189+
190+
return ts.factory.createVariableStatement(
191+
/* modifiers */ tsModifiers({ export: true }),
192+
/* declarationList */ ts.factory.createVariableDeclarationList(
193+
/* declarations */ [
194+
ts.factory.createVariableDeclaration(
195+
/* name */ ts.factory.createIdentifier(`is_${constTypeName}`),
196+
/* exclamationToken */ undefined,
197+
/* type */ undefined,
198+
/* initializer */ ts.factory.createCallExpression(
199+
/* expression */ ts.factory.createIdentifier("get_is"),
200+
/* typeArguments */ [ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(constTypeName))],
201+
/* argumentsArray */ [ts.factory.createIdentifier(constName)],
202+
),
203+
),
204+
],
205+
/* flags */ ts.NodeFlags.Const,
206+
),
207+
);
208+
}
209+
86210
function transformEnumSchemaObject(schemaObject: EnumSchemaObject, options: TransformNodeOptions) {
87211
// If the `enum` or `enumValues` options are enabled, create and hoist the declarations
88212
// for the enum and array, so long as all enum members are string or number literals.
@@ -151,8 +275,13 @@ function transformEnumSchemaObject(schemaObject: EnumSchemaObject, options: Tran
151275
)
152276
: undefined;
153277

278+
const constTypePredicate =
279+
options.ctx.enumValues && constName && constTypeName
280+
? getTypePredicate(constName, constTypeName, options)
281+
: undefined;
282+
154283
// Add declarations for all selected output types.
155-
for (const node of [enumType, constExpression, constMemberType]) {
284+
for (const node of [enumType, constExpression, constMemberType, constTypePredicate]) {
156285
if (node && !options.ctx.injectNodes.includes(node)) {
157286
options.ctx.injectNodes.push(node);
158287
}

packages/openapi-typescript/test/node-api.test.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -888,18 +888,29 @@ export type operations = Record<string, never>;`,
888888
},
889889
},
890890
},
891-
want: `export const pathsUrlGetParametersQueryStatusValues = ["active", "inactive"] as const;
891+
want: `function get_is<TItem extends number | string>(items: readonly TItem[]) {
892+
return (value: null | number | string | undefined): value is TItem => {
893+
return value !== null && value !== undefined && items.includes(value as TItem);
894+
};
895+
}
896+
export const pathsUrlGetParametersQueryStatusValues = ["active", "inactive"] as const;
892897
export type pathsUrlGetParametersQueryStatus = (typeof pathsUrlGetParametersQueryStatusValues)[number];
898+
export const is_pathsUrlGetParametersQueryStatus = get_is<pathsUrlGetParametersQueryStatus>(pathsUrlGetParametersQueryStatusValues);
893899
export const pathsUrl2GetParametersQueryStatusAnyOf0Values = ["approved", "rejected"] as const;
894900
export type pathsUrl2GetParametersQueryStatusAnyOf0 = (typeof pathsUrl2GetParametersQueryStatusAnyOf0Values)[number];
901+
export const is_pathsUrl2GetParametersQueryStatusAnyOf0 = get_is<pathsUrl2GetParametersQueryStatusAnyOf0>(pathsUrl2GetParametersQueryStatusAnyOf0Values);
895902
export const pathsUrl2GetParametersQueryStatusAnyOf1Values = ["appealed"] as const;
896903
export type pathsUrl2GetParametersQueryStatusAnyOf1 = (typeof pathsUrl2GetParametersQueryStatusAnyOf1Values)[number];
904+
export const is_pathsUrl2GetParametersQueryStatusAnyOf1 = get_is<pathsUrl2GetParametersQueryStatusAnyOf1>(pathsUrl2GetParametersQueryStatusAnyOf1Values);
897905
export const StatusAnyOf0Values = ["active", "inactive"] as const;
898906
export type StatusAnyOf0 = (typeof StatusAnyOf0Values)[number];
907+
export const is_StatusAnyOf0 = get_is<StatusAnyOf0>(StatusAnyOf0Values);
899908
export const StatusAnyOf1Values = ["pending"] as const;
900909
export type StatusAnyOf1 = (typeof StatusAnyOf1Values)[number];
910+
export const is_StatusAnyOf1 = get_is<StatusAnyOf1>(StatusAnyOf1Values);
901911
export const ErrorCodeValues = [100, 101, 102, 103, 104, 105] as const;
902912
export type ErrorCode = (typeof ErrorCodeValues)[number];
913+
export const is_ErrorCode = get_is<ErrorCode>(ErrorCodeValues);
903914
export interface paths {
904915
"/url": {
905916
parameters: {

0 commit comments

Comments
 (0)