blob: 1fe33876a3fc5c94ccbefb7c795e73b5e4ab8341 [file]
import { evaluateObjectProperties, getContextIdentifiers, getKeyName, getRuleInfo, hasUnresolvedObjectSpread } from "../utils.mjs";
import { getStaticValue } from "@eslint-community/eslint-utils";
//#region lib/rules/require-meta-fixable.ts
/**
* @fileoverview require rules to implement a `meta.fixable` property
* @author Teddy Katz
*/
const rule = {
meta: {
type: "problem",
docs: {
description: "require rules to implement a `meta.fixable` property",
category: "Rules",
recommended: true,
url: "https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-meta-fixable.md"
},
schema: [{
type: "object",
properties: { catchNoFixerButFixableProperty: {
type: "boolean",
description: "Whether the rule should attempt to detect rules that do not have a fixer but enable the `meta.fixable` property. This option is off by default because it increases the chance of false positives since fixers can't always be detected when helper functions are used."
} },
additionalProperties: false
}],
defaultOptions: [{ catchNoFixerButFixableProperty: false }],
messages: {
invalid: "`meta.fixable` must be `code`, `whitespace`, or `undefined`.",
missing: "`meta.fixable` must be either `code` or `whitespace` for fixable rules.",
noFixerButFixableValue: "`meta.fixable` is enabled but no fixer detected."
}
},
create(context) {
const catchNoFixerButFixableProperty = context.options[0] && context.options[0].catchNoFixerButFixableProperty;
const sourceCode = context.sourceCode;
const { scopeManager } = sourceCode;
const ruleInfo = getRuleInfo(sourceCode);
let contextIdentifiers;
let usesFixFunctions = false;
if (!ruleInfo) return {};
return {
Program(ast) {
contextIdentifiers = getContextIdentifiers(scopeManager, ast);
},
CallExpression(node) {
if (node.callee.type === "MemberExpression" && contextIdentifiers.has(node.callee.object) && node.callee.property.type === "Identifier" && node.callee.property.name === "report" && (node.arguments.length > 4 || node.arguments.length === 1 && evaluateObjectProperties(node.arguments[0], scopeManager).some((prop) => getKeyName(prop) === "fix"))) usesFixFunctions = true;
},
"Program:exit"(ast) {
const scope = sourceCode.getScope(ast);
const metaProperties = evaluateObjectProperties(ruleInfo.meta, scopeManager);
const metaFixableProp = ruleInfo && metaProperties.filter((prop) => prop.type === "Property").find((prop) => getKeyName(prop) === "fixable");
if (metaFixableProp) {
const staticValue = getStaticValue(metaFixableProp.value, scope);
if (!staticValue) return;
if (staticValue.value && (typeof staticValue.value !== "string" || !["code", "whitespace"].includes(staticValue.value))) {
context.report({
node: metaFixableProp.value,
messageId: "invalid"
});
return;
}
if (usesFixFunctions && (typeof staticValue.value !== "string" || !["code", "whitespace"].includes(staticValue.value))) context.report({
node: metaFixableProp.value,
messageId: "missing"
});
else if (catchNoFixerButFixableProperty && !usesFixFunctions && typeof staticValue.value === "string" && ["code", "whitespace"].includes(staticValue.value)) context.report({
node: metaFixableProp.value,
messageId: "noFixerButFixableValue"
});
} else if (!metaFixableProp && usesFixFunctions && !hasUnresolvedObjectSpread(ruleInfo.meta, scopeManager)) context.report({
node: ruleInfo.meta || ruleInfo.create,
messageId: "missing"
});
}
};
}
};
//#endregion
export { rule as default };