Skip to content

Commit bb98656

Browse files
feat(openapi-generator): support nested member expression
2 parents 86c8d8d + 6fa8230 commit bb98656

File tree

2 files changed

+111
-4
lines changed

2 files changed

+111
-4
lines changed

packages/openapi-generator/src/codec.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,37 @@ function codecIdentifier(
5454
} else if (id.type === 'MemberExpression') {
5555
const object = id.object;
5656
if (object.type !== 'Identifier') {
57-
if (object.type === 'MemberExpression')
57+
if (object.type === 'MemberExpression') {
58+
// Handle double-nested member expressions
59+
if (
60+
object.object.type === 'Identifier' &&
61+
object.property.type === 'Identifier' &&
62+
object.property.value === 'keys'
63+
) {
64+
// Handle `Type.keys.propertyName` format
65+
if (id.property.type === 'Identifier') {
66+
return E.right({
67+
type: 'string',
68+
enum: [id.property.value],
69+
});
70+
}
71+
// Handle `Type.keys["Property Name"]` format (computed property)
72+
else if (
73+
id.property.type === 'Computed' &&
74+
id.property.expression.type === 'StringLiteral'
75+
) {
76+
return E.right({
77+
type: 'string',
78+
enum: [id.property.expression.value],
79+
});
80+
}
81+
}
82+
5883
return errorLeft(
59-
`Object ${
60-
((object as swc.MemberExpression) && { value: String }).value
61-
} is deeply nested, which is unsupported`,
84+
`Nested member expressions are not supported (${object.object.type}.${object.property.type}.${id.property.type})`,
6285
);
86+
}
87+
6388
return errorLeft(`Unimplemented object type ${object.type}`);
6489
}
6590

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as E from 'fp-ts/lib/Either';
2+
import assert from 'node:assert/strict';
3+
import test from 'node:test';
4+
5+
import { TestProject } from './testProject';
6+
import { parsePlainInitializer, type Schema } from '../src';
7+
8+
async function testCase(
9+
description: string,
10+
src: string,
11+
expected: Record<string, Schema>,
12+
expectedErrors: string[] = [],
13+
) {
14+
test(description, async () => {
15+
const project = new TestProject({ '/index.ts': src });
16+
await project.parseEntryPoint('/index.ts');
17+
const sourceFile = project.get('/index.ts');
18+
if (sourceFile === undefined) {
19+
throw new Error('Source file not found');
20+
}
21+
22+
const actual: Record<string, Schema> = {};
23+
const errors: string[] = [];
24+
for (const symbol of sourceFile.symbols.declarations) {
25+
if (symbol.init !== undefined) {
26+
const result = parsePlainInitializer(project, sourceFile, symbol.init);
27+
if (E.isLeft(result)) {
28+
// Only keep the error message, not the stack trace
29+
const errorMessage = result.left.split('\n')[0] ?? '';
30+
errors.push(errorMessage);
31+
} else {
32+
if (symbol.comment !== undefined) {
33+
result.right.comment = symbol.comment;
34+
}
35+
actual[symbol.name] = result.right;
36+
}
37+
}
38+
}
39+
40+
assert.deepEqual(errors, expectedErrors);
41+
assert.deepEqual(actual, expected);
42+
});
43+
}
44+
45+
const MINIMAL_NESTED_MEMBER_EXPRESSION = `
46+
import * as t from 'io-ts';
47+
48+
export const colorType = {
49+
red: 'red',
50+
} as const;
51+
52+
export const ColorType = t.keyof(colorType);
53+
54+
export const redItem = t.type({
55+
type: t.literal(ColorType.keys.red),
56+
});
57+
`;
58+
59+
testCase(
60+
'nested member expression is parsed correctly',
61+
MINIMAL_NESTED_MEMBER_EXPRESSION,
62+
{
63+
colorType: {
64+
type: 'object',
65+
properties: {
66+
red: { type: 'string', enum: ['red'] },
67+
},
68+
required: ['red'],
69+
},
70+
ColorType: {
71+
type: 'union',
72+
schemas: [{ type: 'string', enum: ['red'] }],
73+
},
74+
redItem: {
75+
type: 'object',
76+
properties: {
77+
type: { type: 'string', enum: ['red'] },
78+
},
79+
required: ['type'],
80+
},
81+
},
82+
);

0 commit comments

Comments
 (0)