Skip to content

Commit

Permalink
feat: add no-column-migration-without-comment
Browse files Browse the repository at this point in the history
Co-authored-by: Mathieu Gilet <mathieu.gilet@pix.fr>
  • Loading branch information
VincentHardouin and MathieuGilet committed Jul 16, 2024
1 parent 445112d commit bd02646
Show file tree
Hide file tree
Showing 4 changed files with 336 additions and 1 deletion.
1 change: 1 addition & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ module.exports = [
},
],
'@1024pix/no-sinon-stub-with-args-oneliner': 'error',
'@1024pix/no-column-migration-without-comment': 'error',
},
},
];
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';
const noSinonStubWithArgsOneliner = require('./rules/no-sinon-stub-with-args-oneliner.js');
const noColumnMigrationWithoutComment = require('./rules/no-column-migration-without-comment.js');

module.exports = {
rules: { 'no-sinon-stub-with-args-oneliner': noSinonStubWithArgsOneliner },
rules: { 'no-sinon-stub-with-args-oneliner': noSinonStubWithArgsOneliner, 'no-column-migration-without-comment': noColumnMigrationWithoutComment },
};
84 changes: 84 additions & 0 deletions rules/no-column-migration-without-comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';

function report(context, node) {
context.report({
node: node,
messageId: 'chainError',
});
}


function getIdentifierName(node, stackName = []) {
switch (node.type) {
case 'Identifier':
return { identifier: node.name, callStack: stackName };
case 'MemberExpression':
return getIdentifierName(node.object, [...stackName, node.property.name])
case 'CallExpression':
return getIdentifierName(node.callee, stackName);
}
}

const VALID_MEMBER_EXPRESSIONS = ['dropColumn', 'foreign', 'unique', 'index', 'dropUnique', 'dropIndex'];

module.exports = {
meta: {
type: 'problem',
docs: {
description:
'All columns should have a comment explaining their purpose',
},
messages: {
chainError:
'`table.column()` should have a comment explaining its purpose',
},
},
create: function(context) {
return {
CallExpression(node) {
const { callee } = node;

if (
callee.type === 'MemberExpression' &&
callee.object &&
callee.object.object &&
callee.object.object.name === 'knex' &&
callee.object.property &&
callee.object.property.name === 'schema' &&
['alterTable', 'createTable', 'table'].includes(callee.property.name)
) {
const callback = node.arguments[1];

if (callback && callback.type === 'ArrowFunctionExpression') {
const tableParam = callback.params[0];

if (tableParam && tableParam.type === 'Identifier') {

const body = callback.body.body ?? [{ expression: callback.body }];

body.forEach(statement => {
const columnExpression = statement.expression;

const { identifier: identifierName, callStack } = getIdentifierName(columnExpression);

if (!callStack.some((call) => VALID_MEMBER_EXPRESSIONS.includes(call))) {
if (identifierName === tableParam.name) {
// Check if .comment() is present
const hasComment = columnExpression.callee &&
columnExpression.callee.type === 'MemberExpression' &&
columnExpression.callee.property.name === 'comment';

if (!hasComment) {
report(context, node);
}
}
}

});
}
}
}
}
};
}
};
249 changes: 249 additions & 0 deletions rules/no-column-migration-without-comment.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
'use strict';

const rule = require('./no-column-migration-without-comment.js'),
RuleTester = require('eslint').RuleTester;

const ruleTester = new RuleTester({
parserOptions: { ecmaVersion: 2015, sourceType: 'module' },
});

ruleTester.run('no-column-migration-without-comment', rule, {
valid: [
// createTable
{
name: 'With comment',
code: `
const up = function (knex) {
return knex.schema.createTable(TABLE_NAME, (table) => {
table.boolean(COLUMN_NAME).comment('This is a comment');
});
};
`,
},

{
name: 'Without using table callback',
code: `
const up = function (knex) {
return knex.schema.createTable(TABLE_NAME, (table) => {
toto.maFunction();
});
};
`,
},

// alterTable
{
name: 'With comment',
code: `
const up = function (knex) {
return knex.schema.alterTable(TABLE_NAME, (table) => {
table.boolean(COLUMN_NAME).comment('This is a comment');
});
};
`,
},

{
name: 'Without using table callback',
code: `
const up = function (knex) {
return knex.schema.alterTable(TABLE_NAME, (table) => {
toto.maFunction();
});
};
`,
},

// table

{
name: 'With comment',
code: `
const up = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.boolean(COLUMN_NAME).comment('This is a comment');
});
};
`,
},

{
name: 'Without using table callback',
code: `
const up = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
toto.maFunction();
});
};
`,
},


{
name: 'With down function',
code: `
const down = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.boolean('toto').comment('This is a comment');
});
};
`,
},


{
name: 'With dropColumn',
code: `
const uo = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.dropColumn('toto');
});
};
`,
},

{
name: 'With foreign',
code: `
const uo = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.foreign('toto');
});
};
`,
},

{
name: 'With unique',
code: `
const uo = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.unique('toto');
});
};
`,
},

{
name: 'With index',
code: `
const uo = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.index('toto');
});
};
`,
},

{
name: 'With dropUnique',
code: `
const uo = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.dropUnique('toto');
});
};
`,
},

{
name: 'With dropIndex',
code: `
const uo = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.dropIndex('toto');
});
};
`,
},

{
name: 'With oneline',
code: `
const uo = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => table.boolean('toto').comment('comment'));
};
`,
},
],

invalid: [

// CreateTable
{
name: 'Create column without comment',
code: `
const up = function (knex) {
return knex.schema.createTable(TABLE_NAME, (table) => {
table.boolean(COLUMN_NAME).defaultTo(false);
});
};
`,
errors: [{ messageId: 'chainError' }],
},
{
name: 'Create column without comment',
code: `
const up = function (knex) {
return knex.schema.createTable(TABLE_NAME, (table) => {
table.boolean(COLUMN_NAME).defaultTo(false).comment('toto');
table.boolean(COLUMN_NAME_2).defaultTo(false);
});
};
`,
errors: [{ messageId: 'chainError' }],
},

// AlterTable

{
name: 'Create column without comment',
code: `
const up = function (knex) {
return knex.schema.alterTable(TABLE_NAME, (table) => {
table.boolean(COLUMN_NAME).defaultTo(false);
});
};
`,
errors: [{ messageId: 'chainError' }],
},
{
name: 'Create column without comment',
code: `
const up = function (knex) {
return knex.schema.alterTable(TABLE_NAME, (table) => {
table.boolean(COLUMN_NAME).defaultTo(false).comment('toto');
table.boolean(COLUMN_NAME_2).defaultTo(false);
});
};
`,
errors: [{ messageId: 'chainError' }],
},

// table
{
name: 'Create column without comment',
code: `
const up = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.boolean(COLUMN_NAME).defaultTo(false);
});
};
`,
errors: [{ messageId: 'chainError' }],
},
{
name: 'Create column without comment',
code: `
const up = function (knex) {
return knex.schema.table(TABLE_NAME, (table) => {
table.boolean(COLUMN_NAME).defaultTo(false).comment('toto');
table.boolean(COLUMN_NAME_2).defaultTo(false);
});
};
`,
errors: [{ messageId: 'chainError' }],
},
],
});

0 comments on commit bd02646

Please sign in to comment.