init
This commit is contained in:
commit
a956daf8e2
2351 changed files with 163047 additions and 0 deletions
48
kubejs/dx/eslint-plugin/call-chains.mjs
Normal file
48
kubejs/dx/eslint-plugin/call-chains.mjs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// @ts-check
|
||||
/**
|
||||
* "When dealing with long concatenated method calls or extended lists of parameters,
|
||||
* indent any continuations of that statement on a new line,
|
||||
* in the same way that one would indent the body of an if statement or for loop."
|
||||
*/
|
||||
|
||||
import { ESLintUtils } from "@typescript-eslint/utils"
|
||||
|
||||
export default ESLintUtils.RuleCreator.withoutDocs({
|
||||
meta: {
|
||||
type: "problem",
|
||||
hasSuggestions: true,
|
||||
fixable: "code",
|
||||
messages: {
|
||||
"chain-too-long": "There are too many calls in this chain, and no newlines."
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
return {
|
||||
CallExpression(node) {
|
||||
let chainCount = 0
|
||||
for(let iNode = node;
|
||||
iNode.callee.type === "MemberExpression" && iNode.callee.object.type === "CallExpression";
|
||||
iNode = iNode.callee.object
|
||||
) {
|
||||
if(context.sourceCode.text.slice(
|
||||
iNode.callee.object.range[1],
|
||||
iNode.callee.property.range[0]
|
||||
).includes("\n"))
|
||||
break
|
||||
chainCount++
|
||||
}
|
||||
|
||||
if(chainCount < 3)
|
||||
return
|
||||
|
||||
|
||||
context.report({
|
||||
messageId: "chain-too-long",
|
||||
node
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
67
kubejs/dx/eslint-plugin/comment-header.mjs
Normal file
67
kubejs/dx/eslint-plugin/comment-header.mjs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// @ts-check
|
||||
/**
|
||||
* An ESLint rule that ensures all files have a C-style header, just like this one.
|
||||
*/
|
||||
|
||||
import { ESLintUtils } from "@typescript-eslint/utils"
|
||||
|
||||
const defaultHeaderContent = "*\n * Describe this file here!\n "
|
||||
const defaultHeader = `/*${defaultHeaderContent}*/\n\n`
|
||||
|
||||
|
||||
export default ESLintUtils.RuleCreator.withoutDocs({
|
||||
meta: {
|
||||
type: "problem",
|
||||
hasSuggestions: true,
|
||||
fixable: "code",
|
||||
messages: {
|
||||
"no-header": "Label things with comments! " +
|
||||
"Every file should have a C-style comment at the top explaining what that file does " +
|
||||
"(Reference existing files if you don't know what that is).",
|
||||
"default-header": "Describe what this file does. If there is a good reason not to, replace this with /**/"
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
return {
|
||||
Program(node) {
|
||||
const continuousComments = node.comments
|
||||
.slice(0,1)
|
||||
// Comments must start at the beginning of the file
|
||||
.filter(c => c.range[0] === 0)
|
||||
if(continuousComments.length > 0) {
|
||||
// Find an array of continuous comments, that have no code in between
|
||||
for(const comment of node.comments.slice(1)) {
|
||||
if(comment.range[0] - continuousComments[0].range[1] <= 2)
|
||||
continuousComments.unshift(comment)
|
||||
else break
|
||||
}
|
||||
continuousComments.reverse()
|
||||
|
||||
// Find the first occurence of a c-style comment
|
||||
const block = continuousComments.find(c => c.type === "Block")
|
||||
if(block) {
|
||||
if(block.value === defaultHeaderContent)
|
||||
return context.report({
|
||||
node: block,
|
||||
messageId: "default-header",
|
||||
})
|
||||
// Good, there is a comment which is not default
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// No comments at all or no block comment at the top
|
||||
return context.report({
|
||||
node,
|
||||
messageId: "no-header",
|
||||
|
||||
fix: (fixer) => [
|
||||
fixer.insertTextBefore(node, defaultHeader)
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
33
kubejs/dx/eslint-plugin/custom-plugin.mjs
Normal file
33
kubejs/dx/eslint-plugin/custom-plugin.mjs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Custom ESLint plugin for Monifactory's KubeJS
|
||||
*/
|
||||
|
||||
import commentHeader from "./comment-header.mjs"
|
||||
import recipeSpacing from "./recipe-spacing.mjs"
|
||||
import multiblockDeclaration from "./multiblock-declaration.mjs"
|
||||
import callChains from "./call-chains.mjs"
|
||||
|
||||
/**
|
||||
* Creates a custom ESLint plugin
|
||||
* @param {string} name Plugin name
|
||||
* @param {Record<string, *>} rules Plugin rules
|
||||
*/
|
||||
function customPluginWithAllRulesError(name, rules) {
|
||||
return {
|
||||
plugins: {
|
||||
[name]: { rules }
|
||||
},
|
||||
rules: Object.fromEntries(
|
||||
Object.entries(rules).map(([rule]) =>
|
||||
[`${name}/${rule}`, "error"]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const MoniLabs = customPluginWithAllRulesError("moni-labs", {
|
||||
"comment-header": commentHeader,
|
||||
"recipe-spacing": recipeSpacing,
|
||||
"multiblock-declaration": multiblockDeclaration,
|
||||
"call-chains": callChains
|
||||
})
|
||||
126
kubejs/dx/eslint-plugin/multiblock-declaration.mjs
Normal file
126
kubejs/dx/eslint-plugin/multiblock-declaration.mjs
Normal 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)
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
73
kubejs/dx/eslint-plugin/recipe-spacing.mjs
Normal file
73
kubejs/dx/eslint-plugin/recipe-spacing.mjs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// @ts-check
|
||||
/**
|
||||
* An ESLint rule that defines spacing in recipe registrations
|
||||
*/
|
||||
|
||||
import { ESLintUtils } from "@typescript-eslint/utils"
|
||||
|
||||
export default ESLintUtils.RuleCreator.withoutDocs({
|
||||
meta: {
|
||||
type: "problem",
|
||||
hasSuggestions: true,
|
||||
fixable: "code",
|
||||
messages: {
|
||||
"space-before-result": "There should not be any space between '(' and the recipe result.",
|
||||
"space-before-pattern": "There should be exactly 1 space between the recipe result and and '['.",
|
||||
"space-before-ingredients": "There should be exactly 1 space between ']' and '{'.",
|
||||
"space-after-ingredients": "There should not be any space between the ingredient list and ')'.",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
return {
|
||||
CallExpression(node) {
|
||||
if(node.arguments.length !== 3)
|
||||
return
|
||||
const [result, pattern, ingredients] = node.arguments
|
||||
if(pattern.type !== "ArrayExpression" || ingredients.type !== "ObjectExpression")
|
||||
return
|
||||
|
||||
// Space after ( and before first argument
|
||||
if(node.callee.range[1] + 1 !== result.range[0])
|
||||
context.report({
|
||||
messageId: "space-before-result",
|
||||
node: result,
|
||||
fix: (fixer) => [
|
||||
fixer.removeRange([node.callee.range[1] + 1, result.range[0]])
|
||||
]
|
||||
})
|
||||
|
||||
// Space after first argument and before first argument
|
||||
if(context.sourceCode.text.slice(result.range[1], pattern.range[0]) !== ", ")
|
||||
context.report({
|
||||
messageId: "space-before-pattern",
|
||||
node: pattern,
|
||||
fix: (fixer) => [
|
||||
fixer.replaceTextRange([result.range[1], pattern.range[0]], ", ")
|
||||
]
|
||||
})
|
||||
|
||||
// Space after second argument and before third argument
|
||||
if(context.sourceCode.text.slice(pattern.range[1], ingredients.range[0]) !== ", ")
|
||||
context.report({
|
||||
messageId: "space-before-ingredients",
|
||||
node: ingredients,
|
||||
fix: (fixer) => [
|
||||
fixer.replaceTextRange([pattern.range[1], ingredients.range[0]], ", ")
|
||||
]
|
||||
})
|
||||
|
||||
// Space after third argument and before )
|
||||
if(ingredients.range[1] + 1 !== node.range[1])
|
||||
context.report({
|
||||
messageId: "space-after-ingredients",
|
||||
node: ingredients,
|
||||
fix: (fixer) => [
|
||||
fixer.removeRange([ingredients.range[1], node.range[1] - 1])
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue