diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d1697..0114cec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- [#69](https://github.com/green-code-initiative/creedengo-javascript/pull/69) Only support string literals in GCI11 +- [#69](https://github.com/green-code-initiative/creedengo-javascript/pull/69) Only support string literals (GCI11) +- [#70](https://github.com/green-code-initiative/creedengo-javascript/pull/70) Only support SQL queries within standard methods (GCI24) ## [2.0.0] - 2025-01-22 diff --git a/eslint-plugin/docs/rules/limit-db-query-results.md b/eslint-plugin/docs/rules/limit-db-query-results.md index 534835a..7137dec 100644 --- a/eslint-plugin/docs/rules/limit-db-query-results.md +++ b/eslint-plugin/docs/rules/limit-db-query-results.md @@ -19,13 +19,27 @@ If you store data about customers, you certainly don’t need to retrieve inform the table will be, the more elements the query will return. ```js -const query = "SELECT * FROM customers"; // Non-compliant +// Non-compliant: Direct SQL query without LIMIT +const mysql = require("mysql2"); +const connection = mysql.createConnection({ host: "localhost", user: "root" }); + +connection.query("SELECT * FROM users", (err, results) => { + if (err) throw err; + console.log(results); +}); ``` It may therefore be a good idea to limit the results and use pagination, for example. ```js -const query = "SELECT id,name,email FROM customers FETCH FIRST 10 ROWS ONLY"; // Compliant +// Compliant: SQL query with LIMIT clause +const mysql = require("mysql2"); +const connection = mysql.createConnection({ host: "localhost", user: "root" }); + +connection.query("SELECT * FROM users LIMIT 10", (err, results) => { + if (err) throw err; + console.log(results); +}); ``` ## Resources diff --git a/eslint-plugin/lib/rules/limit-db-query-results.js b/eslint-plugin/lib/rules/limit-db-query-results.js index 30f96ce..e20e059 100644 --- a/eslint-plugin/lib/rules/limit-db-query-results.js +++ b/eslint-plugin/lib/rules/limit-db-query-results.js @@ -42,7 +42,12 @@ module.exports = { "FETCH FIRST", "WHERE", ]; + + // List of known SQL client methods or functions + const sqlClientMethods = ["query", "execute", "run"]; + return { + // Detect SQL queries in string literals Literal: function (node) { if (typeof node.value == "string") { const query = node.value.toUpperCase(); @@ -51,10 +56,16 @@ module.exports = { query.includes("FROM") && !limitingClauses.some((clause) => query.includes(clause)) ) { - context.report({ - node: node, - messageId: "LimitTheNumberOfReturns", - }); + // Check if the query is used within a SQL client + const parent = node.parent; + + if ( + parent?.type === "CallExpression" && + parent.callee.type === "MemberExpression" && + sqlClientMethods.includes(parent.callee.property.name) + ) { + context.report({ node, messageId: "LimitTheNumberOfReturns" }); + } } } }, diff --git a/eslint-plugin/tests/lib/rules/limit-db-query-results.js b/eslint-plugin/tests/lib/rules/limit-db-query-results.js index cff326f..ce30d79 100644 --- a/eslint-plugin/tests/lib/rules/limit-db-query-results.js +++ b/eslint-plugin/tests/lib/rules/limit-db-query-results.js @@ -34,6 +34,7 @@ const ruleTester = new RuleTester({ sourceType: "module", }, }); + const expectedError = { messageId: "LimitTheNumberOfReturns", type: "Literal", @@ -42,25 +43,36 @@ const expectedError = { ruleTester.run("limit-db-query-results", rule, { valid: [ ` - const query = "SELECT id, name, email FROM customers LIMIT 10;"; + sqlClient.query("SELECT id, name, email FROM customers LIMIT 10;"); + `, + ` + sqlClient.query("SELECT TOP 5 * FROM products;"); `, ` - const query = "SELECT TOP 5 * FROM products;"; + sqlClient.query("SELECT id, name, email FROM customers WHERE id = 1"); `, ` - const query = "SELECT id, name, email FROM customers WHERE id = 1;"; + sqlClient.query("SELECT * FROM orders FETCH FIRST 20 ROWS ONLY"); `, ` - const query = "SELECT * FROM orders FETCH FIRST 20 ROWS ONLY;"; + sqlClient.query("WITH numbered_customers AS (SELECT *, ROW_NUMBER() OVER (ORDER BY customer_id) AS row_num FROM customers) SELECT * FROM numbered_customers WHERE row_num <= 50"); `, ` - const query = "WITH numbered_customers AS (SELECT *, ROW_NUMBER() OVER (ORDER BY customer_id) AS row_num FROM customers) SELECT * FROM numbered_customers WHERE row_num <= 50;"; + console.log("SELECT id, name, email FROM customers WHERE id = 1"); `, ], invalid: [ { - code: `const query = "SELECT * FROM bikes;";`, + code: `sqlClient.query("SELECT * FROM bikes");`, + errors: [expectedError], + }, + { + code: `sqlClient.run("SELECT id, departure, arrival FROM flights");`, + errors: [expectedError], + }, + { + code: `sqlClient.execute("SELECT * FROM cars");`, errors: [expectedError], }, ],