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": {
|
||||
"clientKind": "git",
|
||||
"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