Skip to content

Commit 1edd64f

Browse files
committed
fix(core): parse mutator with latest ecmaVersion to support dynamic import
1 parent b035ece commit 1edd64f

3 files changed

Lines changed: 41 additions & 29 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
// Mutator that performs a dynamic import in its body.
3+
// Regression fixture for https://github.com/orval-labs/orval/issues/1634:
4+
// esbuild preserves dynamic `import()` in ESM output even when targeting es6,
5+
// so the bundled code parsed by acorn contains an `import()` expression.
6+
export const customInstance = async <T>(_config: {
7+
url: string;
8+
}): Promise<T> => {
9+
const mod = await import('node:os');
10+
return mod as unknown as T;
11+
};

packages/core/src/generators/mutator-info.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,22 @@ describe('getMutatorInfo', () => {
259259
expect(result).toEqual({ numberOfParams: 0 });
260260
});
261261
});
262+
263+
describe('dynamic import', () => {
264+
// Regression test for https://github.com/orval-labs/orval/issues/1634.
265+
// esbuild preserves dynamic `import()` in its ESM output, so the bundled
266+
// code handed to acorn contains an `import()` expression. Acorn must be
267+
// able to parse it; otherwise the named export is reported as missing.
268+
it('should find named export when body contains await import()', async () => {
269+
const result = await getMutatorInfo(
270+
path.join(
271+
basePath,
272+
'dynamic-import-tests',
273+
'dynamic-import-named-export.ts',
274+
),
275+
{ namedExport: 'customInstance' },
276+
);
277+
expect(result).toEqual({ numberOfParams: 1 });
278+
});
279+
});
262280
});

packages/core/src/generators/mutator-info.ts

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import { type ecmaVersion, Parser, type Program } from 'acorn';
1+
import { Parser, type Program } from 'acorn';
22
import { build, type BuildOptions } from 'esbuild';
33
import { isArray } from 'remeda';
44

5-
import type {
6-
GeneratorMutatorParsingInfo,
7-
Tsconfig,
8-
TsConfigTarget,
9-
} from '../types';
5+
import type { GeneratorMutatorParsingInfo, Tsconfig } from '../types';
106

117
export async function getMutatorInfo(
128
filePath: string,
@@ -34,11 +30,7 @@ export async function getMutatorInfo(
3430
tsconfig?.compilerOptions,
3531
);
3632

37-
return parseFile(
38-
code,
39-
namedExport,
40-
getEcmaVersion(tsconfig?.compilerOptions?.target),
41-
);
33+
return parseFile(code, namedExport);
4234
}
4335

4436
async function bundleFile(
@@ -74,10 +66,17 @@ async function bundleFile(
7466
function parseFile(
7567
file: string,
7668
name: string,
77-
ecmaVersion: ecmaVersion = 6,
7869
): GeneratorMutatorParsingInfo | undefined {
7970
try {
80-
const ast = Parser.parse(file, { ecmaVersion, sourceType: 'module' });
71+
// `file` is esbuild's bundled output, not the user's source. esbuild may
72+
// emit any modern syntax (notably dynamic `import()`, which it preserves
73+
// even when targeting es6 in ESM mode), so we parse with the latest
74+
// ecmaVersion to avoid spurious SyntaxErrors that would mask the export
75+
// we are looking for. See https://github.com/orval-labs/orval/issues/1634.
76+
const ast = Parser.parse(file, {
77+
ecmaVersion: 'latest',
78+
sourceType: 'module',
79+
});
8180

8281
const foundSpecifier = ast.body
8382
.filter((x) => x.type === 'ExportNamedDeclaration')
@@ -201,19 +200,3 @@ function parseFunction(
201200
}
202201
}
203202
}
204-
205-
function getEcmaVersion(target?: TsConfigTarget): ecmaVersion | undefined {
206-
if (!target) {
207-
return;
208-
}
209-
210-
if (target.toLowerCase() === 'esnext') {
211-
return 'latest';
212-
}
213-
214-
try {
215-
return Number(target.toLowerCase().replace('es', '')) as ecmaVersion;
216-
} catch {
217-
return;
218-
}
219-
}

0 commit comments

Comments
 (0)