Skip to content

Commit abd2dc8

Browse files
committed
Fail on ambiguous names and type Bun SQL rows
1 parent 38eac24 commit abd2dc8

File tree

15 files changed

+443
-86
lines changed

15 files changed

+443
-86
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ jobs:
3333

3434
- name: Install dependencies
3535
run: bun install
36+
37+
- name: Run unit tests
38+
run: bun test
3639

3740
- name: Build WASM plugin
3841
run: just plugin-wasm

.github/workflows/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ jobs:
3434
- name: Install dependencies
3535
run: bun install
3636

37+
- name: Run unit tests
38+
run: bun test
39+
3740
- name: Build WASM plugin
3841
run: just plugin-wasm && mv examples/plugin.wasm sqlc-gen-typescript.wasm
3942

.oxfmtrc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
3+
"useTabs": false,
4+
"singleQuote": false,
5+
"trailingComma": "all",
6+
"printWidth": 100,
7+
"ignorePatterns": ["out.js", "src/gen/plugin/codegen_pb.ts", "examples/**", "node_modules/**"]
8+
}

.oxlintrc.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"plugins": ["typescript", "import", "unicorn"],
3+
"categories": {
4+
"correctness": "error"
5+
},
6+
"rules": {
7+
"import/order": [
8+
"error",
9+
{
10+
"groups": ["builtin", "external", "internal", "parent", "sibling", "index"],
11+
"newlines-between": "always",
12+
"alphabetize": { "order": "asc", "caseInsensitive": true }
13+
}
14+
]
15+
},
16+
"ignorePatterns": ["out.js", "src/gen/plugin/codegen_pb.ts", "examples/**", "node_modules/**"]
17+
}

Justfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,17 @@ codegen-proto:
2020
clean:
2121
rm -f out.js examples/plugin.wasm
2222

23+
# Format TypeScript
24+
fmt:
25+
bunx oxfmt
26+
27+
# Lint TypeScript
28+
lint:
29+
bunx oxlint --deny-warnings
30+
31+
# Run unit tests
32+
test:
33+
bun test
34+
2335
# Build everything from scratch
2436
build: clean generate

bun.lock

Lines changed: 49 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
"description": "",
55
"main": "app.js",
66
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1"
7+
"test": "bun test"
88
},
99
"author": "",
1010
"license": "ISC",
1111
"devDependencies": {
12+
"@types/bun": "^1.3.5",
1213
"esbuild": "^0.19.5",
14+
"oxfmt": "^0.19.0",
15+
"oxlint": "^1.34.0",
1316
"typescript": "^5.2.2"
1417
},
1518
"dependencies": {

src/app.ts

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import {
2727
} from "./gen/plugin/codegen_pb";
2828

2929
import { argName, colName } from "./drivers/utlis";
30+
import { rowValuesDecl } from "./decls";
31+
import { assertUniqueNames } from "./validate";
3032
import { Driver as Sqlite3Driver } from "./drivers/better-sqlite3";
3133
import { Driver as PgDriver } from "./drivers/pg";
3234
import { Driver as PostgresDriver } from "./drivers/postgres";
@@ -127,23 +129,11 @@ function codegen(input: GenerateRequest): GenerateResponse {
127129
qs?.push(query);
128130
}
129131

130-
for (const [filename, queries] of querymap.entries()) {
131-
const nodes = driver.preamble(queries);
132+
for (const [filename, queries] of querymap.entries()) {
133+
const nodes = driver.preamble(queries);
132134

133-
for (const query of queries) {
134-
const colmap = new Map<string, number>();
135-
for (let column of query.columns) {
136-
if (!column.name) {
137-
continue;
138-
}
139-
const count = colmap.get(column.name) || 0;
140-
if (count > 0) {
141-
column.name = `${column.name}_${count + 1}`;
142-
}
143-
colmap.set(column.name, count + 1);
144-
}
145-
146-
const lowerName = query.name[0].toLowerCase() + query.name.slice(1);
135+
for (const query of queries) {
136+
const lowerName = query.name[0].toLowerCase() + query.name.slice(1);
147137
const textName = `${lowerName}Query`;
148138

149139
nodes.push(
@@ -158,11 +148,28 @@ ${query.text}`
158148
let returnIface = undefined;
159149
if (query.params.length > 0) {
160150
argIface = `${query.name}Args`;
161-
nodes.push(argsDecl(argIface, driver, query.params));
151+
nodes.push(
152+
argsDecl({
153+
name: argIface,
154+
driver,
155+
params: query.params,
156+
queryName: query.name,
157+
fileName: filename,
158+
})
159+
);
162160
}
163161
if (query.columns.length > 0) {
164162
returnIface = `${query.name}Row`;
165-
nodes.push(rowDecl(returnIface, driver, query.columns));
163+
nodes.push(
164+
rowDecl({
165+
name: returnIface,
166+
driver,
167+
columns: query.columns,
168+
queryName: query.name,
169+
fileName: filename,
170+
})
171+
);
172+
nodes.push(rowValuesDecl(`${returnIface}Values`, driver, query.columns));
166173
}
167174

168175
switch (query.cmd) {
@@ -244,43 +251,63 @@ function queryDecl(name: string, sql: string) {
244251
);
245252
}
246253

247-
function argsDecl(
248-
name: string,
249-
driver: Driver,
250-
params: Parameter[]
251-
) {
254+
function argsDecl(options: {
255+
name: string;
256+
driver: Driver;
257+
params: Parameter[];
258+
queryName: string;
259+
fileName: string;
260+
}) {
261+
const names = options.params.map((param, i) => argName(i, param.column));
262+
assertUniqueNames({
263+
kind: "argument",
264+
queryName: options.queryName,
265+
fileName: options.fileName,
266+
names,
267+
});
268+
252269
return factory.createInterfaceDeclaration(
253270
[factory.createToken(SyntaxKind.ExportKeyword)],
254-
factory.createIdentifier(name),
271+
factory.createIdentifier(options.name),
255272
undefined,
256273
undefined,
257-
params.map((param, i) =>
274+
options.params.map((param, i) =>
258275
factory.createPropertySignature(
259276
undefined,
260277
factory.createIdentifier(argName(i, param.column)),
261278
undefined,
262-
driver.columnType(param.column)
279+
options.driver.columnType(param.column)
263280
)
264281
)
265282
);
266283
}
267284

268-
function rowDecl(
269-
name: string,
270-
driver: Driver,
271-
columns: Column[]
272-
) {
285+
function rowDecl(options: {
286+
name: string;
287+
driver: Driver;
288+
columns: Column[];
289+
queryName: string;
290+
fileName: string;
291+
}) {
292+
const names = options.columns.map((column, i) => colName(i, column));
293+
assertUniqueNames({
294+
kind: "column",
295+
queryName: options.queryName,
296+
fileName: options.fileName,
297+
names,
298+
});
299+
273300
return factory.createInterfaceDeclaration(
274301
[factory.createToken(SyntaxKind.ExportKeyword)],
275-
factory.createIdentifier(name),
302+
factory.createIdentifier(options.name),
276303
undefined,
277304
undefined,
278-
columns.map((column, i) =>
305+
options.columns.map((column, i) =>
279306
factory.createPropertySignature(
280307
undefined,
281308
factory.createIdentifier(colName(i, column)),
282309
undefined,
283-
driver.columnType(column)
310+
options.driver.columnType(column)
284311
)
285312
)
286313
);

src/decls.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { describe, expect, it } from "bun:test";
2+
import {
3+
createPrinter,
4+
createSourceFile,
5+
EmitHint,
6+
NewLineKind,
7+
ScriptKind,
8+
ScriptTarget,
9+
} from "typescript";
10+
11+
import { rowValuesDecl } from "./decls";
12+
import { Driver as BunSqlDriver } from "./drivers/bun-sql";
13+
import type { Column } from "./gen/plugin/codegen_pb";
14+
15+
function print(node: unknown): string {
16+
const source = createSourceFile(
17+
"file.ts",
18+
"",
19+
ScriptTarget.Latest,
20+
false,
21+
ScriptKind.TS
22+
);
23+
const printer = createPrinter({ newLine: NewLineKind.LineFeed });
24+
// @ts-expect-error printer expects a ts.Node
25+
return printer.printNode(EmitHint.Unspecified, node, source);
26+
}
27+
28+
describe("rowValuesDecl", () => {
29+
it("emits an exported tuple type", () => {
30+
const driver = new BunSqlDriver();
31+
const columns: Column[] = [
32+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
33+
({ name: "id", type: { name: "text" } } as unknown as Column),
34+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
35+
({ name: "created_at", type: { name: "timestamptz" } } as unknown as Column),
36+
];
37+
38+
const node = rowValuesDecl("ExampleRowValues", driver, columns);
39+
const output = print(node);
40+
41+
expect(output).toContain("export type ExampleRowValues");
42+
expect(output).toContain("string | null");
43+
expect(output).toContain("Date | null");
44+
});
45+
});

src/decls.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { SyntaxKind, TypeNode, factory } from "typescript";
2+
3+
import type { Column } from "./gen/plugin/codegen_pb";
4+
5+
export interface ColumnTyper {
6+
columnType: (c?: Column) => TypeNode;
7+
}
8+
9+
export function rowValuesDecl(name: string, driver: ColumnTyper, columns: Column[]) {
10+
return factory.createTypeAliasDeclaration(
11+
[factory.createToken(SyntaxKind.ExportKeyword)],
12+
factory.createIdentifier(name),
13+
undefined,
14+
factory.createTupleTypeNode(columns.map((c) => driver.columnType(c)))
15+
);
16+
}
17+
18+
export function arrayOfTypeRef(name: string) {
19+
return factory.createArrayTypeNode(
20+
factory.createTypeReferenceNode(factory.createIdentifier(name), undefined)
21+
);
22+
}

0 commit comments

Comments
 (0)