This commit is contained in:
Shiroyasha 2025-09-14 21:22:24 +03:00
commit a956daf8e2
Signed by: shiroyashik
GPG key ID: E4953D3940D7860A
2351 changed files with 163047 additions and 0 deletions

View file

@ -0,0 +1,126 @@
// @ts-check
/**
* An ESLint rule that ensures all multiblock pattern builders use
* `@` char for the controller, ` ` (space) for air and `#` for the 'any' predicate.
*/
import { ESLintUtils } from "@typescript-eslint/utils"
const builderFactoryArgs = "definition"
const builderName = "FactoryBlockPattern"
/** @type {Record<string, string>} */
const rulePredicateCode = {
"@": `Predicates.controller(Predicates.blocks(${builderFactoryArgs}.get()))`,
"#": "Predicates.any()",
" ": "Predicates.air()",
}
/**
* Finds the root expression in the call chain
* @param {import('@typescript-eslint/utils').TSESTree.CallExpression} rootCall
*/
export function isInMultiblockBuilderCallChain(rootCall) {
let rootCallee
for(;;) {
if(rootCall.callee.type === "MemberExpression")
rootCallee = rootCall.callee
else break
if(rootCallee.object.type === "CallExpression")
rootCall = rootCallee.object
else break
}
if(!rootCallee) return false
const o = rootCallee.object
return o && o.type === "Identifier" && o.name === builderName
}
export default ESLintUtils.RuleCreator.withoutDocs({
meta: {
type: "problem",
hasSuggestions: true,
fixable: "code",
messages: {
"char-not-char": "The first argument must be a single character, like '@'.",
"not-2-args": "A 'where' call should have 2 arguments.",
...Object.fromEntries(
Object.entries(rulePredicateCode).map(([char, code]) => [
"predicate-for-" + char, `The predicate for '${char}' should be ${code}`
])
),
"factory-no-args": "Multiblock pattern factory must have a single argument.",
"factory-args": `Multiblock pattern factory arguments should be '${builderFactoryArgs} =>'`
},
schema: [],
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if(node.callee.type !== "MemberExpression")
return
if(node.callee.property.type !== "Identifier" || node.callee.property.name !== "where")
return
if(!isInMultiblockBuilderCallChain(node))
return
// Now we are sure we are dealing with a multiblock pattern builder
if(node.arguments.length !== 2)
return context.report({
messageId: "not-2-args",
node: node
})
const [char, predicate] = node.arguments
// First argument check
if(char.type !== "Literal" || typeof char.value !== "string" || char.value.length !== 1)
return context.report({
messageId: "char-not-char",
node: char
})
// Second argument check
const predicateCodeActual = context.sourceCode.text.slice(...predicate.range)
const predicateCodeShouldBe = rulePredicateCode[char.value]
if(predicateCodeShouldBe && predicateCodeActual !== predicateCodeShouldBe)
context.report({
// @ts-ignore It's not smart enough
messageId: "predicate-for-" + char.value,
node: predicate,
fix: fixer => [
fixer.replaceText(predicate, predicateCodeShouldBe)
]
})
},
ArrowFunctionExpression(node) {
if(node.body.type !== "CallExpression")
return
if(!isInMultiblockBuilderCallChain(node.body))
return
// Now we are sure we are dealing with a multiblock factory function
if(node.params.length < 1)
return context.report({
messageId: "factory-no-args",
node: node
})
/** @type {[number, number]} */
const paramsRange = [
node.params[0].range[0],
node.params.at(-1).range[1]
]
if(context.sourceCode.text.slice(...paramsRange) !== builderFactoryArgs)
return context.report({
messageId: "factory-args",
node: node.params[0],
fix: fixer => [
fixer.replaceTextRange(paramsRange, builderFactoryArgs)
]
})
}
}
},
})