Skip to content

Commit 2205314

Browse files
authored
Merge branch 'master' into feat/3704-angular-artifact-groups
2 parents d1418e0 + c082bb4 commit 2205314

6 files changed

Lines changed: 119 additions & 6 deletions

File tree

packages/core/src/generators/imports.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,36 @@ export type MyError = Error;
143143

144144
expect(dep).toBe("import {\n schema$Value\n} from '../models';\n");
145145
});
146+
147+
// Regression for #3695: an aliased import is referenced by its alias only
148+
// (rendered `name as alias`), so a bare occurrence of the pre-alias name in
149+
// generated code (e.g. a path param `z` colliding with `z as zod`) must not
150+
// pull the dependency in.
151+
it('does not add an aliased import when only its pre-alias name appears', () => {
152+
const dep = addDependency({
153+
implementation: 'export const getUrl = (z: string) => `/${z}`;',
154+
dependency: 'zod',
155+
projectName: undefined,
156+
hasSchemaDir: true,
157+
isAllowSyntheticDefaultImports: true,
158+
exports: [{ name: 'z', alias: 'zod', values: true }],
159+
});
160+
161+
expect(dep).toBeUndefined();
162+
});
163+
164+
it('adds an aliased import when its alias appears', () => {
165+
const dep = addDependency({
166+
implementation: 'const schema = zod.string();',
167+
dependency: 'zod',
168+
projectName: undefined,
169+
hasSchemaDir: true,
170+
isAllowSyntheticDefaultImports: true,
171+
exports: [{ name: 'z', alias: 'zod', values: true }],
172+
});
173+
174+
expect(dep).toBe("import {\n z as zod\n} from 'zod';\n");
175+
});
146176
});
147177

148178
// `oneMore` is set only by the tags-split writer (split-tags-mode.ts),

packages/core/src/generators/imports.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,16 +242,20 @@ export function addDependency({
242242
isAllowSyntheticDefaultImports,
243243
}: AddDependencyOptions) {
244244
const toAdds = exports.filter((e) => {
245-
const searchWords = [e.alias, e.name]
246-
.filter((p): p is string => Boolean(p?.length))
247-
.map((part) => escapeRegExp(part))
248-
.join('|');
245+
// An aliased import is rendered as `name as alias`, so the alias is the only
246+
// binding in scope; the pre-alias name never appears as a reference. Match on
247+
// the alias when present (otherwise the name) to avoid false positives such as
248+
// a path param `z` colliding with `z as zod` (#3695).
249+
const identifier = e.alias?.length ? e.alias : e.name;
249250

250-
if (!searchWords) {
251+
if (!identifier) {
251252
return false;
252253
}
253254

254-
const pattern = new RegExp(String.raw`\b(${searchWords})\b`, 'g');
255+
const pattern = new RegExp(
256+
String.raw`\b(${escapeRegExp(identifier)})\b`,
257+
'g',
258+
);
255259

256260
return implementation.match(pattern);
257261
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Generated by orval v8.20.0 🍺
3+
* Do not edit manually.
4+
* Issue 3695 - path parameter named `z` must not pull in the zod import
5+
* OpenAPI spec version: 1.0.0
6+
*/
7+
export type getZResponse200 = {
8+
data: void;
9+
status: 200;
10+
};
11+
12+
export type getZResponseSuccess = getZResponse200 & {
13+
headers: Headers;
14+
};
15+
export type getZResponse = getZResponseSuccess;
16+
17+
export const getGetZUrl = (z: string) => {
18+
return `/${z}`;
19+
};
20+
21+
export const getZ = async (
22+
z: string,
23+
options?: RequestInit,
24+
): Promise<getZResponse> => {
25+
const res = await fetch(getGetZUrl(z), {
26+
...options,
27+
method: 'GET',
28+
});
29+
30+
const body = [204, 205, 304].includes(res.status) ? null : await res.text();
31+
32+
const data: getZResponse['data'] = body ? JSON.parse(body) : undefined;
33+
return { data, status: res.status, headers: res.headers } as getZResponse;
34+
};

tests/api-generation.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,20 @@ test('fetch issue-3663 combines required from a constraint-only allOf overlay',
11151115
expect(barInlineType).toMatch(/Required<Pick<[\s\S]*?'id' \| 'name'/);
11161116
});
11171117

1118+
test('fetch issue-3695 does not import zod for a path parameter named `z`', async () => {
1119+
// The fetch client imports zod as `import { z as zod } from 'zod'`. A path
1120+
// parameter named exactly `z` must not be mistaken for a zod usage and pull
1121+
// the (otherwise unused) zod import into the client. See #3695.
1122+
const content = await readFile(
1123+
generated('fetch', 'issue-3695', 'endpoints.ts'),
1124+
'utf8',
1125+
);
1126+
1127+
expect(content).not.toContain("from 'zod'");
1128+
// The parameter itself is still generated as a normal string argument.
1129+
expect(content).toContain('z: string');
1130+
});
1131+
11181132
test('zod override.zod.version pins the output target independently of the installed zod', async () => {
11191133
// `tests` installs Zod 4, so installed-version detection would emit Zod 4 for
11201134
// both. These two clients generate from the SAME petstore spec but pin

tests/configs/fetch.config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,4 +913,15 @@ export default defineConfig({
913913
target: '../specifications/issue-3663.yaml',
914914
},
915915
},
916+
'issue-3695': {
917+
output: {
918+
target: '../generated/fetch/issue-3695/endpoints.ts',
919+
client: 'fetch',
920+
clean: true,
921+
formatter: 'prettier',
922+
},
923+
input: {
924+
target: '../specifications/issue-3695.yaml',
925+
},
926+
},
916927
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Issue 3695 - path parameter named `z` must not pull in the zod import
4+
version: 1.0.0
5+
paths:
6+
# The fetch client imports zod as `import { z as zod } from 'zod'`. A path
7+
# parameter named exactly `z` used to collide with the pre-alias name `z`,
8+
# forcing an unused zod import into the generated client. See #3695.
9+
/{z}:
10+
get:
11+
operationId: getZ
12+
parameters:
13+
- name: z
14+
in: path
15+
required: true
16+
schema:
17+
type: string
18+
responses:
19+
'200':
20+
description: ok

0 commit comments

Comments
 (0)