Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: incorrect type inference from type reference #208

Merged
merged 2 commits into from
Apr 27, 2024
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clever-elephants-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ts-safeql/eslint-plugin": patch
---

fixed an issue where type reference was inferred incorrectly
75 changes: 74 additions & 1 deletion packages/eslint-plugin/src/rules/check-sql.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { generateTestDatabaseName, setupTestDatabase } from "@ts-safeql/test-utils";
import {
generateTestDatabaseName,
setupTestDatabase,
typeColumnTsTypeEntries,
} from "@ts-safeql/test-utils";
import { ESLintUtils } from "@typescript-eslint/utils";
import { RuleTester } from "@typescript-eslint/utils/dist/ts-eslint";
import { after, before, describe, it } from "mocha";
Expand Down Expand Up @@ -66,6 +70,52 @@ const runMigrations1 = <TTypes extends Record<string, unknown>>(sql: Sql<TTypes>
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
jsonb_col JSONB NOT NULL
);

CREATE TABLE all_types (
id SERIAL PRIMARY KEY NOT NULL,
text_column TEXT NOT NULL,
varchar_column VARCHAR(255) NOT NULL,
char_column CHAR(10) NOT NULL,
int_column INTEGER NOT NULL,
smallint_column SMALLINT NOT NULL,
bigint_column BIGINT NOT NULL,
decimal_column DECIMAL(10, 2) NOT NULL,
numeric_column NUMERIC(14, 4) NOT NULL,
real_column REAL NOT NULL,
double_column DOUBLE PRECISION NOT NULL,
serial_column SERIAL NOT NULL,
bigserial_column BIGSERIAL NOT NULL,
boolean_column BOOLEAN NOT NULL,
date_column DATE NOT NULL,
time_column TIME NOT NULL,
time_with_timezone_column TIME WITH TIME ZONE NOT NULL,
timestamp_column TIMESTAMP NOT NULL,
timestamp_with_timezone_column TIMESTAMP WITH TIME ZONE NOT NULL,
interval_column INTERVAL NOT NULL,
uuid_column UUID NOT NULL,
json_column JSON NOT NULL,
jsonb_column JSONB NOT NULL,
array_text_column TEXT[] NOT NULL,
array_int_column INTEGER[] NOT NULL,
bytea_column BYTEA NOT NULL,
inet_column INET NOT NULL,
cidr_column CIDR NOT NULL,
macaddr_column MACADDR NOT NULL,
macaddr8_column MACADDR8 NOT NULL,
tsvector_column TSVECTOR NOT NULL,
tsquery_column TSQUERY NOT NULL,
xml_column XML NOT NULL,
point_column POINT NOT NULL,
line_column LINE NOT NULL,
lseg_column LSEG NOT NULL,
box_column BOX NOT NULL,
path_column PATH NOT NULL,
polygon_column POLYGON NOT NULL,
circle_column CIRCLE NOT NULL,
money_column MONEY NOT NULL,
bit_column BIT(3) NOT NULL,
bit_varying_column BIT VARYING(5) NOT NULL
);
`);

RuleTester.describe = describe;
Expand Down Expand Up @@ -715,6 +765,29 @@ RuleTester.describe("check-sql", () => {
],
});

ruleTester.run("pg type to ts type check (inline type)", rules["check-sql"], {
valid: typeColumnTsTypeEntries.map(([colName, colType]) => ({
name: `select ${colName} from table as ${colType} (using type reference)`,
filename,
options: withConnection(connections.withTag),
code: `sql<{ ${colName}: ${colType} }>\`select ${colName} from all_types\``,
})),
invalid: [],
});

ruleTester.run("pg type to ts type check (type reference)", rules["check-sql"], {
valid: typeColumnTsTypeEntries.map(([colName, colType]) => ({
name: `select ${colName} from table as ${colType} (using type reference)`,
filename,
options: withConnection(connections.withTag),
code: `
type MyType = { ${colName}: ${colType} };
sql<MyType>\`select ${colName} from all_types\`
`,
})),
invalid: [],
});

ruleTester.run("base with transform", rules["check-sql"], {
valid: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,28 +143,46 @@ function getTypePropertiesFromTypeReference(params: {
}): ResolvedTarget {
const { type, checker, parser, typeNode, reservedTypes } = params;

if (reservedTypes.has(checker.typeToString(type))) {
return { kind: "type", value: checker.typeToString(type) };
}
const typeAsString = checker.typeToString(type);

if (type.flags === ts.TypeFlags.String) {
return { kind: "type", value: "string" };
}

if (type.flags === ts.TypeFlags.Number) {
return { kind: "type", value: "number" };
}

if (type.flags === ts.TypeFlags.Boolean) {
return { kind: "type", value: "boolean" };
if (reservedTypes.has(typeAsString)) {
return { kind: "type", value: checker.typeToString(type) };
}

if (type.flags === ts.TypeFlags.Null) {
return { kind: "type", value: "null" };
switch (typeAsString) {
case "string":
return { kind: "type", value: "string" };
case "number":
return { kind: "type", value: "number" };
case "boolean":
return { kind: "type", value: "boolean" };
case "false":
return { kind: "type", value: "false" };
case "true":
return { kind: "type", value: "true" };
case "null":
return { kind: "type", value: "null" };
case "undefined":
return { kind: "type", value: "undefined" };
case "any":
return { kind: "type", value: "any" };
}

if (type.flags === ts.TypeFlags.Undefined) {
return { kind: "type", value: "undefined" };
switch (type.flags) {
case ts.TypeFlags.String:
return { kind: "type", value: "string" };
case ts.TypeFlags.Number:
return { kind: "type", value: "number" };
case ts.TypeFlags.Boolean:
return { kind: "type", value: "boolean" };
case ts.TypeFlags.Null:
return { kind: "type", value: "null" };
case ts.TypeFlags.Undefined:
return { kind: "type", value: "undefined" };
case ts.TypeFlags.Any:
return { kind: "type", value: "any" };
default:
break;
}

if (type.isLiteral()) {
Expand Down
99 changes: 98 additions & 1 deletion packages/generate/src/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,53 @@ function runMigrations(sql: SQL) {
CREATE TABLE test_overriden_domain (
col overriden_domain NOT NULL,
nullable_col overriden_domain
)
);

CREATE TABLE all_types (
id SERIAL PRIMARY KEY NOT NULL,
text_column TEXT NOT NULL,
varchar_column VARCHAR(255) NOT NULL,
char_column CHAR(10) NOT NULL,
int_column INTEGER NOT NULL,
smallint_column SMALLINT NOT NULL,
bigint_column BIGINT NOT NULL,
decimal_column DECIMAL(10, 2) NOT NULL,
numeric_column NUMERIC(14, 4) NOT NULL,
real_column REAL NOT NULL,
double_column DOUBLE PRECISION NOT NULL,
serial_column SERIAL NOT NULL,
bigserial_column BIGSERIAL NOT NULL,
boolean_column BOOLEAN NOT NULL,
date_column DATE NOT NULL,
time_column TIME NOT NULL,
time_with_timezone_column TIME WITH TIME ZONE NOT NULL,
timestamp_column TIMESTAMP NOT NULL,
timestamp_with_timezone_column TIMESTAMP WITH TIME ZONE NOT NULL,
interval_column INTERVAL NOT NULL,
uuid_column UUID NOT NULL,
json_column JSON NOT NULL,
jsonb_column JSONB NOT NULL,
array_text_column TEXT[] NOT NULL,
array_int_column INTEGER[] NOT NULL,
bytea_column BYTEA NOT NULL,
inet_column INET NOT NULL,
cidr_column CIDR NOT NULL,
macaddr_column MACADDR NOT NULL,
macaddr8_column MACADDR8 NOT NULL,
tsvector_column TSVECTOR NOT NULL,
tsquery_column TSQUERY NOT NULL,
xml_column XML NOT NULL,
point_column POINT NOT NULL,
line_column LINE NOT NULL,
lseg_column LSEG NOT NULL,
box_column BOX NOT NULL,
path_column PATH NOT NULL,
polygon_column POLYGON NOT NULL,
circle_column CIRCLE NOT NULL,
money_column MONEY NOT NULL,
bit_column BIT(3) NOT NULL,
bit_varying_column BIT VARYING(5) NOT NULL
);
`);
}

Expand Down Expand Up @@ -172,6 +218,57 @@ test("select columns", async () => {
});
});

test.only("select all_types", async () => {
await testQuery({
query: `SELECT * FROM all_types`,
expected: [
["id", { kind: "type", value: "number" }],
["text_column", { kind: "type", value: "string" }],
["varchar_column", { kind: "type", value: "string" }],
["char_column", { kind: "type", value: "string" }],
["int_column", { kind: "type", value: "number" }],
["smallint_column", { kind: "type", value: "number" }],
["bigint_column", { kind: "type", value: "string" }],
["decimal_column", { kind: "type", value: "string" }],
["numeric_column", { kind: "type", value: "string" }],
["real_column", { kind: "type", value: "number" }],
["double_column", { kind: "type", value: "number" }],
["serial_column", { kind: "type", value: "number" }],
["bigserial_column", { kind: "type", value: "string" }],
["boolean_column", { kind: "type", value: "boolean" }],
["date_column", { kind: "type", value: "Date" }],
["time_column", { kind: "type", value: "string" }],
["time_with_timezone_column", { kind: "type", value: "string" }],
["timestamp_column", { kind: "type", value: "Date" }],
["timestamp_with_timezone_column", { kind: "type", value: "Date" }],
["interval_column", { kind: "type", value: "string" }],
["uuid_column", { kind: "type", value: "string" }],
["json_column", { kind: "type", value: "any" }],
["jsonb_column", { kind: "type", value: "any" }],
["array_text_column", { kind: "array", value: { kind: "type", value: "string" } }],
["array_int_column", { kind: "array", value: { kind: "type", value: "number" } }],
["bytea_column", { kind: "type", value: "any" }],
["inet_column", { kind: "type", value: "string" }],
["cidr_column", { kind: "type", value: "string" }],
["macaddr_column", { kind: "type", value: "string" }],
["macaddr8_column", { kind: "type", value: "string" }],
["tsvector_column", { kind: "type", value: "unknown" }],
["tsquery_column", { kind: "type", value: "unknown" }],
["xml_column", { kind: "type", value: "unknown" }],
["point_column", { kind: "type", value: "unknown" }],
["line_column", { kind: "type", value: "unknown" }],
["lseg_column", { kind: "type", value: "unknown" }],
["box_column", { kind: "type", value: "unknown" }],
["path_column", { kind: "type", value: "unknown" }],
["polygon_column", { kind: "type", value: "unknown" }],
["circle_column", { kind: "type", value: "unknown" }],
["money_column", { kind: "type", value: "number" }],
["bit_column", { kind: "type", value: "boolean" }],
["bit_varying_column", { kind: "type", value: "unknown" }],
],
});
});

test("camel case field transform", async () => {
await testQuery({
options: { fieldTransform: "camel" },
Expand Down
1 change: 1 addition & 0 deletions packages/test-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./setup-test-database";
export * from "./type-column-ts-type-entries";
45 changes: 45 additions & 0 deletions packages/test-utils/src/type-column-ts-type-entries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export const typeColumnTsTypeEntries = [
["id", "number"],
["text_column", "string"],
["varchar_column", "string"],
["char_column", "string"],
["int_column", "number"],
["smallint_column", "number"],
["bigint_column", "string"],
["decimal_column", "string"],
["numeric_column", "string"],
["real_column", "number"],
["double_column", "number"],
["serial_column", "number"],
["bigserial_column", "string"],
["boolean_column", "boolean"],
["date_column", "Date"],
["time_column", "string"],
["time_with_timezone_column", "string"],
["timestamp_column", "Date"],
["timestamp_with_timezone_column", "Date"],
["interval_column", "string"],
["uuid_column", "string"],
["json_column", "any"],
["jsonb_column", "any"],
["array_text_column", "string[]"],
["array_int_column", "number[]"],
["bytea_column", "any"],
["inet_column", "string"],
["cidr_column", "string"],
["macaddr_column", "string"],
["macaddr8_column", "string"],
["tsvector_column", "unknown"],
["tsquery_column", "unknown"],
["xml_column", "unknown"],
["point_column", "unknown"],
["line_column", "unknown"],
["lseg_column", "unknown"],
["box_column", "unknown"],
["path_column", "unknown"],
["polygon_column", "unknown"],
["circle_column", "unknown"],
["money_column", "number"],
["bit_column", "boolean"],
["bit_varying_column", "unknown"],
] as const;
Loading