diff --git a/.changeset/clever-elephants-play.md b/.changeset/clever-elephants-play.md new file mode 100644 index 00000000..2a64e943 --- /dev/null +++ b/.changeset/clever-elephants-play.md @@ -0,0 +1,5 @@ +--- +"@ts-safeql/eslint-plugin": patch +--- + +fixed an issue where type reference was inferred incorrectly diff --git a/packages/eslint-plugin/src/rules/check-sql.test.ts b/packages/eslint-plugin/src/rules/check-sql.test.ts index bbb756e8..97e66b7f 100644 --- a/packages/eslint-plugin/src/rules/check-sql.test.ts +++ b/packages/eslint-plugin/src/rules/check-sql.test.ts @@ -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"; @@ -66,6 +70,52 @@ const runMigrations1 = >(sql: Sql 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; @@ -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\`select ${colName} from all_types\` + `, + })), + invalid: [], + }); + ruleTester.run("base with transform", rules["check-sql"], { valid: [ { diff --git a/packages/eslint-plugin/src/utils/get-resolved-target-by-type-node.ts b/packages/eslint-plugin/src/utils/get-resolved-target-by-type-node.ts index afd354d2..cb47b19b 100644 --- a/packages/eslint-plugin/src/utils/get-resolved-target-by-type-node.ts +++ b/packages/eslint-plugin/src/utils/get-resolved-target-by-type-node.ts @@ -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()) { diff --git a/packages/generate/src/generate.test.ts b/packages/generate/src/generate.test.ts index 50d27135..7754143d 100644 --- a/packages/generate/src/generate.test.ts +++ b/packages/generate/src/generate.test.ts @@ -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 + ); `); } @@ -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" }, diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts index d5bb63cc..a94e9fa6 100644 --- a/packages/test-utils/src/index.ts +++ b/packages/test-utils/src/index.ts @@ -1 +1,2 @@ export * from "./setup-test-database"; +export * from "./type-column-ts-type-entries"; diff --git a/packages/test-utils/src/type-column-ts-type-entries.ts b/packages/test-utils/src/type-column-ts-type-entries.ts new file mode 100644 index 00000000..a0527a90 --- /dev/null +++ b/packages/test-utils/src/type-column-ts-type-entries.ts @@ -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;