Skip to content

Commit 9b8efe7

Browse files
authored
Merge pull request #2582 from hey-api/feat/codegen-symbol
🪧 Symbol API
2 parents a17130e + f6e3b24 commit 9b8efe7

File tree

1,552 files changed

+35849
-13208
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,552 files changed

+35849
-13208
lines changed

.changeset/brown-paws-design.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/openapi-ts': minor
3+
---
4+
5+
feat: add symbol api

.changeset/cuddly-bears-deny.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': minor
3+
---
4+
5+
feat: expand symbol api

.changeset/thirty-shoes-end.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/openapi-ts': minor
3+
---
4+
5+
feat(pinia-colada): remove groupByTag option

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,5 @@
7070
"typescript-eslint": "8.29.1",
7171
"vitest": "3.1.1"
7272
},
73-
"packageManager": "[email protected].0"
73+
"packageManager": "[email protected].1"
7474
}

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

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import { beforeEach, describe, expect, it } from 'vitest';
22

33
import { CodegenFile } from '../files/file';
44
import type { ICodegenImport } from '../imports/types';
5-
import type { ICodegenSymbol } from '../symbols/types';
5+
import { CodegenProject } from '../project/project';
6+
import type { ICodegenSymbolIn } from '../symbols/types';
67

78
describe('CodegenFile', () => {
89
let file: CodegenFile;
10+
let project: CodegenProject;
911

1012
beforeEach(() => {
11-
file = new CodegenFile('a.ts');
13+
project = new CodegenProject();
14+
file = new CodegenFile('a.ts', project);
1215
});
1316

1417
it('initializes with empty imports and symbols', () => {
@@ -58,7 +61,7 @@ describe('CodegenFile', () => {
5861
B: 'AliasB',
5962
},
6063
from: 'a',
61-
names: ['A', 'B'],
64+
names: ['A', 'AType', 'B'],
6265
typeNames: ['AType'],
6366
});
6467
});
@@ -105,41 +108,48 @@ describe('CodegenFile', () => {
105108
B: 'AliasB',
106109
},
107110
from: 'a',
108-
names: ['A', 'B'],
111+
names: ['A', 'AType', 'B'],
109112
typeNames: ['AType'],
110113
});
111114
});
112115

113116
it('adds symbols', () => {
114-
const sym1: ICodegenSymbol = { name: 'a' };
115-
const sym2: ICodegenSymbol = { name: 'b' };
117+
const sym1: ICodegenSymbolIn = { name: 'a', value: 'a' };
118+
const sym2: ICodegenSymbolIn = { name: 'b', value: 'b' };
119+
const sym3: ICodegenSymbolIn = { headless: true, name: 'c' };
116120

117121
file.addSymbol(sym1);
118122
file.addSymbol(sym2);
123+
file.addSymbol(sym3);
119124

120125
expect(file.symbols.length).toBe(2);
126+
expect(file.symbols[0]).not.toBeUndefined();
121127
expect(file.symbols[0]).not.toBe(sym1);
122-
expect(file.symbols[0]).toEqual(sym1);
128+
expect(file.symbols[0]).toMatchObject(sym1);
129+
expect(file.symbols[1]).not.toBeUndefined();
123130
expect(file.symbols[1]).not.toBe(sym2);
124-
expect(file.symbols[1]).toEqual(sym2);
131+
expect(file.symbols[1]).toMatchObject(sym2);
125132
});
126133

127-
it('merges duplicate symbols', () => {
128-
const sym1: ICodegenSymbol = {
134+
it('updates symbols', () => {
135+
const sym1: ICodegenSymbolIn = {
136+
headless: true,
129137
name: 'a',
130138
value: 1,
131139
};
132-
const sym2: ICodegenSymbol = {
133-
name: 'a',
140+
const inserted = file.addSymbol(sym1);
141+
expect(file.symbols.length).toBe(0);
142+
143+
const sym2: ICodegenSymbolIn = {
144+
headless: false,
145+
name: 'b',
134146
value: 'foo',
135147
};
136-
137-
file.addSymbol(sym1);
138-
file.addSymbol(sym2);
148+
inserted.update(sym2);
139149

140150
expect(file.symbols.length).toBe(1);
141-
expect(file.symbols[0]).toEqual({
142-
name: 'a',
151+
expect(file.symbols[0]).toMatchObject({
152+
name: 'b',
143153
value: 'foo',
144154
});
145155
});
@@ -162,9 +172,9 @@ describe('CodegenFile', () => {
162172
});
163173

164174
it('hasSymbol returns true if symbol exists', () => {
165-
file.addSymbol({ name: 'Exists', value: {} });
166-
expect(file.hasSymbol('Exists')).toBe(true);
167-
expect(file.hasSymbol('Missing')).toBe(false);
175+
const symbol = file.addSymbol({ name: 'Exists', value: {} });
176+
expect(file.hasSymbol(symbol.id)).toBe(true);
177+
expect(file.hasSymbol(-1)).toBe(false);
168178
});
169179

170180
it('imports, exports, and symbols getters cache arrays and update after add', () => {
@@ -182,7 +192,8 @@ describe('CodegenFile', () => {
182192
expect(file.imports).toEqual([imp]);
183193

184194
file.addSymbol(symbol);
185-
expect(file.symbols).toEqual([symbol]);
195+
expect(file.symbols.length).toBe(1);
196+
expect(file.symbols[0]).toMatchObject(symbol);
186197
});
187198

188199
it('returns relative path to another files', () => {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* @ts-nocheck */
2+
3+
type _heyapi_5_ = string;
4+
type _heyapi_4_ = () => _heyapi_5_;
5+
6+
/**
7+
* something about _heyapi_1_. Did you know that __heyapi_1__?
8+
*/
9+
export class _heyapi_1_ {
10+
// _heyapi_1_ is great!
11+
_heyapi_2_(_heyapi_12_: ReturnType<_heyapi_4_>): _heyapi_5_ {
12+
return _heyapi_12_;
13+
}
14+
}

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

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { beforeEach, describe, expect, it } from 'vitest';
22

33
import { CodegenFile } from '../files/file';
4+
import type { ICodegenFile } from '../files/types';
45
import type { ICodegenMeta } from '../meta/types';
5-
import type { ICodegenOutput } from '../output/types';
66
import { CodegenProject } from '../project/project';
77
import type { ICodegenRenderer } from '../renderers/types';
88

@@ -31,37 +31,37 @@ describe('CodegenProject', () => {
3131
expect(project.getFileByPath('b.ts')).toBe(newFile2);
3232
});
3333

34-
it('addExportToFile creates file if missing and adds export', () => {
34+
it('addExport creates file if missing and adds export', () => {
3535
const imp = { from: 'lib', names: ['Foo'] };
3636

37-
project.addExportToFile('a.ts', imp);
37+
project.addExport('a.ts', imp);
3838

3939
const file = project.getFileByPath('a.ts')!;
4040
expect(file).toBeDefined();
4141
expect(file.exports.length).toBe(1);
4242
expect(file.exports[0]).toEqual(imp);
4343
});
4444

45-
it('addImportToFile creates file if missing and adds import', () => {
45+
it('addImport creates file if missing and adds import', () => {
4646
const imp = { from: 'lib', names: ['Foo'] };
4747

48-
project.addImportToFile('a.ts', imp);
48+
project.addImport('a.ts', imp);
4949

5050
const file = project.getFileByPath('a.ts')!;
5151
expect(file).toBeDefined();
5252
expect(file.imports.length).toBe(1);
5353
expect(file.imports[0]).toEqual(imp);
5454
});
5555

56-
it('addSymbolToFile creates file if missing and adds symbol', () => {
56+
it('addSymbol creates file if missing and adds symbol', () => {
5757
const symbol = { name: 'MySymbol', value: {} };
5858

59-
project.addSymbolToFile('a.ts', symbol);
59+
project.addSymbol('a.ts', symbol);
6060

6161
const file = project.getFileByPath('a.ts')!;
6262
expect(file).toBeDefined();
6363
expect(file.symbols.length).toBe(1);
64-
expect(file.symbols[0]).toEqual(symbol);
64+
expect(file.symbols[0]).toMatchObject(symbol);
6565
});
6666

6767
it('getAllSymbols returns all symbols from all files', () => {
@@ -87,19 +87,29 @@ describe('CodegenProject', () => {
8787

8888
// @ts-expect-error
8989
// mutate returned array should not affect internal state
90-
files.push(new CodegenFile('b.ts'));
90+
files.push(new CodegenFile('b.ts', project));
9191
expect(project.files).toEqual([file]);
9292
});
9393

9494
it('render returns output from all files', () => {
9595
class Renderer implements ICodegenRenderer {
9696
id = 'foo';
97-
render(file: CodegenFile, meta?: ICodegenMeta): ICodegenOutput {
98-
return {
99-
content: `content ${file.path}`,
100-
meta: { ...meta },
101-
path: file.path,
102-
};
97+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
98+
renderHeader(_file: CodegenFile, _meta?: ICodegenMeta): string {
99+
return '';
100+
}
101+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
102+
renderSymbols(file: CodegenFile, _meta?: ICodegenMeta): string {
103+
return `content ${file.path}`;
104+
}
105+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
106+
replacerFn(_args: {
107+
file: ICodegenFile;
108+
headless?: boolean;
109+
scope?: 'file' | 'project';
110+
symbolId: number;
111+
}): string | undefined {
112+
return undefined;
103113
}
104114
}
105115
const renderer = new Renderer();
@@ -109,8 +119,8 @@ describe('CodegenProject', () => {
109119

110120
const outputs = project.render(meta);
111121
expect(outputs).toEqual([
112-
{ content: 'content a.ts', meta: { foo: 42 }, path: 'a.ts' },
113-
{ content: 'content b.ts', meta: { foo: 42 }, path: 'b.ts' },
122+
{ content: 'content a.ts', meta: { renderer: 'foo' }, path: 'a.ts' },
123+
{ content: 'content b.ts', meta: { renderer: 'foo' }, path: 'b.ts' },
114124
]);
115125
});
116126

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
4+
import { describe, expect, it } from 'vitest';
5+
6+
import { replaceWrappedIds } from '../renderers/renderer';
7+
8+
describe('replaceWrappedIds', () => {
9+
it('replaces ids with names', () => {
10+
const source = fs.readFileSync(path.resolve(__dirname, 'file.ts'), {
11+
encoding: 'utf8',
12+
});
13+
14+
const substitutions: Record<number, string> = {
15+
1: 'Foo',
16+
12: 'baz',
17+
2: 'bar',
18+
4: 'Bar',
19+
5: 'Foo',
20+
};
21+
22+
const replaced = replaceWrappedIds(source, (id) => substitutions[id]);
23+
24+
expect(replaced).toEqual(`/* @ts-nocheck */
25+
26+
type Foo = string;
27+
type Bar = () => Foo;
28+
29+
/**
30+
* something about Foo. Did you know that _Foo_?
31+
*/
32+
export class Foo {
33+
// Foo is great!
34+
bar(baz: ReturnType<Bar>): Foo {
35+
return baz;
36+
}
37+
}
38+
`);
39+
});
40+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { ICodegenBiMap } from './types';
2+
3+
export class BiMap<Key, Value> implements ICodegenBiMap<Key, Value> {
4+
private map = new Map<Key, Value>();
5+
private reverse = new Map<Value, Key>();
6+
7+
delete(key: Key): boolean {
8+
const value = this.map.get(key);
9+
if (value !== undefined) {
10+
this.reverse.delete(value);
11+
}
12+
return this.map.delete(key);
13+
}
14+
15+
deleteValue(value: Value): boolean {
16+
const key = this.reverse.get(value);
17+
if (key !== undefined) {
18+
this.map.delete(key);
19+
}
20+
return this.reverse.delete(value);
21+
}
22+
23+
entries(): IterableIterator<[Key, Value]> {
24+
return this.map.entries();
25+
}
26+
27+
get(key: Key): Value | undefined {
28+
return this.map.get(key);
29+
}
30+
31+
getKey(value: Value): Key | undefined {
32+
return this.reverse.get(value);
33+
}
34+
35+
hasKey(key: Key): boolean {
36+
return this.map.has(key);
37+
}
38+
39+
hasValue(value: Value): boolean {
40+
return this.reverse.has(value);
41+
}
42+
43+
keys(): IterableIterator<Key> {
44+
return this.map.keys();
45+
}
46+
47+
set(key: Key, value: Value): this {
48+
this.map.set(key, value);
49+
this.reverse.set(value, key);
50+
return this;
51+
}
52+
53+
get size(): number {
54+
return this.map.size;
55+
}
56+
57+
values(): IterableIterator<Value> {
58+
return this.map.values();
59+
}
60+
61+
[Symbol.iterator](): IterableIterator<[Key, Value]> {
62+
return this.map[Symbol.iterator]();
63+
}
64+
}

0 commit comments

Comments
 (0)