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,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
})
},
}
},
})

View 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)
]
})
}
}
},
})

View 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
})

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)
]
})
}
}
},
})

View 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])
]
})
}
}
},
})

View file

@ -0,0 +1,109 @@
export type MCIdentifier = `${string}:${string}`
export type GTJSONRecipeChanced = {
chance: number
maxChance: number
tierChanceBoost: number
}
export type GTJSONRecipeChancedContents<Content> = ({content: Content} & GTJSONRecipeChanced)[]
export type GTJSONRecipeItemIngredient = {
item: MCIdentifier
} | {
tag: MCIdentifier
} | {
type: "forge:nbt"
item: MCIdentifier
count: number
nbt: object
}
export type GTJSONRecipeItem = {
type: "gtceu:sized"
count: number
ingredient: GTJSONRecipeItemIngredient
} | {
type: "gtceu:circuit"
configuration: number
} | {
type: "forge:intersection"
children: {tag: MCIdentifier}[]
}
export type GTJSONRecipeFluid = {
amount: number
// Yes, this array does always have exactly 1 element. IDK why.
value: [{
tag: MCIdentifier
} | {
fluid: MCIdentifier
}]
}
export type GTJSONRecipeIO = {
item?: GTJSONRecipeChancedContents<GTJSONRecipeItem>
fluid?: GTJSONRecipeChancedContents<GTJSONRecipeFluid>
}
export type GTJSONRecipeCondition = {
type: "cleanroom"
cleanroom: "cleanroom" | "sterile_cleanroom"
} | {
type: "research"
research: [{
researchId: string
dataItem: {
id: MCIdentifier
Count: number
tag: object
}
}]
}
export type GTJSONRecipe = {
/** Machine ID
* @example gtceu:arc_furnace
*/
type: MCIdentifier
/** Recipe category
* @example gtceu:arc_furnace_recycling
*/
category: MCIdentifier
duration: number
inputs?: GTJSONRecipeIO
outputs?: GTJSONRecipeIO
tickInputs?: {
eu?: GTJSONRecipeChancedContents<number>
cwu?: GTJSONRecipeChancedContents<number>
}
tickOutputs?: {
eu?: GTJSONRecipeChancedContents<number>
}
// All fields above are 100% complete
recipeConditions?: GTJSONRecipeCondition[]
// TODO these when needed
inputChanceLogics: unknown
outputChanceLogics: unknown
tickInputChanceLogics: unknown
tickOutputChanceLogics: unknown
category: unknown
pattern: unknown
key: unknown
overrideCharge: unknown
transferMaxCharge: unknown
chargeIngredient: unknown
result: unknown
ingredient: unknown
experience: unknown
cookingtime: unknown
ingredients: unknown
data: unknown
}