feat: add new biome plugins for enforcing select and where clauses, remove deprecated kysely rules
This commit is contained in:
parent
82dd4d19cb
commit
1860bce2a3
10 changed files with 39 additions and 347 deletions
|
|
@ -100,6 +100,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"plugins": ["biome_plugins/missing.grit", "biome_plugins/null.grit"],
|
||||||
"vcs": {
|
"vcs": {
|
||||||
"clientKind": "git",
|
"clientKind": "git",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|
|
||||||
25
apps/infoalloggi/biome_plugins/missing.grit
Normal file
25
apps/infoalloggi/biome_plugins/missing.grit
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
language js
|
||||||
|
|
||||||
|
`$fn($_)` where {
|
||||||
|
if ($fn <: contains `selectFrom`) {
|
||||||
|
or {
|
||||||
|
$fn <: contains `execute`,
|
||||||
|
$fn <: contains `executeTakeFirst`,
|
||||||
|
$fn <: contains `executeTakeFirstOrThrow`
|
||||||
|
},
|
||||||
|
! $fn <: contains `select`,
|
||||||
|
! $fn <: contains `selectAll`,
|
||||||
|
register_diagnostic(span=$fn, message="select statement must contain select() or selectAll()", severity="error")
|
||||||
|
} else if (or {
|
||||||
|
$fn <: contains `updateTable`,
|
||||||
|
$fn <: contains `deleteFrom`
|
||||||
|
}) {
|
||||||
|
or {
|
||||||
|
$fn <: contains `execute`,
|
||||||
|
$fn <: contains `executeTakeFirst`,
|
||||||
|
$fn <: contains `executeTakeFirstOrThrow`
|
||||||
|
},
|
||||||
|
! $fn <: contains `where`,
|
||||||
|
register_diagnostic(span=$fn, message="where or delete statements must contain where()", severity="error")
|
||||||
|
}
|
||||||
|
}
|
||||||
13
apps/infoalloggi/biome_plugins/null.grit
Normal file
13
apps/infoalloggi/biome_plugins/null.grit
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
language js
|
||||||
|
|
||||||
|
`$fn($a)` where {
|
||||||
|
$fn <: contains `where`,
|
||||||
|
or {
|
||||||
|
$a <: contains `'='`,
|
||||||
|
$a <: contains `'=='`,
|
||||||
|
$a <: contains `'!='`,
|
||||||
|
$a <: contains `'!=='`
|
||||||
|
},
|
||||||
|
$a <: contains `null`,
|
||||||
|
register_diagnostic(span=$a, message=$a, severity="error")
|
||||||
|
}
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
const plugin = {
|
|
||||||
meta: {
|
|
||||||
name: "kyselyRules",
|
|
||||||
version: "1.0.0",
|
|
||||||
},
|
|
||||||
configs: {},
|
|
||||||
rules: {
|
|
||||||
"enforce-select": require("./rules/select"),
|
|
||||||
"enforce-where": require("./rules/where"),
|
|
||||||
"enforce-null": require("./rules/null"),
|
|
||||||
},
|
|
||||||
processors: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = plugin;
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
//@ts-nocheck
|
|
||||||
module.exports = {
|
|
||||||
meta: {
|
|
||||||
type: "problem",
|
|
||||||
docs: {
|
|
||||||
description:
|
|
||||||
"Ensure that all where condition that thest for null values utilize the correct operators.",
|
|
||||||
},
|
|
||||||
fixable: "code",
|
|
||||||
schema: [], // no options
|
|
||||||
},
|
|
||||||
create(context) {
|
|
||||||
const sourceCode = context.sourceCode;
|
|
||||||
const reportedNodes = new Set();
|
|
||||||
if (!/\.where\(/.test(sourceCode.text)) {
|
|
||||||
// Skip
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ExpressionStatement(node) {
|
|
||||||
evaluate(node.expression, sourceCode.getTokens(node));
|
|
||||||
},
|
|
||||||
AwaitExpression(node) {
|
|
||||||
evaluate(node.argument, sourceCode.getTokens(node));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function Check(expression, operator) {
|
|
||||||
const forbiddenOperators = [
|
|
||||||
"'='",
|
|
||||||
"'=='",
|
|
||||||
"'!='",
|
|
||||||
"'!=='",
|
|
||||||
'"="',
|
|
||||||
'"=="',
|
|
||||||
'"!="',
|
|
||||||
'"!=="',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (forbiddenOperators.includes(operator.value)) {
|
|
||||||
const message = `The where condition is testing for null using the ${operator.value} operator. Ensure this is intentional and uses the correct operator.`;
|
|
||||||
|
|
||||||
context.report({
|
|
||||||
node: expression,
|
|
||||||
message,
|
|
||||||
fix: (fixer) => {
|
|
||||||
const correctOperator =
|
|
||||||
operator.value === "'='" ||
|
|
||||||
operator.value === "'=='" ||
|
|
||||||
operator.value === '"="' ||
|
|
||||||
operator.value === '"=="'
|
|
||||||
? "'is'"
|
|
||||||
: "'is not'";
|
|
||||||
return fixer.replaceText(operator, correctOperator);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function findNodes(expression, node, nodes) {
|
|
||||||
const nullNodes = nodes.filter((token) => token.value === "null");
|
|
||||||
if (nullNodes.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < nullNodes.length; i++) {
|
|
||||||
const nullNode = nullNodes[i];
|
|
||||||
|
|
||||||
// Avoid duplicate reports
|
|
||||||
if (reportedNodes.has(nullNode)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
reportedNodes.add(nullNode);
|
|
||||||
|
|
||||||
const operator = sourceCode.getTokensBefore(nullNode, 2)[0];
|
|
||||||
|
|
||||||
if (!operator) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Check(expression, operator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function evaluate(expression, nodes) {
|
|
||||||
if (!expression || expression.type !== "CallExpression") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereNodes = nodes.filter((token) => token.value === "where");
|
|
||||||
|
|
||||||
if (whereNodes.length === 0 || !whereNodes) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (whereNodes.length === 1) {
|
|
||||||
findNodes(expression, whereNodes[0], nodes);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < whereNodes.length; i++) {
|
|
||||||
const whereNode = whereNodes[i];
|
|
||||||
findNodes(expression, whereNode, nodes);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
const { RuleTester } = require("eslint");
|
|
||||||
const rule = require("./null");
|
|
||||||
|
|
||||||
const ruleTester = new RuleTester({
|
|
||||||
languageOptions: { ecmaVersion: 2015 },
|
|
||||||
});
|
|
||||||
|
|
||||||
ruleTester.run(
|
|
||||||
"enforce-null", // rule name
|
|
||||||
rule,
|
|
||||||
{
|
|
||||||
valid: [
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').select(['field']).where('something', 'is', null).execute()",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').select(['field']).where('something', 'is not', null).execute()",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').select(['field']).where('something', '=', null).execute()",
|
|
||||||
errors: 1,
|
|
||||||
output:
|
|
||||||
"trx.selectFrom('name').select(['field']).where('something', 'is', null).execute()",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').select(['field']).where('something', '==', null).execute()",
|
|
||||||
errors: 1,
|
|
||||||
output:
|
|
||||||
"trx.selectFrom('name').select(['field']).where('something', 'is', null).execute()",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').select(['field']).where('something', '!=', null).execute()",
|
|
||||||
errors: 1,
|
|
||||||
output:
|
|
||||||
"trx.selectFrom('name').select(['field']).where('something', 'is not', null).execute()",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').select(['field']).where('something', '!==', null).execute()",
|
|
||||||
errors: 1,
|
|
||||||
output:
|
|
||||||
"trx.selectFrom('name').select(['field']).where('something', 'is not', null).execute()",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').select(['field']).where('something', 'is', null).where('something2', '=', null).execute()",
|
|
||||||
errors: 1,
|
|
||||||
output:
|
|
||||||
"trx.selectFrom('name').select(['field']).where('something', 'is', null).where('something2', 'is', null).execute()",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("EnforceNull: All tests passed!");
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
//@ts-nocheck
|
|
||||||
module.exports = {
|
|
||||||
meta: {
|
|
||||||
type: "suggestion",
|
|
||||||
docs: {
|
|
||||||
description: "Description of the rule",
|
|
||||||
},
|
|
||||||
fixable: "code",
|
|
||||||
schema: [], // no options
|
|
||||||
},
|
|
||||||
create(context) {
|
|
||||||
const sourceCode = context.sourceCode;
|
|
||||||
if (!/\.selectFrom\(/.test(sourceCode.text)) {
|
|
||||||
// Skip
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
function evaluate(expression, nodes) {
|
|
||||||
if (!expression || expression.type !== "CallExpression") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasSelectFrom = nodes.some((token) => token.value === "selectFrom");
|
|
||||||
|
|
||||||
if (hasSelectFrom) {
|
|
||||||
const hasSelect = nodes.some(
|
|
||||||
(token) => token.value === "select" || token.value === "selectAll",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasSelect) {
|
|
||||||
let closingParenthesisNode = null;
|
|
||||||
const selectNodeIndex = nodes.findIndex(
|
|
||||||
(token) => token.value === "selectFrom",
|
|
||||||
);
|
|
||||||
if (selectNodeIndex !== -1) {
|
|
||||||
closingParenthesisNode = nodes
|
|
||||||
.slice(selectNodeIndex)
|
|
||||||
.find((token) => token.value === ")");
|
|
||||||
}
|
|
||||||
context.report({
|
|
||||||
node: expression,
|
|
||||||
message: `Call chains containing "selectFrom" must also include a "select" or "selectAll" clause.`,
|
|
||||||
fix: (fixer) => {
|
|
||||||
return fixer.insertTextAfter(
|
|
||||||
closingParenthesisNode || nodes[6],
|
|
||||||
".selectAll()",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ExpressionStatement(node) {
|
|
||||||
evaluate(node.expression, sourceCode.getTokens(node));
|
|
||||||
},
|
|
||||||
AwaitExpression(node) {
|
|
||||||
evaluate(node.argument, sourceCode.getTokens(node));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
const { RuleTester } = require("eslint");
|
|
||||||
const rule = require("./select");
|
|
||||||
|
|
||||||
const ruleTester = new RuleTester({
|
|
||||||
languageOptions: { ecmaVersion: 2015 },
|
|
||||||
});
|
|
||||||
|
|
||||||
ruleTester.run(
|
|
||||||
"enforce-select", // rule name
|
|
||||||
rule,
|
|
||||||
{
|
|
||||||
valid: [
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').select(['field']).where('something', '=', something).execute()",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').selectAll().where('something', '=', something).execute()",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
{
|
|
||||||
code: "trx.selectFrom('name').where('something', '=', something).execute()",
|
|
||||||
errors: 1,
|
|
||||||
output:
|
|
||||||
"trx.selectFrom('name').selectAll().where('something', '=', something).execute()",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("EnforceSelect: All tests passed!");
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
//@ts-nocheck
|
|
||||||
module.exports = {
|
|
||||||
meta: {
|
|
||||||
type: "problem",
|
|
||||||
docs: {
|
|
||||||
description:
|
|
||||||
"Ensures that any call chain which affects a table also includes a 'where' clause.",
|
|
||||||
},
|
|
||||||
fixable: "code",
|
|
||||||
schema: [], // no options
|
|
||||||
},
|
|
||||||
create(context) {
|
|
||||||
const sourceCode = context.sourceCode;
|
|
||||||
|
|
||||||
if (!/\.updateTable\(|\.deleteFrom\(/.test(sourceCode.text)) {
|
|
||||||
// Skip
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ExpressionStatement(node) {
|
|
||||||
evaluate(node.expression, sourceCode.getTokens(node));
|
|
||||||
},
|
|
||||||
AwaitExpression(node) {
|
|
||||||
evaluate(node.argument, sourceCode.getTokens(node));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function evaluate(expression, nodes) {
|
|
||||||
if (!expression || expression.type !== "CallExpression") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasUpdateOrDelete = nodes.some(
|
|
||||||
(token) =>
|
|
||||||
token.value === "updateTable" || token.value === "deleteFrom",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasUpdateOrDelete) {
|
|
||||||
const hasWhere = nodes.some((token) => token.value === "where");
|
|
||||||
|
|
||||||
if (!hasWhere) {
|
|
||||||
const message = `Call chains containing "updateTable" or "deleteFrom" must also include a "where" clause.`;
|
|
||||||
context.report({
|
|
||||||
node: expression,
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
const { RuleTester } = require("eslint");
|
|
||||||
const rule = require("./where");
|
|
||||||
|
|
||||||
const ruleTester = new RuleTester({
|
|
||||||
languageOptions: { ecmaVersion: 2015 },
|
|
||||||
});
|
|
||||||
|
|
||||||
ruleTester.run(
|
|
||||||
"enforce-where", // rule name
|
|
||||||
rule,
|
|
||||||
{
|
|
||||||
valid: [
|
|
||||||
{
|
|
||||||
code: "trx.updateTable('name').set({foo:bar}).where('something', '=', something).execute()",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
{
|
|
||||||
code: "trx.updateTable('name').set({foo:bar}).execute()",
|
|
||||||
errors: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("EnforceWhere: All tests passed!");
|
|
||||||
Loading…
Add table
Reference in a new issue