From 5e08f3f75bae722958667ca9092c2245e8380d86 Mon Sep 17 00:00:00 2001 From: "Pierre TOP (TOPI)" Date: Fri, 22 Sep 2023 16:13:27 +0200 Subject: [PATCH 1/3] Handle only knex client --- index.js | 3 ++- rules/avoid-updating-all-rows.js | 26 ++++++++++++++++++++++++++ rules/avoid-updating-all-rows.test.js | 25 +++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 rules/avoid-updating-all-rows.js create mode 100644 rules/avoid-updating-all-rows.test.js diff --git a/index.js b/index.js index a73ac9c..5e3215e 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ module.exports = { rules: { - 'avoid-injections': require('./rules/avoid-injections') + 'avoid-injections': require('./rules/avoid-injections'), + 'avoid-updating-all-rows': require('./rules/avoid-updating-all-rows') } } diff --git a/rules/avoid-updating-all-rows.js b/rules/avoid-updating-all-rows.js new file mode 100644 index 0000000..18de8cf --- /dev/null +++ b/rules/avoid-updating-all-rows.js @@ -0,0 +1,26 @@ +const meta = { + type: "problem", + docs: { + description: "Avoid updating all rows", + }, + messages: { + avoid: `Avoid updating all rows`, + }, +}; + +const create = function (context) { + return { + [`CallExpression[callee.property.name='update'][callee.object.callee.name='knex']`](node) { + check(context, node); + }, + }; +}; +const check = function (context, node) { + + context.report({ + node, + messageId: "avoid" + }); +}; + +module.exports = { meta, create }; diff --git a/rules/avoid-updating-all-rows.test.js b/rules/avoid-updating-all-rows.test.js new file mode 100644 index 0000000..4bfca35 --- /dev/null +++ b/rules/avoid-updating-all-rows.test.js @@ -0,0 +1,25 @@ +const { RuleTester } = require("eslint"); +const rule = require("./avoid-updating-all-rows"); + +function invalidCase(code, errors = [], others = {}) { + return Object.assign( + { + code, + errors, + }, + others, + ); +} + +const tester = new RuleTester({ + parserOptions: { ecmaVersion: 2015 }, +}); + +tester.run("avoid-updating-all-rows", rule, { + valid: ["knex('books').where({id:1}).update({'status': 'archived'})"], + invalid: [ + invalidCase("knex('books').update({'status': 'archived'})", [ + { messageId: "avoid" }, + ]), + ], +}); From 666747575ca6ee05434115ab1941b6d147c04be9 Mon Sep 17 00:00:00 2001 From: "Pierre TOP (TOPI)" Date: Tue, 26 Sep 2023 13:28:49 +0200 Subject: [PATCH 2/3] Allow ignoring tables --- README.md | 20 +++++++++++++++++++- rules/avoid-updating-all-rows.js | 15 ++++++++++++--- rules/avoid-updating-all-rows.test.js | 21 ++++++++++++++++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8b3ac30..d222e8b 100644 --- a/README.md +++ b/README.md @@ -42,4 +42,22 @@ include the library itself (`knex`), but also transaction variables (`trx`, ### `knex/avoid-injections` -Avoid some issues related to SQL injection by disallowing plain strings as the query argument to the raw queries. Check out [the tests](https://github.com/AntonNiklasson/eslint-plugin-knex/blob/master/rules/avoid-injections.test.js) to get a sense for what is valid and not. +Avoid some issues related to SQL injection by disallowing plain strings as the query argument to the raw queries. Check +out [the tests](https://github.com/AntonNiklasson/eslint-plugin-knex/blob/master/rules/avoid-injections.test.js) to get +a sense for what is valid and not. + +### `knex/avoid-updating-all-rows` + +Avoid updating all rows of a table when unwanted. +You can ignore tables for which it is a legitimate usage. + +``` +{ + settings: { + knex: { + rule: { "avoid-updating-all-rows": { tablesToIgnore: ["author"] } }, + }, + } +} +``` + diff --git a/rules/avoid-updating-all-rows.js b/rules/avoid-updating-all-rows.js index 18de8cf..f1c75e9 100644 --- a/rules/avoid-updating-all-rows.js +++ b/rules/avoid-updating-all-rows.js @@ -8,18 +8,27 @@ const meta = { }, }; -const create = function (context) { +const create = function(context) { return { [`CallExpression[callee.property.name='update'][callee.object.callee.name='knex']`](node) { check(context, node); }, }; }; -const check = function (context, node) { +const check = function(context, node) { + + if (context.settings && context.settings.knex && context.settings.knex.rule) { + const ruleSettings = context.settings.knex.rule["avoid-updating-all-rows"]; + const tablesToIgnore = ruleSettings.tablesToIgnore; + const tableToUpdate = node.callee.object.arguments[0].value; + if (tablesToIgnore.includes(tableToUpdate)) { + return; + } + } context.report({ node, - messageId: "avoid" + messageId: "avoid", }); }; diff --git a/rules/avoid-updating-all-rows.test.js b/rules/avoid-updating-all-rows.test.js index 4bfca35..b255092 100644 --- a/rules/avoid-updating-all-rows.test.js +++ b/rules/avoid-updating-all-rows.test.js @@ -16,10 +16,29 @@ const tester = new RuleTester({ }); tester.run("avoid-updating-all-rows", rule, { - valid: ["knex('books').where({id:1}).update({'status': 'archived'})"], + valid: ["knex('books').where({id:1}).update({'status': 'archived'})", + { + code: "knex('books').update({'status': 'archived'})", + settings: { + knex: { + rule: { "avoid-updating-all-rows": { tablesToIgnore: ["books"] } }, + }, + }, + }], invalid: [ invalidCase("knex('books').update({'status': 'archived'})", [ { messageId: "avoid" }, ]), + invalidCase("knex('books').update({'status': 'archived'})", [ + { messageId: "avoid" }], + { + settings: { + knex: { + rule: { "avoid-updating-all-rows": { tablesToIgnore: ["author"] } }, + }, + }, + }, + ), + ], }); From f657d135f8309ac77f82dc99e1c5cb94839b1ee5 Mon Sep 17 00:00:00 2001 From: "Pierre TOP (TOPI)" Date: Tue, 26 Sep 2023 13:53:35 +0200 Subject: [PATCH 3/3] Ensure only knex queries are checked --- rules/avoid-updating-all-rows.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/rules/avoid-updating-all-rows.test.js b/rules/avoid-updating-all-rows.test.js index b255092..5585551 100644 --- a/rules/avoid-updating-all-rows.test.js +++ b/rules/avoid-updating-all-rows.test.js @@ -17,6 +17,7 @@ const tester = new RuleTester({ tester.run("avoid-updating-all-rows", rule, { valid: ["knex('books').where({id:1}).update({'status': 'archived'})", + "sequelize('books').update({'status': 'archived'})", { code: "knex('books').update({'status': 'archived'})", settings: {