Skip to content

Commit 6f816e5

Browse files
authored
Merge pull request #92 from 5app/next
chore: release latest
2 parents 1b4ba49 + 4b9f898 commit 6f816e5

12 files changed

+546
-25
lines changed

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,31 @@
1+
## [1.12.1](https://github.com/5app/eslint-plugin-sequel/compare/v1.12.0...v1.12.1) (2024-08-01)
2+
3+
4+
### Bug Fixes
5+
6+
* **no-shorthand-offset:** fix perf issue with lookbehind assertion, noissue ([8c62032](https://github.com/5app/eslint-plugin-sequel/commit/8c6203214c8792cd83eb5b814f63e234e6c386a9))
7+
8+
# [1.12.0](https://github.com/5app/eslint-plugin-sequel/compare/v1.11.0...v1.12.0) (2024-08-01)
9+
10+
11+
### Features
12+
13+
* **no-shorthand-offset:** prevent 'LIMIT offset, count' syntax ([94e0409](https://github.com/5app/eslint-plugin-sequel/commit/94e04098c52133349acd3476fdc541e1dfe714dc))
14+
15+
# [1.11.0](https://github.com/5app/eslint-plugin-sequel/compare/v1.10.0...v1.11.0) (2024-07-15)
16+
17+
18+
### Features
19+
20+
* **no-backticks:** new rule to prevent incompatible backticks, noissue ([0181fce](https://github.com/5app/eslint-plugin-sequel/commit/0181fce8ff78e595829f248e854d63cc608ded49))
21+
22+
# [1.10.0](https://github.com/5app/eslint-plugin-sequel/compare/v1.9.10...v1.10.0) (2024-07-08)
23+
24+
25+
### Features
26+
27+
* **allowed-functions:** rule allowed-functions or rather disallowed, fixes [#86](https://github.com/5app/eslint-plugin-sequel/issues/86) ([d2be98c](https://github.com/5app/eslint-plugin-sequel/commit/d2be98c4766cb5f47b0ab9e8f41675a9c952409f))
28+
129
## [1.9.10](https://github.com/5app/eslint-plugin-sequel/compare/v1.9.9...v1.9.10) (2023-07-19)
230

331

README.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,31 +30,47 @@ Then configure the rules you want to use under the rules section.
3030
```json
3131
{
3232
"rules": {
33-
"sequel/function-case": 2,
34-
"sequel/indent": [2, "tab"],
35-
"sequel/max-placeholders": [2, {"max": 3}],
36-
"sequel/no-eol-command": [2, {"allowOnOwnLine": true}],
33+
"sequel/allowed-functions": [
34+
"error"
35+
{"disallow": ["GROUP_CONCAT"]}
36+
],
37+
"sequel/function-case": "error",
38+
"sequel/indent": ["error", "tab"],
39+
"sequel/max-placeholders": [
40+
"error",
41+
{"max": 3}
42+
],
43+
"sequel/no-backticks": "error",
44+
"sequel/no-eol-command": [
45+
"error",
46+
{"allowOnOwnLine": true}
47+
],
3748
"sequel/no-shorthand-all": [
38-
2,
49+
"error",
3950
{"allowQualified": true, "allowCountAll": true}
4051
],
41-
"sequel/no-unsafe-query": 2,
42-
"sequel/spacing": 2
52+
"sequel/no-shorthand-offset": "error",
53+
"sequel/no-unsafe-query": "error",
54+
"sequel/spacing": "error"
4355
}
4456
}
4557
```
4658

4759
## Rules
4860

61+
- `sequel/allowed-functions`: List functions which are **not** allowed
62+
- `disallow`: Array of disallowed SQL functions
4963
- `sequel/function-case`: Makes SQL function names uppercase, e.g. 'SELECT' **fixable**
5064
- `sequel/indent`: Enforces indentation **fixable**
5165
- `'tab'|Number`: Defines the characters to use, where Number is given it uses spaces (default `2`).
5266
- `sequel/max-placeholders`: Placeholders, `?` character, can be hard to read if there are many in the same SQL string.
5367
- `max`: Maximum number of placeholders allowed (default `3`)
68+
- `sequel/no-backticks`: Prevent the use of non-standard backticks to quote identifiers - use quotes, table prefixes on fields, or naming which does not conflict.
5469
- `sequel/no-eol-command`: Avoid ending lines with a SQL command which is always followed by a value.
5570
- `allowOnOwnLine`: Permits the command to appear if it is not preceeded by anything, allowing commands to be easily read.
5671
- `sequel/no-shorthand-all`: Avoid using the ambiguous shorthand all '\*'.
5772
- `allowQualified` (Boolean, default: `false`): Permits qualified shorthand all e.g. `table.*` to get everything from a table.
5873
- `allowCountAll` (Boolean, default: `false`): Permits within `COUNT()` e.g. `COUNT(*)`.
74+
- `sequel/no-shorthand-offset`: Prevent non-standard SQL `LIMIT offset, count`
5975
- `sequel/no-unsafe-query`: Checks whether there are potentially any vulnerable SQL'ish template literals, fix by using SQL placeholders or using [SQL templating formatter](https://www.npmjs.com/search?q=sql%20template)
6076
- `sequel/spacing`: Multiple spaces and tabs should only be used for indentation **fixable**

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-sequel",
3-
"version": "1.9.10",
3+
"version": "1.12.1",
44
"description": "Eslint rules for inline SQL",
55
"keywords": [
66
"eslint",

rules/allowed-functions.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const createSQLTemplateElementHandler = require('../utils/createSQLTemplateElementHandler');
2+
const getLocation = require('../utils/getLocation.js');
3+
4+
/**
5+
* Template Element Handler for Function Names allowed and disallowed
6+
* @param {object} node - Element Node
7+
* @param {object} context - Eslint Context object
8+
* @returns {object|undefined} Returns a format object or undefined
9+
*/
10+
function templateElementHandler(node, context) {
11+
const text = node.value.raw;
12+
13+
const {disallowed} = context.options.at(0);
14+
15+
// Special words?
16+
const regexp = new RegExp(
17+
`\\b(?<funcName>${disallowed.join('|')})\\(`,
18+
'gi'
19+
);
20+
21+
const test = text.matchAll(regexp);
22+
23+
const incidents = [];
24+
25+
for (const match of test) {
26+
const {funcName} = match.groups;
27+
28+
const report = {
29+
messageId: 'isDisallowed',
30+
data: {
31+
funcName,
32+
},
33+
...getLocation(node, match.index, funcName),
34+
};
35+
36+
incidents.push(report);
37+
}
38+
39+
return incidents;
40+
}
41+
42+
/**
43+
* Export `function-case`.
44+
*
45+
* @param {object} context - Eslint Context object
46+
* @returns {object} Object rule
47+
*/
48+
module.exports = {
49+
meta: {
50+
type: 'suggestion',
51+
docs: {
52+
description: 'Enforce allowed and disallowed functions',
53+
category: 'Stylistic Issues',
54+
},
55+
messages: {
56+
isDisallowed: 'Function is disallowed "{{funcName}}"',
57+
},
58+
schema: {
59+
minItems: 1,
60+
maxItems: 1,
61+
items: [
62+
{
63+
type: 'object',
64+
properties: {
65+
disallowed: {
66+
type: 'array',
67+
items: {type: 'string'},
68+
minItems: 1,
69+
},
70+
},
71+
additionalProperties: false,
72+
},
73+
],
74+
},
75+
},
76+
create: createSQLTemplateElementHandler({templateElementHandler}),
77+
};

rules/no-backticks.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
const createSQLTemplateElementHandler = require('../utils/createSQLTemplateElementHandler');
2+
3+
/**
4+
* Template Element Handler for Backticks
5+
* @param {object} node - Element Node
6+
* @returns {object|undefined} Returns a format object or undefined
7+
*/
8+
function templateElementHandler(node) {
9+
const text = node.value.raw;
10+
11+
const backtick = '\\`';
12+
13+
const test = text.matchAll(backtick);
14+
15+
const incidents = [];
16+
17+
for (const match of test) {
18+
// Get the text before the match, and split it into lines
19+
const prefixLines = text.slice(0, match.index).split('\n');
20+
// Extract the last line, everything in the column preceeding the backtick
21+
const prefixLast = prefixLines.pop();
22+
// Derive the column, if the prefix is empty i.e. no multi-line, include the start column of the node
23+
const column =
24+
(prefixLines.length === 0 ? node.loc.start?.column : -1) +
25+
prefixLast.length;
26+
// Count the lines: the text node, and then the lines within the text node before the backtick
27+
const line = node.loc.start?.line + prefixLines.length;
28+
29+
const report = {
30+
messageId: 'isDisallowed',
31+
loc: {
32+
start: {
33+
line,
34+
column,
35+
},
36+
end: {
37+
line,
38+
column: column + backtick.length,
39+
},
40+
},
41+
};
42+
43+
incidents.push(report);
44+
}
45+
46+
return incidents;
47+
}
48+
49+
/**
50+
* Export `no-backticks`.
51+
*
52+
* @param {object} context - Eslint Context object
53+
* @returns {object} Object rule
54+
*/
55+
module.exports = {
56+
meta: {
57+
type: 'suggestion',
58+
docs: {
59+
description:
60+
'Enforce allowed and disallowed backticks in SQL template literals',
61+
category: 'Stylistic Issues',
62+
},
63+
messages: {
64+
isDisallowed:
65+
'Backticks are disallowed as they are incompatible with SQL. Use double quotes instead, prefix identifier names or ammend names of fields stored in the database.',
66+
},
67+
},
68+
create: createSQLTemplateElementHandler({templateElementHandler}),
69+
};

rules/no-shorthand-offset.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
const isSqlQuery = require('../utils/sqlParser.js');
2+
const isTagged = require('../utils/isTagged.js');
3+
const getLocation = require('../utils/getLocation.js');
4+
5+
/**
6+
* no-shorthand-offset
7+
* MySQL permits the non-standard `LIMIT offset, count` syntax, but it is not portable to other SQL databases.
8+
* So this rule will flag the potential use of `LIMIT offset, count` syntax.
9+
*/
10+
module.exports = {
11+
meta: {
12+
type: 'suggestion',
13+
docs: {
14+
description: 'Disallow the use of `LIMIT offset, count` syntax',
15+
category: 'non-standard SQL',
16+
},
17+
messages: {
18+
nonStandardSQL:
19+
'Non-standard SQL syntax `LIMIT offset, count` is not portable',
20+
},
21+
},
22+
23+
/**
24+
* Create `no-shorthand-offset` rule
25+
*
26+
* @param {object} context - Eslint Context object
27+
* @returns {object} Object rule
28+
*/
29+
create(context) {
30+
/**
31+
* Validate node.
32+
* @param {object} templateLiteralNode - Node
33+
* @returns {void}
34+
*/
35+
function validate(templateLiteralNode) {
36+
const {parent} = templateLiteralNode;
37+
38+
// Tagged with a 'SQL' like name?
39+
const tagged = isTagged(parent);
40+
41+
// Join up the parts...
42+
const literal = templateLiteralNode.quasis
43+
.map(quasi => quasi.value.raw)
44+
.join('?');
45+
46+
// Is this something other than a SQL expression?
47+
if (!tagged && !isSqlQuery(literal)) {
48+
return;
49+
}
50+
51+
const incidents = [];
52+
53+
// Loop through the TemplateElements
54+
templateLiteralNode.quasis.forEach((node, index) => {
55+
// Is there a `LIMIT offset,` clause?
56+
const hardcodedShortHandOffset = node.value.raw.match(
57+
/\b(?<prefix>limit(\s+(--.*\n)*)+)(?<body>(\d+|\?)\s*,)/i
58+
);
59+
60+
if (hardcodedShortHandOffset) {
61+
const matchStr = hardcodedShortHandOffset.groups.body;
62+
const matchIndex =
63+
hardcodedShortHandOffset.index +
64+
hardcodedShortHandOffset.groups.prefix.length;
65+
66+
incidents.push({
67+
messageId: 'nonStandardSQL',
68+
...getLocation(node, matchIndex, matchStr),
69+
});
70+
return;
71+
}
72+
73+
// Is there a `LIMIT` clause?
74+
const endsInLimitClause = node.value.raw.match(
75+
/limit(\s+(--.*\n)*)+$/i
76+
);
77+
78+
if (endsInLimitClause && !node.tail) {
79+
// Get the next node in sequence, does that start with a `,`?
80+
const nextNode = templateLiteralNode.quasis[index + 1];
81+
82+
const nextStartsWithComma =
83+
nextNode.value.raw.match(/^(\s*),/i);
84+
85+
// console.log({
86+
// nextNode,
87+
// endsInLimitClause,
88+
// nextStartsWithComma,
89+
// });
90+
91+
if (nextStartsWithComma) {
92+
const strPattern = ',';
93+
94+
// Report the error
95+
const matchIndex =
96+
nextNode.value.raw.indexOf(strPattern);
97+
98+
incidents.push({
99+
messageId: 'nonStandardSQL',
100+
...getLocation(nextNode, matchIndex, strPattern),
101+
});
102+
}
103+
}
104+
});
105+
106+
// console.log(incidents);
107+
108+
incidents.forEach(incident => context.report(incident));
109+
}
110+
111+
return {
112+
TemplateLiteral: validate,
113+
};
114+
},
115+
};

0 commit comments

Comments
 (0)