Skip to content

Commit 86c7115

Browse files
committed
fix: language-aware declaration sharing check
1 parent 0109508 commit 86c7115

4 files changed

Lines changed: 68 additions & 7 deletions

File tree

.changeset/stale-spiders-sleep.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hey-api/codegen-core": patch
3+
---
4+
5+
**planner**: language-aware declaration sharing check

packages/codegen-core/src/__tests__/project.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { canDeclarationsShareIdentifier } from '../project/namespace';
12
import { Project } from '../project/project';
3+
import type { SymbolKind } from '../symbols/types';
24

35
// Mock Planner so we control what files appear in project.files
46
vi.mock('../planner/planner', () => {
@@ -54,3 +56,44 @@ describe('Project', () => {
5456
});
5557
});
5658
});
59+
60+
describe('canDeclarationsShareIdentifier', () => {
61+
const kinds: ReadonlyArray<SymbolKind> = [
62+
'class',
63+
'enum',
64+
'function',
65+
'interface',
66+
'namespace',
67+
'type',
68+
'var',
69+
];
70+
71+
it('matches TypeScript declaration merging matrix', () => {
72+
const allowed = new Set([
73+
'interface|interface',
74+
'class|interface',
75+
'class|namespace',
76+
'enum|namespace',
77+
'function|namespace',
78+
'namespace|namespace',
79+
'function|type',
80+
'type|var',
81+
]);
82+
83+
for (const a of kinds) {
84+
for (const b of kinds) {
85+
expect(canDeclarationsShareIdentifier('typescript', a, b)).toBe(
86+
allowed.has([a, b].sort().join('|')),
87+
);
88+
}
89+
}
90+
});
91+
92+
it('returns false for Python declaration pairs', () => {
93+
for (const a of kinds) {
94+
for (const b of kinds) {
95+
expect(canDeclarationsShareIdentifier('python', a, b)).toBe(false);
96+
}
97+
}
98+
});
99+
});

packages/codegen-core/src/planner/planner.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { ExportModule, ImportModule } from '../bindings';
44
import type { IProjectRenderMeta } from '../extensions';
55
import type { File } from '../files/file';
66
import type { INode } from '../nodes/node';
7-
import { canShareName } from '../project/namespace';
7+
import { canDeclarationsShareIdentifier } from '../project/namespace';
88
import type { IProject } from '../project/types';
99
import { fromRef } from '../refs/refs';
1010
import type { RenderContext } from '../renderer';
@@ -417,13 +417,13 @@ export class Planner {
417417

418418
const localNames = ctx.localNames(scope);
419419
while (true) {
420+
const language = node?.language || symbol.node?.language || file.language;
421+
420422
const kinds = [...(localNames.get(finalName) ?? [])];
421423

422-
// TODO: adjust canShareName for language
423-
const ok = kinds.every((kind) => canShareName(symbol.kind, kind));
424+
const ok = kinds.every((kind) => canDeclarationsShareIdentifier(language, symbol.kind, kind));
424425
if (ok) break;
425426

426-
const language = node?.language || symbol.node?.language || file.language;
427427
const resolver =
428428
(language ? this.project.nameConflictResolvers[language] : undefined) ??
429429
this.project.defaultNameConflictResolver;

packages/codegen-core/src/project/namespace.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import type { Language } from '../languages/types';
12
import type { SymbolKind } from '../symbols/types';
23

3-
const kindRank: Record<SymbolKind, number> = {
4+
const typescriptMergeKindRank: Record<SymbolKind, number> = {
45
class: 3,
56
enum: 4,
67
function: 5,
@@ -14,10 +15,10 @@ const kindRank: Record<SymbolKind, number> = {
1415
* Returns true if two declarations of given kinds
1516
* are allowed to share the same identifier in TypeScript.
1617
*/
17-
export function canShareName(a: SymbolKind, b: SymbolKind): boolean {
18+
function canTypeScriptDeclarationsShareIdentifier(a: SymbolKind, b: SymbolKind): boolean {
1819
// sort based on TypeScript merge precedence so `a` is always the weaker merge candidate
1920
// ensures that asymmetric merges like `type + var` are correctly handled
20-
if (kindRank[a] > kindRank[b]) {
21+
if (typescriptMergeKindRank[a] > typescriptMergeKindRank[b]) {
2122
[a, b] = [b, a];
2223
}
2324

@@ -33,3 +34,15 @@ export function canShareName(a: SymbolKind, b: SymbolKind): boolean {
3334
return false;
3435
}
3536
}
37+
38+
export function canDeclarationsShareIdentifier(
39+
language: Language | undefined,
40+
a: SymbolKind,
41+
b: SymbolKind,
42+
): boolean {
43+
if (language === 'typescript') {
44+
return canTypeScriptDeclarationsShareIdentifier(a, b);
45+
}
46+
47+
return false;
48+
}

0 commit comments

Comments
 (0)