diff --git a/package.json b/package.json index 479af70..b1d7a78 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "eslint-plugin-prettier": "3.4.0", "jest": "26.6.3", "np": "7.5.0", - "prettier": "2.2.1", + "prettier": "2.3.0", "testcontainers": "7.11.0", "ts-jest": "26.5.6", "typescript": "4.2.4" diff --git a/src/engine.js b/src/engine.js index b965bb8..caa9a00 100644 --- a/src/engine.js +++ b/src/engine.js @@ -14,24 +14,21 @@ function consoleReporter({ rule, identifier, message }) { let anyIssues = false; const suggestedMigrations = []; -const createReportFunction = (reporter, ignoreMatchers) => ({ - rule, - identifier, - message, - suggestedMigration, -}) => { - if (ignoreMatchers.find((im) => im(rule, identifier))) { - // This one is ignored. - return; - } +const createReportFunction = + (reporter, ignoreMatchers) => + ({ rule, identifier, message, suggestedMigration }) => { + if (ignoreMatchers.find((im) => im(rule, identifier))) { + // This one is ignored. + return; + } - reporter({ rule, identifier, message }); + reporter({ rule, identifier, message }); - if (suggestedMigration) { - suggestedMigrations.push(suggestedMigration); - } - anyIssues = true; -}; + if (suggestedMigration) { + suggestedMigrations.push(suggestedMigration); + } + anyIssues = true; + }; export async function processDatabase({ connection, diff --git a/src/rules/nameCasing.js b/src/rules/nameCasing.js index 80df822..4bccb9c 100644 --- a/src/rules/nameCasing.js +++ b/src/rules/nameCasing.js @@ -4,45 +4,47 @@ export const nameCasing = { name: 'name-casing', docs: { description: 'Enforce casing style of names', - url: - 'https://github.com/kristiandupont/schemalint/tree/master/src/rules#name-casing', + url: 'https://github.com/kristiandupont/schemalint/tree/master/src/rules#name-casing', }, process({ options, schemaObject, report }) { const expectedCasing = (options.length && options[0]) || 'snake'; - const validator = (entityType) => ({ name: entityName }) => { - const casing = detectCasing(entityName); - const matches = casing === null || casing === expectedCasing; - if (!matches) { - report({ - rule: this.name, - identifier: `${schemaObject.name}.${entityName}`, - message: `The ${entityType} ${entityName} seems to be ${casing}-cased rather than ${expectedCasing}-cased.`, - suggestedMigration: `ALTER ${entityType.toUpperCase()} "${entityName}" RENAME TO "${recase( - casing, - expectedCasing, - entityName - )}";`, - }); - } - }; - const columnValidator = (entityType) => ({ name: entityName }) => ({ - name: columnName, - }) => { - const casing = detectCasing(columnName); - const matches = casing === null || casing === expectedCasing; - if (!matches) { - report({ - rule: this.name, - identifier: `${schemaObject.name}.${entityName}.${columnName}`, - message: `The column ${columnName} on the ${entityType} ${entityName} seems to be ${casing}-cased rather than ${expectedCasing}-cased.`, - suggestedMigration: `ALTER ${entityType.toUpperCase()} "${entityName}" RENAME COLUMN "${columnName}" TO "${recase( - casing, - expectedCasing, - columnName - )}";`, - }); - } - }; + const validator = + (entityType) => + ({ name: entityName }) => { + const casing = detectCasing(entityName); + const matches = casing === null || casing === expectedCasing; + if (!matches) { + report({ + rule: this.name, + identifier: `${schemaObject.name}.${entityName}`, + message: `The ${entityType} ${entityName} seems to be ${casing}-cased rather than ${expectedCasing}-cased.`, + suggestedMigration: `ALTER ${entityType.toUpperCase()} "${entityName}" RENAME TO "${recase( + casing, + expectedCasing, + entityName + )}";`, + }); + } + }; + const columnValidator = + (entityType) => + ({ name: entityName }) => + ({ name: columnName }) => { + const casing = detectCasing(columnName); + const matches = casing === null || casing === expectedCasing; + if (!matches) { + report({ + rule: this.name, + identifier: `${schemaObject.name}.${entityName}.${columnName}`, + message: `The column ${columnName} on the ${entityType} ${entityName} seems to be ${casing}-cased rather than ${expectedCasing}-cased.`, + suggestedMigration: `ALTER ${entityType.toUpperCase()} "${entityName}" RENAME COLUMN "${columnName}" TO "${recase( + casing, + expectedCasing, + columnName + )}";`, + }); + } + }; schemaObject.tables.forEach((entity) => { validator('table')(entity); entity.columns.forEach(columnValidator('table')(entity)); diff --git a/src/rules/nameInflection.js b/src/rules/nameInflection.js index e11864c..806c361 100644 --- a/src/rules/nameInflection.js +++ b/src/rules/nameInflection.js @@ -41,23 +41,24 @@ export const nameInflection = { name: 'name-inflection', docs: { description: 'Enforce singluar or plural naming of tables and views', - url: - 'https://github.com/kristiandupont/schemalint/tree/master/src/rules#name-inflection', + url: 'https://github.com/kristiandupont/schemalint/tree/master/src/rules#name-inflection', }, process({ options, schemaObject, report }) { const expectedPlurality = (options.length && options[0]) || 'singular'; - const validator = (entityType) => ({ name: entityName }) => { - const plurality = detectInflection(entityName); - const matches = - plurality === expectedPlurality || plurality === 'unknown'; - if (!matches) { - report({ - rule: this.name, - identifier: `${schemaObject.name}.${entityName}`, - message: `Expected ${expectedPlurality} names, but '${entityName}' seems to be ${plurality}`, - }); - } - }; + const validator = + (entityType) => + ({ name: entityName }) => { + const plurality = detectInflection(entityName); + const matches = + plurality === expectedPlurality || plurality === 'unknown'; + if (!matches) { + report({ + rule: this.name, + identifier: `${schemaObject.name}.${entityName}`, + message: `Expected ${expectedPlurality} names, but '${entityName}' seems to be ${plurality}`, + }); + } + }; schemaObject.tables.forEach(validator('table')); schemaObject.views.forEach(validator('view')); }, diff --git a/src/rules/requirePrimaryKey.js b/src/rules/requirePrimaryKey.js index dc059ea..a47d3b3 100644 --- a/src/rules/requirePrimaryKey.js +++ b/src/rules/requirePrimaryKey.js @@ -2,8 +2,7 @@ export const requirePrimaryKey = { name: 'require-primary-key', docs: { description: 'Enforce primary key definition', - url: - 'https://github.com/kristiandupont/schemalint/tree/master/src/rules#require-primary-key', + url: 'https://github.com/kristiandupont/schemalint/tree/master/src/rules#require-primary-key', }, process({ options, schemaObject, report }) { const ignorePatternsMatch = diff --git a/src/rules/types.js b/src/rules/types.js index 7acbb99..be54ab0 100644 --- a/src/rules/types.js +++ b/src/rules/types.js @@ -5,16 +5,18 @@ export const preferJsonbToJson = { url: '...', }, process({ schemaObject, report }) { - const validator = ({ name: tableName }) => ({ name: columnName, type }) => { - if (type === 'json') { - report({ - rule: this.name, - identifier: `${schemaObject.name}.${tableName}.${columnName}`, - message: 'Prefer JSONB to JSON types', - suggestedMigration: `ALTER TABLE "${schemaObject.name}"."${tableName}" ALTER COLUMN "${columnName}" TYPE JSONB;`, - }); - } - }; + const validator = + ({ name: tableName }) => + ({ name: columnName, type }) => { + if (type === 'json') { + report({ + rule: this.name, + identifier: `${schemaObject.name}.${tableName}.${columnName}`, + message: 'Prefer JSONB to JSON types', + suggestedMigration: `ALTER TABLE "${schemaObject.name}"."${tableName}" ALTER COLUMN "${columnName}" TYPE JSONB;`, + }); + } + }; schemaObject.tables.forEach((table) => table.columns.forEach(validator(table)) ); @@ -28,16 +30,18 @@ export const preferTextToVarchar = { url: '...', }, process({ schemaObject, report }) { - const validator = ({ name: tableName }) => ({ name: columnName, type }) => { - if (type.startsWith('varchar')) { - report({ - rule: this.name, - identifier: `${schemaObject.name}.${tableName}.${columnName}`, - message: `Prefer text to ${type} types`, - suggestedMigration: `ALTER TABLE "${schemaObject.name}"."${tableName}" ALTER COLUMN "${columnName}" TYPE TEXT;`, - }); - } - }; + const validator = + ({ name: tableName }) => + ({ name: columnName, type }) => { + if (type.startsWith('varchar')) { + report({ + rule: this.name, + identifier: `${schemaObject.name}.${tableName}.${columnName}`, + message: `Prefer text to ${type} types`, + suggestedMigration: `ALTER TABLE "${schemaObject.name}"."${tableName}" ALTER COLUMN "${columnName}" TYPE TEXT;`, + }); + } + }; schemaObject.tables.forEach((table) => table.columns.forEach(validator(table)) ); @@ -53,16 +57,18 @@ export const preferTimestamptz = { url: 'https://www.postgresqltutorial.com/postgresql-timestamp/', }, process({ schemaObject, report }) { - const validator = ({ name: tableName }) => ({ name: columnName, type }) => { - if (type === 'timestamp') { - report({ - rule: this.name, - identifier: `${schemaObject.name}.${tableName}.${columnName}`, - message: 'Prefer TIMESTAMPTZ to type TIMESTAMP', - suggestedMigration: `ALTER TABLE "${schemaObject.name}"."${tableName}" ALTER COLUMN "${columnName}" TYPE TIMESTAMPTZ;`, - }); - } - }; + const validator = + ({ name: tableName }) => + ({ name: columnName, type }) => { + if (type === 'timestamp') { + report({ + rule: this.name, + identifier: `${schemaObject.name}.${tableName}.${columnName}`, + message: 'Prefer TIMESTAMPTZ to type TIMESTAMP', + suggestedMigration: `ALTER TABLE "${schemaObject.name}"."${tableName}" ALTER COLUMN "${columnName}" TYPE TIMESTAMPTZ;`, + }); + } + }; schemaObject.tables.forEach((table) => table.columns.forEach(validator(table)) ); @@ -77,30 +83,28 @@ export const preferIdentity = { url: 'https://www.2ndquadrant.com/en/blog/postgresql-10-identity-columns/', }, process({ schemaObject, report }) { - const validator = ({ name: tableName }) => ({ - name: columnName, - rawInfo, - defaultValue, - }) => { - if ( - rawInfo.is_identity === 'NO' && - defaultValue !== null && - defaultValue.indexOf('nextval') >= 0 - ) { - let sequenceName = defaultValue.match("'(.*)'")[1]; - sequenceName = sequenceName.replace(/"/g, ''); + const validator = + ({ name: tableName }) => + ({ name: columnName, rawInfo, defaultValue }) => { + if ( + rawInfo.is_identity === 'NO' && + defaultValue !== null && + defaultValue.indexOf('nextval') >= 0 + ) { + let sequenceName = defaultValue.match("'(.*)'")[1]; + sequenceName = sequenceName.replace(/"/g, ''); - report({ - rule: this.name, - identifier: `${schemaObject.name}.${tableName}.${columnName}`, - message: 'Prefer IDENTITY to type SERIAL', - suggestedMigration: `ALTER TABLE "${schemaObject.name}"."${tableName}" ALTER "${columnName}" DROP DEFAULT; + report({ + rule: this.name, + identifier: `${schemaObject.name}.${tableName}.${columnName}`, + message: 'Prefer IDENTITY to type SERIAL', + suggestedMigration: `ALTER TABLE "${schemaObject.name}"."${tableName}" ALTER "${columnName}" DROP DEFAULT; DROP SEQUENCE "schema"."${sequenceName}"; ALTER TABLE "${schemaObject.name}"."${tableName}" ALTER "${columnName}" ADD GENERATED BY DEFAULT AS IDENTITY; SELECT setval('"${sequenceName}"', max("${columnName}")) FROM "schema"."${tableName}";`, - }); - } - }; + }); + } + }; schemaObject.tables.forEach((table) => table.columns.forEach(validator(table)) ); diff --git a/yarn.lock b/yarn.lock index 158a190..5087ffb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4898,10 +4898,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" - integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== +prettier@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18" + integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== pretty-format@^26.0.0, pretty-format@^26.6.2: version "26.6.2"