Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/brown-paws-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': minor
---

feat: add symbol api
5 changes: 5 additions & 0 deletions .changeset/cuddly-bears-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/codegen-core': minor
---

feat: expand symbol api
5 changes: 5 additions & 0 deletions .changeset/thirty-shoes-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': minor
---

feat(pinia-colada): remove groupByTag option
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,5 @@
"typescript-eslint": "8.29.1",
"vitest": "3.1.1"
},
"packageManager": "[email protected].0"
"packageManager": "[email protected].1"
}
53 changes: 32 additions & 21 deletions packages/codegen-core/src/__tests__/file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { beforeEach, describe, expect, it } from 'vitest';

import { CodegenFile } from '../files/file';
import type { ICodegenImport } from '../imports/types';
import type { ICodegenSymbol } from '../symbols/types';
import { CodegenProject } from '../project/project';
import type { ICodegenSymbolIn } from '../symbols/types';

describe('CodegenFile', () => {
let file: CodegenFile;
let project: CodegenProject;

beforeEach(() => {
file = new CodegenFile('a.ts');
project = new CodegenProject();
file = new CodegenFile('a.ts', project);
});

it('initializes with empty imports and symbols', () => {
Expand Down Expand Up @@ -58,7 +61,7 @@ describe('CodegenFile', () => {
B: 'AliasB',
},
from: 'a',
names: ['A', 'B'],
names: ['A', 'AType', 'B'],
typeNames: ['AType'],
});
});
Expand Down Expand Up @@ -105,41 +108,48 @@ describe('CodegenFile', () => {
B: 'AliasB',
},
from: 'a',
names: ['A', 'B'],
names: ['A', 'AType', 'B'],
typeNames: ['AType'],
});
});

it('adds symbols', () => {
const sym1: ICodegenSymbol = { name: 'a' };
const sym2: ICodegenSymbol = { name: 'b' };
const sym1: ICodegenSymbolIn = { name: 'a', value: 'a' };
const sym2: ICodegenSymbolIn = { name: 'b', value: 'b' };
const sym3: ICodegenSymbolIn = { headless: true, name: 'c' };

file.addSymbol(sym1);
file.addSymbol(sym2);
file.addSymbol(sym3);

expect(file.symbols.length).toBe(2);
expect(file.symbols[0]).not.toBeUndefined();
expect(file.symbols[0]).not.toBe(sym1);
expect(file.symbols[0]).toEqual(sym1);
expect(file.symbols[0]).toMatchObject(sym1);
expect(file.symbols[1]).not.toBeUndefined();
expect(file.symbols[1]).not.toBe(sym2);
expect(file.symbols[1]).toEqual(sym2);
expect(file.symbols[1]).toMatchObject(sym2);
});

it('merges duplicate symbols', () => {
const sym1: ICodegenSymbol = {
it('updates symbols', () => {
const sym1: ICodegenSymbolIn = {
headless: true,
name: 'a',
value: 1,
};
const sym2: ICodegenSymbol = {
name: 'a',
const inserted = file.addSymbol(sym1);
expect(file.symbols.length).toBe(0);

const sym2: ICodegenSymbolIn = {
headless: false,
name: 'b',
value: 'foo',
};

file.addSymbol(sym1);
file.addSymbol(sym2);
inserted.update(sym2);

expect(file.symbols.length).toBe(1);
expect(file.symbols[0]).toEqual({
name: 'a',
expect(file.symbols[0]).toMatchObject({
name: 'b',
value: 'foo',
});
});
Expand All @@ -162,9 +172,9 @@ describe('CodegenFile', () => {
});

it('hasSymbol returns true if symbol exists', () => {
file.addSymbol({ name: 'Exists', value: {} });
expect(file.hasSymbol('Exists')).toBe(true);
expect(file.hasSymbol('Missing')).toBe(false);
const symbol = file.addSymbol({ name: 'Exists', value: {} });
expect(file.hasSymbol(symbol.id)).toBe(true);
expect(file.hasSymbol(-1)).toBe(false);
});

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

file.addSymbol(symbol);
expect(file.symbols).toEqual([symbol]);
expect(file.symbols.length).toBe(1);
expect(file.symbols[0]).toMatchObject(symbol);
});

it('returns relative path to another files', () => {
Expand Down
14 changes: 14 additions & 0 deletions packages/codegen-core/src/__tests__/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* @ts-nocheck */

type _heyapi_5_ = string;
type _heyapi_4_ = () => _heyapi_5_;

/**
* something about _heyapi_1_. Did you know that __heyapi_1__?
*/
export class _heyapi_1_ {
// _heyapi_1_ is great!
_heyapi_2_(_heyapi_12_: ReturnType<_heyapi_4_>): _heyapi_5_ {
return _heyapi_12_;
}
}
44 changes: 27 additions & 17 deletions packages/codegen-core/src/__tests__/project.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { beforeEach, describe, expect, it } from 'vitest';

import { CodegenFile } from '../files/file';
import type { ICodegenFile } from '../files/types';
import type { ICodegenMeta } from '../meta/types';
import type { ICodegenOutput } from '../output/types';
import { CodegenProject } from '../project/project';
import type { ICodegenRenderer } from '../renderers/types';

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

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

project.addExportToFile('a.ts', imp);
project.addExport('a.ts', imp);

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

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

project.addImportToFile('a.ts', imp);
project.addImport('a.ts', imp);

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

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

project.addSymbolToFile('a.ts', symbol);
project.addSymbol('a.ts', symbol);

const file = project.getFileByPath('a.ts')!;
expect(file).toBeDefined();
expect(file.symbols.length).toBe(1);
expect(file.symbols[0]).toEqual(symbol);
expect(file.symbols[0]).toMatchObject(symbol);
});

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

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

it('render returns output from all files', () => {
class Renderer implements ICodegenRenderer {
id = 'foo';
render(file: CodegenFile, meta?: ICodegenMeta): ICodegenOutput {
return {
content: `content ${file.path}`,
meta: { ...meta },
path: file.path,
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
renderHeader(_file: CodegenFile, _meta?: ICodegenMeta): string {
return '';
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
renderSymbols(file: CodegenFile, _meta?: ICodegenMeta): string {
return `content ${file.path}`;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
replacerFn(_args: {
file: ICodegenFile;
headless?: boolean;
scope?: 'file' | 'project';
symbolId: number;
}): string | undefined {
return undefined;
}
}
const renderer = new Renderer();
Expand All @@ -109,8 +119,8 @@ describe('CodegenProject', () => {

const outputs = project.render(meta);
expect(outputs).toEqual([
{ content: 'content a.ts', meta: { foo: 42 }, path: 'a.ts' },
{ content: 'content b.ts', meta: { foo: 42 }, path: 'b.ts' },
{ content: 'content a.ts', meta: { renderer: 'foo' }, path: 'a.ts' },
{ content: 'content b.ts', meta: { renderer: 'foo' }, path: 'b.ts' },
]);
});

Expand Down
40 changes: 40 additions & 0 deletions packages/codegen-core/src/__tests__/renderer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import fs from 'node:fs';
import path from 'node:path';

import { describe, expect, it } from 'vitest';

import { replaceWrappedIds } from '../renderers/renderer';

describe('replaceWrappedIds', () => {
it('replaces ids with names', () => {
const source = fs.readFileSync(path.resolve(__dirname, 'file.ts'), {
encoding: 'utf8',
});

const substitutions: Record<number, string> = {
1: 'Foo',
12: 'baz',
2: 'bar',
4: 'Bar',
5: 'Foo',
};

const replaced = replaceWrappedIds(source, (id) => substitutions[id]);

expect(replaced).toEqual(`/* @ts-nocheck */

type Foo = string;
type Bar = () => Foo;

/**
* something about Foo. Did you know that _Foo_?
*/
export class Foo {
// Foo is great!
bar(baz: ReturnType<Bar>): Foo {
return baz;
}
}
`);
});
});
64 changes: 64 additions & 0 deletions packages/codegen-core/src/bimap/bimap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { ICodegenBiMap } from './types';

export class BiMap<Key, Value> implements ICodegenBiMap<Key, Value> {
private map = new Map<Key, Value>();
private reverse = new Map<Value, Key>();

delete(key: Key): boolean {
const value = this.map.get(key);
if (value !== undefined) {
this.reverse.delete(value);
}
return this.map.delete(key);
}

deleteValue(value: Value): boolean {
const key = this.reverse.get(value);
if (key !== undefined) {
this.map.delete(key);
}
return this.reverse.delete(value);
}

entries(): IterableIterator<[Key, Value]> {
return this.map.entries();
}

get(key: Key): Value | undefined {
return this.map.get(key);
}

getKey(value: Value): Key | undefined {
return this.reverse.get(value);
}

hasKey(key: Key): boolean {
return this.map.has(key);
}

hasValue(value: Value): boolean {
return this.reverse.has(value);
}

keys(): IterableIterator<Key> {
return this.map.keys();
}

set(key: Key, value: Value): this {
this.map.set(key, value);
this.reverse.set(value, key);
return this;
}

get size(): number {
return this.map.size;
}

values(): IterableIterator<Value> {
return this.map.values();
}

[Symbol.iterator](): IterableIterator<[Key, Value]> {
return this.map[Symbol.iterator]();
}
}
Loading
Loading