From 8038561df2e7a54e58a9f2112141f9213cbbfa54 Mon Sep 17 00:00:00 2001 From: orangain Date: Fri, 9 Feb 2024 22:38:54 +0900 Subject: [PATCH 01/13] npm i --save-dev @types/pg --- package-lock.json | 90 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 91 insertions(+) diff --git a/package-lock.json b/package-lock.json index 967af1d..ab95b13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ }, "devDependencies": { "@kristiandupont/dev-deps": "^2.20.0", + "@types/pg": "^8.11.0", "@types/ramda": "0.29.10", "np": "9.2.0", "testcontainers": "10.7.1" @@ -1727,6 +1728,74 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, + "node_modules/@types/pg": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.0.tgz", + "integrity": "sha512-sDAlRiBNthGjNFfvt0k6mtotoVYVQ63pA8R4EMWka7crawSR60waVYR0HAgmPRs/e2YaeJTD/43OoZ3PFw80pw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, + "node_modules/@types/pg/node_modules/pg-types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz", + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "dev": true, + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/pg/node_modules/postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dev": true, + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/pg/node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@types/ramda": { "version": "0.29.10", "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.29.10.tgz", @@ -8293,6 +8362,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, "node_modules/once": { "version": "1.4.0", "dev": true, @@ -8966,6 +9041,15 @@ "node": ">=4.0.0" } }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/pg-pool": { "version": "3.6.1", "license": "MIT", @@ -9249,6 +9333,12 @@ "node": ">=0.10.0" } }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.1.2", "engines": { diff --git a/package.json b/package.json index 2401179..3093bc8 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@kristiandupont/dev-deps": "^2.20.0", + "@types/pg": "^8.11.0", "@types/ramda": "0.29.10", "np": "9.2.0", "testcontainers": "10.7.1" From 9f930b692901ad27b070f8a641d96311dc3052a4 Mon Sep 17 00:00:00 2001 From: orangain Date: Fri, 9 Feb 2024 22:51:24 +0900 Subject: [PATCH 02/13] Convert rules to typescript --- src/rules/{index.js => index.ts} | 0 src/rules/{requirePrimaryKey.js => requirePrimaryKey.ts} | 9 +++++---- 2 files changed, 5 insertions(+), 4 deletions(-) rename src/rules/{index.js => index.ts} (100%) rename src/rules/{requirePrimaryKey.js => requirePrimaryKey.ts} (84%) diff --git a/src/rules/index.js b/src/rules/index.ts similarity index 100% rename from src/rules/index.js rename to src/rules/index.ts diff --git a/src/rules/requirePrimaryKey.js b/src/rules/requirePrimaryKey.ts similarity index 84% rename from src/rules/requirePrimaryKey.js rename to src/rules/requirePrimaryKey.ts index bb31331..0aceb9b 100644 --- a/src/rules/requirePrimaryKey.js +++ b/src/rules/requirePrimaryKey.ts @@ -1,7 +1,8 @@ -/** @typedef {import('../Rule').default} Rule */ +import { TableDetails } from "extract-pg-schema"; -/** @type {Rule} */ -export const requirePrimaryKey = { +import Rule from "../Rule"; + +export const requirePrimaryKey: Rule = { name: "require-primary-key", docs: { description: "Enforce primary key definition", @@ -12,7 +13,7 @@ export const requirePrimaryKey = { options[0] && options[0].ignorePattern ? new RegExp(options[0].ignorePattern) : null; - const validator = ({ columns, name: tableName }) => { + const validator = ({ columns, name: tableName }: TableDetails) => { const idColumns = columns.filter((c) => c.isPrimaryKey); if (idColumns.length === 0) { From 2e8e8c8f866ead78869d7e2426332b08c1158aa6 Mon Sep 17 00:00:00 2001 From: orangain Date: Fri, 9 Feb 2024 22:52:09 +0900 Subject: [PATCH 03/13] Convert engine to typescript --- src/Config.ts | 76 ++++++++++++++++++++++++++++++++++++ src/Rule.ts | 6 ++- src/{engine.js => engine.ts} | 73 ++++++++++++++++++---------------- 3 files changed, 120 insertions(+), 35 deletions(-) create mode 100644 src/Config.ts rename src/{engine.js => engine.ts} (64%) diff --git a/src/Config.ts b/src/Config.ts new file mode 100644 index 0000000..f48ab1a --- /dev/null +++ b/src/Config.ts @@ -0,0 +1,76 @@ +import { ClientConfig } from "pg"; + +/** + * The configuration for schemalint. + */ +type Config = { + /** + * The connection configuration for the database. + * @see https://node-postgres.com/apis/client + */ + connection: ClientConfig; + /** + * The plugins to be used. + */ + plugins?: string[]; + /** + * The rules to be used. + */ + rules: Record; + /** + * The schemas to be linted. + */ + schemas: SchemaConfig[]; + /** + * The configuration for ignoring problems. + */ + ignores?: IgnoreConfig[]; +}; + +/** + * A schema to be linted. + */ +export type SchemaConfig = { + /** + * The name of the schema to be linted. + */ + name: string; + /** + * The rules to be used spefically for this schema. These rules will be merged with the global rules. + */ + rules?: Record; +}; + +/** + * A rule configuration. The first element is the severity of the rule, and the rest of the elements are the options for the rule. + */ +export type RuleConfig = [Severity, ...unknown[]]; + +/** + * The severity of a rule. `off` means the rule is disabled, `error` means the rule is enabled. + */ +export type Severity = "off" | "error"; + +/** + * A configuration for ignoring problems. + */ +export type IgnoreConfig = { + /** + * The rule name to ignore. `rule` or `rulePattern` must be provided. + */ + rule?: string; + /** + * A pattern to match against the rule name. `rule` or `rulePattern` must be provided. + */ + rulePattern?: string; + /** + * The identifier to ignore. `identifier` or `identifierPattern` must be provided. + */ + identifier?: string; + /** + * A pattern to match against the identifier. `identifier` or `identifierPattern` must be provided. + */ + identifierPattern?: string; +}; + +export default Config; diff --git a/src/Rule.ts b/src/Rule.ts index 70c57fe..ab73713 100644 --- a/src/Rule.ts +++ b/src/Rule.ts @@ -1,11 +1,13 @@ import { Schema } from "extract-pg-schema"; -export type Reporter = (p: { +export type Issue = { rule: string; identifier: string; message: string; suggestedMigration?: string; -}) => void; +}; + +export type Reporter = (p: Issue) => void; type Rule = { name: string; diff --git a/src/engine.js b/src/engine.ts similarity index 64% rename from src/engine.js rename to src/engine.ts index b2e2e69..c423530 100644 --- a/src/engine.js +++ b/src/engine.ts @@ -3,19 +3,23 @@ import { extractSchemas } from "extract-pg-schema"; import path from "path"; import { indexBy, keys, prop, values } from "ramda"; +import Config from "./Config"; +import { Issue, Reporter } from "./Rule"; import * as builtinRules from "./rules"; -function consoleReporter({ rule, identifier, message }) { +type IgnoreMatcher = (rule: string, identifier: string) => boolean; + +function consoleReporter({ rule, identifier, message }: Issue) { console.error( `${chalk.yellow(identifier)}: error ${chalk.red(rule)} : ${message}`, ); } let anyIssues = false; -const suggestedMigrations = []; +const suggestedMigrations: string[] = []; const createReportFunction = - (reporter, ignoreMatchers) => + (reporter: Reporter, ignoreMatchers: IgnoreMatcher[]): Reporter => ({ rule, identifier, message, suggestedMigration }) => { if (ignoreMatchers.some((im) => im(rule, identifier))) { // This one is ignored. @@ -36,7 +40,7 @@ export async function processDatabase({ rules, schemas, ignores = [], -}) { +}: Config): Promise { const pluginRules = plugins.map((p) => require(path.join(process.cwd(), p))); const allRules = [builtinRules, ...pluginRules].reduce( (acc, elem) => ({ ...acc, ...elem }), @@ -50,35 +54,38 @@ export async function processDatabase({ }`, ); - const ignoreMatchers = ignores.map((i) => (rule, identifier) => { - let ruleMatch; - if (i.rule) { - ruleMatch = rule === i.rule; - } else if (i.rulePattern) { - ruleMatch = new RegExp(i.rulePattern).test(rule); - } else { - throw new Error( - `Ignore object is missing a rule or rulePattern property: ${JSON.stringify( - i, - )}`, - ); - } - - let identifierMatch; - if (i.identifier) { - identifierMatch = identifier === i.identifier; - } else if (i.identifierPattern) { - identifierMatch = new RegExp(i.identifierPattern).test(identifier); - } else { - throw new Error( - `Ignore object is missing an identifier or identifierPattern property: ${JSON.stringify( - i, - )}`, - ); - } - - return ruleMatch && identifierMatch; - }); + const ignoreMatchers = ignores.map( + (i): IgnoreMatcher => + (rule, identifier) => { + let ruleMatch; + if (i.rule) { + ruleMatch = rule === i.rule; + } else if (i.rulePattern) { + ruleMatch = new RegExp(i.rulePattern).test(rule); + } else { + throw new Error( + `Ignore object is missing a rule or rulePattern property: ${JSON.stringify( + i, + )}`, + ); + } + + let identifierMatch; + if (i.identifier) { + identifierMatch = identifier === i.identifier; + } else if (i.identifierPattern) { + identifierMatch = new RegExp(i.identifierPattern).test(identifier); + } else { + throw new Error( + `Ignore object is missing an identifier or identifierPattern property: ${JSON.stringify( + i, + )}`, + ); + } + + return ruleMatch && identifierMatch; + }, + ); const report = createReportFunction(consoleReporter, ignoreMatchers); const extractedSchemas = await extractSchemas(connection, { From c1ad3ce04c5384e98a482a82a355f325c84b861c Mon Sep 17 00:00:00 2001 From: orangain Date: Fri, 9 Feb 2024 22:52:28 +0900 Subject: [PATCH 04/13] Convert cli to typescript --- src/{cli.js => cli.ts} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename src/{cli.js => cli.ts} (90%) diff --git a/src/cli.js b/src/cli.ts similarity index 90% rename from src/cli.js rename to src/cli.ts index 63bf092..689faee 100644 --- a/src/cli.js +++ b/src/cli.ts @@ -4,7 +4,7 @@ import optionator from "optionator"; import path from "path"; import { processDatabase } from "./engine"; -// @ts-ignore +// eslint-disable-next-line @typescript-eslint/no-var-requires const { version } = require("../package.json"); async function main() { @@ -38,7 +38,7 @@ async function main() { try { options = o.parseArgv(process.argv); - } catch (error) { + } catch (error: any) { console.error(error.message); process.exit(1); } @@ -60,6 +60,7 @@ async function main() { ); try { + // eslint-disable-next-line @typescript-eslint/no-var-requires const config = require(configFile); const exitCode = await processDatabase(config); process.exit(exitCode); From 438de849a2557114273f139b45a14daa23988fec Mon Sep 17 00:00:00 2001 From: orangain Date: Sat, 10 Feb 2024 23:36:52 +0900 Subject: [PATCH 05/13] Move DeepPartial to dedicated file to be used from multiple files --- src/rules/types.test.ts | 7 +------ src/tests/DeepPartial.ts | 7 +++++++ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 src/tests/DeepPartial.ts diff --git a/src/rules/types.test.ts b/src/rules/types.test.ts index 812b16a..4f269e2 100644 --- a/src/rules/types.test.ts +++ b/src/rules/types.test.ts @@ -2,14 +2,9 @@ import { Schema } from "extract-pg-schema"; import { describe, expect, it, vi } from "vitest"; import { Reporter } from "../Rule"; +import DeepPartial from "../tests/DeepPartial"; import * as types from "./types"; -type DeepPartial = T extends object - ? { - [P in keyof T]?: DeepPartial; - } - : T; - const assertReport = ( mockReporter: Reporter, expectedRule: string, diff --git a/src/tests/DeepPartial.ts b/src/tests/DeepPartial.ts new file mode 100644 index 0000000..7479671 --- /dev/null +++ b/src/tests/DeepPartial.ts @@ -0,0 +1,7 @@ +type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; + +export default DeepPartial; From 1279ae61626c563853b5ff2d1ea8bc810606a900 Mon Sep 17 00:00:00 2001 From: orangain Date: Sat, 10 Feb 2024 23:44:59 +0900 Subject: [PATCH 06/13] Convert test files to typescript --- ...{nameCasing.test.js => nameCasing.test.ts} | 109 +++++++++--------- ...lection.test.js => nameInflection.test.ts} | 45 ++++---- 2 files changed, 80 insertions(+), 74 deletions(-) rename src/rules/{nameCasing.test.js => nameCasing.test.ts} (81%) rename src/rules/{nameInflection.test.js => nameInflection.test.ts} (81%) diff --git a/src/rules/nameCasing.test.js b/src/rules/nameCasing.test.ts similarity index 81% rename from src/rules/nameCasing.test.js rename to src/rules/nameCasing.test.ts index 5052deb..d6d6e46 100644 --- a/src/rules/nameCasing.test.js +++ b/src/rules/nameCasing.test.ts @@ -1,17 +1,20 @@ +import { Schema } from "extract-pg-schema"; import { describe, expect, it, test, vi } from "vitest"; +import DeepPartial from "../tests/DeepPartial"; import { nameCasing } from "./nameCasing"; describe("nameCasing", () => { it("no tables or views passed no errors", () => { const mockReporter = vi.fn(); + const schemaObject: DeepPartial = { + tables: [], + views: [], + }; nameCasing.process({ options: [], - schemaObject: { - tables: [], - views: [], - }, + schemaObject: schemaObject as Schema, report: mockReporter, }); @@ -28,17 +31,18 @@ describe("nameCasing", () => { "$type : param of $param applies to table names and requires $expected", ({ _type, param, expected1, expected2 }) => { const mockReporter = vi.fn(); + const schemaObject: DeepPartial = { + name: "schema", + tables: [ + { name: "Th-IsIsNoSnA_kECaSE", columns: [] }, + { name: "neit_he-rIsThis", columns: [] }, + ], + views: [], + }; nameCasing.process({ options: [param], - schemaObject: { - name: "schema", - tables: [ - { name: "Th-IsIsNoSnA_kECaSE", columns: [] }, - { name: "neit_he-rIsThis", columns: [] }, - ], - views: [], - }, + schemaObject: schemaObject as Schema, report: mockReporter, }); @@ -77,17 +81,18 @@ describe("nameCasing", () => { "$type : param of $param applies to view names and requires $expected", ({ _type, param, expected1, expected2 }) => { const mockReporter = vi.fn(); + const schemaObject: DeepPartial = { + name: "schema", + tables: [], + views: [ + { name: "Th-IsIsNoSnA_kECaSE", columns: [] }, + { name: "neit_he-rIsThis", columns: [] }, + ], + }; nameCasing.process({ options: [param], - schemaObject: { - name: "schema", - tables: [], - views: [ - { name: "Th-IsIsNoSnA_kECaSE", columns: [] }, - { name: "neit_he-rIsThis", columns: [] }, - ], - }, + schemaObject: schemaObject as Schema, report: mockReporter, }); @@ -126,26 +131,24 @@ describe("nameCasing", () => { "$type : param of $param applies to view names and requires $expected", ({ _type, param, expected1, expected2 }) => { const mockReporter = vi.fn(); + const schemaObject: DeepPartial = { + name: "schema", + tables: [ + { + name: "one_table", + columns: [{ name: "Th-IsIsNoSnA_kECaSE" }, { name: "snake_case" }], + }, + { + name: "two_table", + columns: [{ name: "neit_he-rIsThis" }, { name: "snake_case" }], + }, + ], + views: [], + }; nameCasing.process({ options: [param], - schemaObject: { - name: "schema", - tables: [ - { - name: "one_table", - columns: [ - { name: "Th-IsIsNoSnA_kECaSE" }, - { name: "snake_case" }, - ], - }, - { - name: "two_table", - columns: [{ name: "neit_he-rIsThis" }, { name: "snake_case" }], - }, - ], - views: [], - }, + schemaObject: schemaObject as Schema, report: mockReporter, }); @@ -183,26 +186,24 @@ describe("nameCasing", () => { "$type : param of $param applies to view names and requires $expected", ({ _type, param, expected1, expected2 }) => { const mockReporter = vi.fn(); + const schemaObject: DeepPartial = { + name: "schema", + tables: [], + views: [ + { + name: "one_view", + columns: [{ name: "Th-IsIsNoSnA_kECaSE" }, { name: "snake_case" }], + }, + { + name: "two_view", + columns: [{ name: "neit_he-rIsThis" }, { name: "snake_case" }], + }, + ], + }; nameCasing.process({ options: [param], - schemaObject: { - name: "schema", - views: [ - { - name: "one_view", - columns: [ - { name: "Th-IsIsNoSnA_kECaSE" }, - { name: "snake_case" }, - ], - }, - { - name: "two_view", - columns: [{ name: "neit_he-rIsThis" }, { name: "snake_case" }], - }, - ], - tables: [], - }, + schemaObject: schemaObject as Schema, report: mockReporter, }); diff --git a/src/rules/nameInflection.test.js b/src/rules/nameInflection.test.ts similarity index 81% rename from src/rules/nameInflection.test.js rename to src/rules/nameInflection.test.ts index ce15b81..6d9af89 100644 --- a/src/rules/nameInflection.test.js +++ b/src/rules/nameInflection.test.ts @@ -1,17 +1,20 @@ +import { Schema } from "extract-pg-schema"; import { describe, expect, it, test, vi } from "vitest"; +import DeepPartial from "../tests/DeepPartial"; import { nameInflection } from "./nameInflection"; describe("nameInflection", () => { it("no tables or views passed no errors", () => { const mockReporter = vi.fn(); + const schemaObject: DeepPartial = { + tables: [], + views: [], + }; nameInflection.process({ options: [], - schemaObject: { - tables: [], - views: [], - }, + schemaObject: schemaObject as Schema, report: mockReporter, }); @@ -27,17 +30,18 @@ describe("nameInflection", () => { "$type : param of $param applies to table names and requires $expected", ({ param, actual1, actual2, _expected1, _expected2 }) => { const mockReporter = vi.fn(); + const schemaObject: DeepPartial = { + name: "schema", + tables: [ + { name: `${actual1}`, columns: [] }, + { name: `${actual2}`, columns: [] }, + ], + views: [], + }; nameInflection.process({ options: [param], - schemaObject: { - name: "schema", - tables: [ - { name: `${actual1}`, columns: [] }, - { name: `${actual2}`, columns: [] }, - ], - views: [], - }, + schemaObject: schemaObject as Schema, report: mockReporter, }); @@ -72,17 +76,18 @@ describe("nameInflection", () => { "$type : param of $param applies to view names and requires $expected", ({ param, actual1, actual2, _expected1, _expected2 }) => { const mockReporter = vi.fn(); + const schemaObject: DeepPartial = { + name: "schema", + views: [ + { name: `${actual1}`, columns: [] }, + { name: `${actual2}`, columns: [] }, + ], + tables: [], + }; nameInflection.process({ options: [param], - schemaObject: { - name: "schema", - views: [ - { name: `${actual1}`, columns: [] }, - { name: `${actual2}`, columns: [] }, - ], - tables: [], - }, + schemaObject: schemaObject as Schema, report: mockReporter, }); From 13671de6fda40285559c79c383f4d06de5e60444 Mon Sep 17 00:00:00 2001 From: orangain Date: Sun, 11 Feb 2024 11:36:09 +0900 Subject: [PATCH 07/13] Replace test.each`...` with test.each([...]) --- src/rules/nameCasing.test.ts | 86 +++++++++++++++++--------------- src/rules/nameInflection.test.ts | 48 ++++++++++++------ 2 files changed, 78 insertions(+), 56 deletions(-) diff --git a/src/rules/nameCasing.test.ts b/src/rules/nameCasing.test.ts index d6d6e46..0c0e9a7 100644 --- a/src/rules/nameCasing.test.ts +++ b/src/rules/nameCasing.test.ts @@ -20,16 +20,43 @@ describe("nameCasing", () => { expect(mockReporter).toBeCalledTimes(0); }); - test.each` - type | param | expected1 | expected2 - ${`default`} | ${null} | ${`th_is_is_no_sn_a_k_e_ca_s_e`} | ${`neit_he_r_is_this`} - ${`snake-case`} | ${`snake`} | ${`th_is_is_no_sn_a_k_e_ca_s_e`} | ${`neit_he_r_is_this`} - ${`dash case`} | ${`dash`} | ${`th-is-is-no-sn-a-k-e-ca-s-e`} | ${`neit-he-r-is-this`} - ${`camel case`} | ${`camel`} | ${`thIsIsNoSnAKECaSE`} | ${`neitHeRIsThis`} - ${`pascal case`} | ${`pascal`} | ${`ThIsIsNoSnAKECaSE`} | ${`NeitHeRIsThis`} - `( - "$type : param of $param applies to table names and requires $expected", - ({ _type, param, expected1, expected2 }) => { + + const testCases = [ + { + type: "default", + param: null, + expected1: "th_is_is_no_sn_a_k_e_ca_s_e", + expected2: "neit_he_r_is_this", + }, + { + type: "snake-case", + param: "snake", + expected1: "th_is_is_no_sn_a_k_e_ca_s_e", + expected2: "neit_he_r_is_this", + }, + { + type: "dash case", + param: "dash", + expected1: "th-is-is-no-sn-a-k-e-ca-s-e", + expected2: "neit-he-r-is-this", + }, + { + type: "camel case", + param: "camel", + expected1: "thIsIsNoSnAKECaSE", + expected2: "neitHeRIsThis", + }, + { + type: "pascal case", + param: "pascal", + expected1: "ThIsIsNoSnAKECaSE", + expected2: "NeitHeRIsThis", + }, + ]; + + test.each(testCases)( + "$type : param of $param applies to table names and requires $expected1 and $expected2", + ({ param, expected1, expected2 }) => { const mockReporter = vi.fn(); const schemaObject: DeepPartial = { name: "schema", @@ -70,16 +97,9 @@ describe("nameCasing", () => { }, ); - test.each` - type | param | expected1 | expected2 - ${`default`} | ${null} | ${`th_is_is_no_sn_a_k_e_ca_s_e`} | ${`neit_he_r_is_this`} - ${`snake-case`} | ${`snake`} | ${`th_is_is_no_sn_a_k_e_ca_s_e`} | ${`neit_he_r_is_this`} - ${`dash case`} | ${`dash`} | ${`th-is-is-no-sn-a-k-e-ca-s-e`} | ${`neit-he-r-is-this`} - ${`camel case`} | ${`camel`} | ${`thIsIsNoSnAKECaSE`} | ${`neitHeRIsThis`} - ${`pascal case`} | ${`pascal`} | ${`ThIsIsNoSnAKECaSE`} | ${`NeitHeRIsThis`} - `( - "$type : param of $param applies to view names and requires $expected", - ({ _type, param, expected1, expected2 }) => { + test.each(testCases)( + "$type : param of $param applies to view names and requires $expected1 and $expected2", + ({ param, expected1, expected2 }) => { const mockReporter = vi.fn(); const schemaObject: DeepPartial = { name: "schema", @@ -120,16 +140,9 @@ describe("nameCasing", () => { }, ); - test.each` - type | param | expected1 | expected2 - ${`default`} | ${null} | ${`th_is_is_no_sn_a_k_e_ca_s_e`} | ${`neit_he_r_is_this`} - ${`snake-case`} | ${`snake`} | ${`th_is_is_no_sn_a_k_e_ca_s_e`} | ${`neit_he_r_is_this`} - ${`dash case`} | ${`dash`} | ${`th-is-is-no-sn-a-k-e-ca-s-e`} | ${`neit-he-r-is-this`} - ${`camel case`} | ${`camel`} | ${`thIsIsNoSnAKECaSE`} | ${`neitHeRIsThis`} - ${`pascal case`} | ${`pascal`} | ${`ThIsIsNoSnAKECaSE`} | ${`NeitHeRIsThis`} - `( - "$type : param of $param applies to view names and requires $expected", - ({ _type, param, expected1, expected2 }) => { + test.each(testCases)( + "$type : param of $param applies to view names and requires $expected1 and $expected2", + ({ param, expected1, expected2 }) => { const mockReporter = vi.fn(); const schemaObject: DeepPartial = { name: "schema", @@ -175,16 +188,9 @@ describe("nameCasing", () => { }, ); - test.each` - type | param | expected1 | expected2 - ${`default`} | ${null} | ${`th_is_is_no_sn_a_k_e_ca_s_e`} | ${`neit_he_r_is_this`} - ${`snake-case`} | ${`snake`} | ${`th_is_is_no_sn_a_k_e_ca_s_e`} | ${`neit_he_r_is_this`} - ${`dash case`} | ${`dash`} | ${`th-is-is-no-sn-a-k-e-ca-s-e`} | ${`neit-he-r-is-this`} - ${`camel case`} | ${`camel`} | ${`thIsIsNoSnAKECaSE`} | ${`neitHeRIsThis`} - ${`pascal case`} | ${`pascal`} | ${`ThIsIsNoSnAKECaSE`} | ${`NeitHeRIsThis`} - `( - "$type : param of $param applies to view names and requires $expected", - ({ _type, param, expected1, expected2 }) => { + test.each(testCases)( + "$type : param of $param applies to view names and requires $expected1 and $expected2", + ({ param, expected1, expected2 }) => { const mockReporter = vi.fn(); const schemaObject: DeepPartial = { name: "schema", diff --git a/src/rules/nameInflection.test.ts b/src/rules/nameInflection.test.ts index 6d9af89..18ac82e 100644 --- a/src/rules/nameInflection.test.ts +++ b/src/rules/nameInflection.test.ts @@ -21,14 +21,35 @@ describe("nameInflection", () => { expect(mockReporter).toBeCalledTimes(0); }); - test.each` - type | param | actual1 | actual 2 | expected1 | expected2 - ${`default`} | ${null} | ${`one_wives`} | ${`two_jims`} | ${`one_wife`} | ${`two_jim`} - ${`singular`} | ${`singular`} | ${`one_wives`} | ${`two_jims`} | ${`one_wife`} | ${`two_jim`} - ${`plural`} | ${`plural`} | ${`one_wife`} | ${`two_jim`} | ${`one_wives`} | ${`two_jims`} - `( - "$type : param of $param applies to table names and requires $expected", - ({ param, actual1, actual2, _expected1, _expected2 }) => { + const testCases = [ + { + type: "default", + param: null, + actual1: "one_wives", + actual2: "two_jims", + expected1: "one_wife", + expected2: "two_jim", + }, + { + type: "singular", + param: "singular", + actual1: "one_wives", + actual2: "two_jims", + expected1: "one_wife", + expected2: "two_jim", + }, + { + type: "plural", + param: "plural", + actual1: "one_wife", + actual2: "two_jim", + expected1: "one_wives", + expected2: "two_jims", + }, + ]; + test.each(testCases)( + "$type : param of $param applies to table names and requires $expected1 and $expected2", + ({ param, actual1, actual2 }) => { const mockReporter = vi.fn(); const schemaObject: DeepPartial = { name: "schema", @@ -67,14 +88,9 @@ describe("nameInflection", () => { ); }, ); - test.each` - type | param | actual1 | actual 2 | expected1 | expected2 - ${`default`} | ${null} | ${`one_wives`} | ${`two_jims`} | ${`one_wife`} | ${`two_jim`} - ${`singular`} | ${`singular`} | ${`one_wives`} | ${`two_jims`} | ${`one_wife`} | ${`two_jim`} - ${`plural`} | ${`plural`} | ${`one_wife`} | ${`two_jim`} | ${`one_wives`} | ${`two_jims`} - `( - "$type : param of $param applies to view names and requires $expected", - ({ param, actual1, actual2, _expected1, _expected2 }) => { + test.each(testCases)( + "$type : param of $param applies to view names and requires $expected1 and $expected2", + ({ param, actual1, actual2 }) => { const mockReporter = vi.fn(); const schemaObject: DeepPartial = { name: "schema", From b5090300871cd55a362802d61fbd0a4ee6469f19 Mon Sep 17 00:00:00 2001 From: orangain Date: Sun, 11 Feb 2024 11:39:13 +0900 Subject: [PATCH 08/13] Fix test names --- src/rules/nameCasing.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rules/nameCasing.test.ts b/src/rules/nameCasing.test.ts index 0c0e9a7..2ce2117 100644 --- a/src/rules/nameCasing.test.ts +++ b/src/rules/nameCasing.test.ts @@ -141,7 +141,7 @@ describe("nameCasing", () => { ); test.each(testCases)( - "$type : param of $param applies to view names and requires $expected1 and $expected2", + "$type : param of $param applies to table column names and requires $expected1 and $expected2", ({ param, expected1, expected2 }) => { const mockReporter = vi.fn(); const schemaObject: DeepPartial = { @@ -189,7 +189,7 @@ describe("nameCasing", () => { ); test.each(testCases)( - "$type : param of $param applies to view names and requires $expected1 and $expected2", + "$type : param of $param applies to view column names and requires $expected1 and $expected2", ({ param, expected1, expected2 }) => { const mockReporter = vi.fn(); const schemaObject: DeepPartial = { From 4e5574ec701fcc2f10b3ff0832cb8bf82e717ae5 Mon Sep 17 00:00:00 2001 From: orangain Date: Sat, 10 Feb 2024 23:25:27 +0900 Subject: [PATCH 09/13] Introduce index.ts to export major types from package root --- package.json | 4 ++-- src/index.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 src/index.ts diff --git a/package.json b/package.json index 3093bc8..46a6c4a 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "name": "Kristian Dupont", "url": "http://www.kristiandupont.com" }, - "main": "build/engine.js", - "types": "build/engine.d.ts", + "main": "build/index.js", + "types": "build/index.d.ts", "bin": { "schemalint": "./bin/schemalint" }, diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d4310eb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export type { default as Config } from "./Config"; +export * from "./engine"; +export type { default as Rule } from "./Rule"; From b4233a9bbd82030e2300820a7bee9d79977e96a7 Mon Sep 17 00:00:00 2001 From: orangain Date: Sun, 11 Feb 2024 13:10:51 +0900 Subject: [PATCH 10/13] Add type assertion --- src/engine.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/engine.ts b/src/engine.ts index c423530..defe8b2 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -4,7 +4,7 @@ import path from "path"; import { indexBy, keys, prop, values } from "ramda"; import Config from "./Config"; -import { Issue, Reporter } from "./Rule"; +import Rule, { Issue, Reporter } from "./Rule"; import * as builtinRules from "./rules"; type IgnoreMatcher = (rule: string, identifier: string) => boolean; @@ -41,7 +41,10 @@ export async function processDatabase({ schemas, ignores = [], }: Config): Promise { - const pluginRules = plugins.map((p) => require(path.join(process.cwd(), p))); + const pluginRules = plugins.map( + // eslint-disable-next-line @typescript-eslint/no-var-requires + (p) => require(path.join(process.cwd(), p)) as Record, + ); const allRules = [builtinRules, ...pluginRules].reduce( (acc, elem) => ({ ...acc, ...elem }), {}, From 38b20779e89f67a0c0fce6da091c5e12d755e2e2 Mon Sep 17 00:00:00 2001 From: orangain Date: Sun, 11 Feb 2024 13:13:30 +0900 Subject: [PATCH 11/13] No longer allow js --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index e1fb8f3..574bd04 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, // "lib": [], /* Specify library files to be included in the compilation. */ - "allowJs": true /* Allow javascript files to be compiled. */, + // "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true /* Report errors in .js files. */, // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "declaration": true /* Generates corresponding '.d.ts' file. */, From 36f28d151470d4b5da0121506f9c4fd6e88a3099 Mon Sep 17 00:00:00 2001 From: orangain Date: Sun, 11 Feb 2024 13:37:25 +0900 Subject: [PATCH 12/13] Specify types --- src/cli.ts | 2 +- src/engine.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 689faee..4d2a361 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -34,7 +34,7 @@ async function main() { ], }); - let options; + let options: any; try { options = o.parseArgv(process.argv); diff --git a/src/engine.ts b/src/engine.ts index defe8b2..eb098c7 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -60,7 +60,7 @@ export async function processDatabase({ const ignoreMatchers = ignores.map( (i): IgnoreMatcher => (rule, identifier) => { - let ruleMatch; + let ruleMatch: boolean; if (i.rule) { ruleMatch = rule === i.rule; } else if (i.rulePattern) { @@ -73,7 +73,7 @@ export async function processDatabase({ ); } - let identifierMatch; + let identifierMatch: boolean; if (i.identifier) { identifierMatch = identifier === i.identifier; } else if (i.identifierPattern) { From ad5cd3c6709becf3f2514846453f6b939cdf0f4e Mon Sep 17 00:00:00 2001 From: orangain Date: Sun, 11 Feb 2024 13:45:02 +0900 Subject: [PATCH 13/13] Use import instead of require --- src/cli.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 4d2a361..3f9e029 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,9 +3,8 @@ import chalk from "chalk"; import optionator from "optionator"; import path from "path"; +import { version } from "../package.json"; import { processDatabase } from "./engine"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { version } = require("../package.json"); async function main() { const o = optionator({