eslint-plugin-effector
Version:
Enforcing best practices for Effector
1,295 lines • 66.1 kB
JavaScript
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let _typescript_eslint_utils = require("@typescript-eslint/utils");
let _typescript_eslint_type_utils = require("@typescript-eslint/type-utils");
let typescript = require("typescript");
typescript = __toESM(typescript, 1);
let esquery = require("esquery");
esquery = __toESM(esquery, 1);
//#region package.json
var name = "eslint-plugin-effector";
var version = "0.19.0";
//#endregion
//#region src/shared/create.ts
const createRule = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://eslint.effector.dev/rules/${name}`);
//#endregion
//#region src/shared/is.ts
const check$1 = (symbol, types, from) => {
const name = symbol.getName();
const declarations = symbol.declarations ?? [];
return types.includes(name) && declarations.map((decl) => decl.getSourceFile().fileName).some((fname) => fname.includes("node_modules") && fname.includes(from));
};
const isType = {
store: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
from: "package",
package: "effector",
name: ["Store", "StoreWritable"]
}, program),
event: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
from: "package",
package: "effector",
name: ["Event", "EventCallable"]
}, program),
effect: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
from: "package",
package: "effector",
name: "Effect"
}, program),
domain: (type, program) => (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
from: "package",
package: "effector",
name: "Domain"
}, program),
unit: (type, program) => {
return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
from: "package",
package: "effector",
name: [
"Store",
"StoreWritable",
"Event",
"EventCallable",
"Effect",
"Domain"
]
}, program);
},
gate: (type) => {
const symbol = type.getSymbol() ?? type.aliasSymbol;
return symbol ? check$1(symbol, ["Gate"], "effector") : false;
},
jsx: (type, program) => {
return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
from: "package",
package: "react",
name: [
"Element",
"ReactNode",
"ReactElement"
]
}, program);
},
component: (type, program) => {
return (0, _typescript_eslint_type_utils.typeMatchesSpecifier)(type, {
from: "package",
package: "react",
name: [
"FC",
"FunctionComponent",
"ComponentType",
"ComponentClass",
"ForwardRefRenderFunction"
]
}, program);
}
};
//#endregion
//#region src/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.ts
var enforce_effect_naming_convention_default = createRule({
name: "enforce-effect-naming-convention",
meta: {
type: "problem",
docs: { description: "Enforce Fx as a suffix for any Effector Effect." },
messages: {
invalid: "Effect \"{{ current }}\" should be named with `Fx` suffix, rename it to \"{{ fixed }}\"",
rename: "Rename \"{{ current }}\" to \"{{ fixed }}\""
},
schema: [],
hasSuggestions: true
},
defaultOptions: [],
create: (context) => {
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
return {
[`${selector$14.variable}, ${selector$14.array.identifier}, ${selector$14.array.assignment}, ${selector$14.function.identifier}, ${selector$14.function.assignment}`]: (node) => {
const type = services.getTypeAtLocation(node);
if (!isType.effect(type, services.program)) return;
const data = {
current: node.name,
fixed: node.name + "Fx"
};
if (node.typeAnnotation) return context.report({
node,
messageId: "invalid",
data
});
const suggestion = {
messageId: "rename",
data: {
current: node.name,
fixed: data.fixed
},
fix: (fixer) => fixer.replaceText(node, data.fixed)
};
context.report({
node,
messageId: "invalid",
data,
suggest: [suggestion]
});
},
[`${selector$14.shape.identifier}, ${selector$14.shape.assignment}`]: (node) => {
const type = services.getTypeAtLocation(node.value);
const ident = node.value.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier ? node.value : node.value.left;
if (!isType.effect(type, services.program)) return;
const data = {
current: ident.name,
fixed: ident.name + "Fx"
};
const suggestion = {
messageId: "rename",
data: {
current: ident.name,
fixed: data.fixed
},
fix: (fixer) => node.shorthand ? fixer.insertTextAfter(node.key, `: ${data.fixed}`) : fixer.replaceText(ident, data.fixed)
};
context.report({
node: ident,
messageId: "invalid",
data,
suggest: [suggestion]
});
}
};
}
});
const FxRegex = /Fx$/;
const selector$14 = {
variable: `VariableDeclarator > Identifier.id[name!=${FxRegex}]`,
array: {
identifier: `ArrayPattern > Identifier.elements[name!=${FxRegex}]`,
assignment: `ArrayPattern > AssignmentPattern > Identifier.left[name!=${FxRegex}]`
},
shape: {
identifier: `ObjectPattern > Property:has(> Identifier.value[name!=${FxRegex}])`,
assignment: `ObjectPattern > Property:has(> AssignmentPattern:has(> Identifier.left[name!=${FxRegex}]))`
},
function: {
identifier: `:function > Identifier.params[name!=${FxRegex}]`,
assignment: `:function > AssignmentPattern > Identifier.left[name!=${FxRegex}]`
}
};
//#endregion
//#region src/shared/package.ts
const PACKAGE_NAME$1 = {
core: /^effector(?:\u002Fcompat)?$/,
react: /^effector-react$/,
storage: /^@?effector-storage(\u002F[\w-]+)*$/
};
//#endregion
//#region src/rules/enforce-exhaustive-useUnit-destructuring/enforce-exhaustive-useUnit-destructuring.ts
var enforce_exhaustive_useUnit_destructuring_default = createRule({
name: "enforce-exhaustive-useUnit-destructuring",
meta: {
type: "problem",
docs: { description: "Ensure all units passed to useUnit are properly destructured." },
messages: {
unusedKey: "Property \"{{name}}\" is passed but not destructured.",
missingKey: "Property \"{{name}}\" is destructured but not passed in the unit object."
},
schema: [],
defaultOptions: []
},
create(context) {
const importedAs = /* @__PURE__ */ new Set();
return {
[selector$13.import]: (node) => void importedAs.add(node.local.name),
[`${selector$13.variable.shape}:has(> ${selector$13.call}:has(${selector$13.arg.shape}))`](node) {
if (!importedAs.has(node.init.callee.name)) return;
const provided = shapeToKeyMap(node.init.arguments[0]);
const consumed = shapeToKeyMap(node.id);
if (provided === null || consumed === null) return;
for (const { type, name } of check(provided, consumed)) if (type === "unused") context.report({
node: node.init.arguments[0],
messageId: "unusedKey",
data: { name }
});
else context.report({
node: node.id,
messageId: "missingKey",
data: { name }
});
},
[`${selector$13.variable.list}:has(> ${selector$13.call}:has(${selector$13.arg.list}))`](node) {
if (!importedAs.has(node.init.callee.name)) return;
const provided = listToKeyMap(node.init.arguments[0]);
const consumed = listToKeyMap(node.id);
if (provided === null || consumed === null) return;
for (const { type, name } of check(provided, consumed)) if (type === "unused") context.report({
node: node.init.arguments[0],
messageId: "unusedKey",
data: { name }
});
else context.report({
node: node.id,
messageId: "missingKey",
data: { name }
});
}
};
}
});
const selector$13 = {
import: `ImportDeclaration[source.value=${PACKAGE_NAME$1.react}] > ImportSpecifier[imported.name=useUnit]`,
variable: {
shape: "VariableDeclarator[id.type=ObjectPattern]",
list: "VariableDeclarator[id.type=ArrayPattern]"
},
call: "CallExpression.init[arguments.length=1][callee.type=Identifier]",
arg: {
shape: "ObjectExpression.arguments",
list: "ArrayExpression.arguments"
}
};
function toName$1(key, node) {
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.name;
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Literal) return String(node.value);
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && node.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return `${toName$1(key, node.object)}.${node.property.name}`;
return `<unknown at ${key}>`;
}
function toKey(prop) {
if (prop.computed) return null;
else if (prop.key.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return prop.key.name;
else return prop.key.value;
}
function* check(provided, consumed) {
for (const [key, node] of provided) if (!consumed.has(key)) yield {
type: "unused",
name: toName$1(key, node)
};
for (const [key, node] of consumed) if (!provided.has(key)) yield {
type: "missing",
name: toName$1(key, node)
};
}
function shapeToKeyMap(shape) {
const map = /* @__PURE__ */ new Map();
for (const prop of shape.properties) {
if (prop.type !== _typescript_eslint_utils.AST_NODE_TYPES.Property) return null;
const key = toKey(prop);
if (key === null) return null;
else map.set(key, prop.key);
}
return map;
}
function listToKeyMap(list) {
const map = /* @__PURE__ */ new Map();
for (const [index, element] of list.elements.entries()) {
if (element === null) continue;
if (element.type === _typescript_eslint_utils.AST_NODE_TYPES.RestElement || element.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) return null;
map.set(index, element);
}
return map;
}
//#endregion
//#region src/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.ts
var enforce_gate_naming_convention_default = createRule({
name: "enforce-gate-naming-convention",
meta: {
type: "problem",
docs: { description: "Enforce a Gate is named capitalized like a React Component" },
messages: {
invalid: "Gate \"{{ current }}\" should be named with first capital letter, rename it to \"{{ fixed }}\"",
rename: "Rename \"{{ current }}\" to \"{{ fixed }}\""
},
schema: [],
hasSuggestions: true
},
defaultOptions: [],
create: (context) => {
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
return { [`VariableDeclarator[id.name=${GateRegex}]`]: (node) => {
const type = services.getTypeAtLocation(node);
if (!isType.gate(type)) return;
const current = node.id.name;
const fixed = current[0].toUpperCase() + current.slice(1);
const data = {
current,
fixed
};
const suggestion = {
messageId: "rename",
data: {
current,
fixed
},
fix: (fixer) => fixer.replaceText(node.id, fixed)
};
context.report({
node: node.id,
messageId: "invalid",
data,
suggest: [suggestion]
});
} };
}
});
const GateRegex = /^[^A-Z]/;
//#endregion
//#region src/rules/enforce-store-naming-convention/enforce-store-naming-convention.ts
var enforce_store_naming_convention_default = createRule({
name: "enforce-store-naming-convention",
meta: {
type: "problem",
docs: { description: "Enforce $ as a prefix/postfix for any Effector `Store`" },
messages: {
invalid: "Store \"{{ current }}\" should be named with a `$` {{ convention }}, rename it to \"{{ fixed }}\"",
rename: "Rename \"{{ current }}\" to \"{{ fixed }}\""
},
schema: [{
type: "object",
properties: { mode: {
type: "string",
enum: ["prefix", "postfix"]
} }
}],
hasSuggestions: true
},
defaultOptions: [{ mode: "prefix" }],
create: (context, [options]) => {
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
const selector = createSelector(options.mode === "prefix" ? PrefixRegex : PostfixRegex);
const rename = (node) => {
const trimmed = node.name.replace(options.mode === "prefix" ? /\$+$/g : /^\$+/g, "");
const fixed = options.mode === "prefix" ? `$${trimmed}` : `${trimmed}$`;
return {
current: node.name,
convention: options.mode,
fixed
};
};
return {
[`${selector.variable}, ${selector.array.identifier}, ${selector.array.assignment}, ${selector.function.identifier}, ${selector.function.assignment}`]: (node) => {
const type = services.getTypeAtLocation(node);
if (!isType.store(type, services.program)) return;
const data = rename(node);
if (node.typeAnnotation) return context.report({
node,
messageId: "invalid",
data
});
const suggestion = {
messageId: "rename",
data: {
current: node.name,
fixed: data.fixed
},
fix: (fixer) => fixer.replaceText(node, data.fixed)
};
context.report({
node,
messageId: "invalid",
data,
suggest: [suggestion]
});
},
[`${selector.shape.identifier}, ${selector.shape.assignment}`]: (node) => {
const type = services.getTypeAtLocation(node.value);
const ident = node.value.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier ? node.value : node.value.left;
if (!isType.store(type, services.program)) return;
const data = rename(ident);
const suggestion = {
messageId: "rename",
data: {
current: ident.name,
fixed: data.fixed
},
fix: (fixer) => node.shorthand ? fixer.insertTextAfter(node.key, `: ${data.fixed}`) : fixer.replaceText(ident, data.fixed)
};
context.report({
node: ident,
messageId: "invalid",
data,
suggest: [suggestion]
});
}
};
}
});
const PrefixRegex = /^[^$]/;
const PostfixRegex = /[^$]$/;
const createSelector = (regex) => ({
variable: `VariableDeclarator > Identifier.id[name=${regex}]`,
array: {
identifier: `ArrayPattern > Identifier.elements[name=${regex}]`,
assignment: `ArrayPattern > AssignmentPattern > Identifier.left[name=${regex}]`
},
shape: {
identifier: `ObjectPattern > Property:has(> Identifier.value[name=${regex}])`,
assignment: `ObjectPattern > Property:has(> AssignmentPattern:has(> Identifier.left[name=${regex}]))`
},
function: {
identifier: `:function > Identifier.params[name=${regex}]`,
assignment: `:function > AssignmentPattern > Identifier.left[name=${regex}]`
}
});
//#endregion
//#region src/rules/keep-options-order/keep-options-order.ts
var keep_options_order_default = createRule({
name: "keep-options-order",
meta: {
type: "problem",
docs: { description: "Enforce options order for Effector methods" },
messages: {
invalidOrder: `Order of options should be \`{{ correctOrder }}\`, but found \`{{ currentOrder }}\`.`,
changeOrder: "Sort options to follow the recommended order."
},
schema: [],
hasSuggestions: true
},
defaultOptions: [],
create: (context) => {
const source = context.sourceCode;
const imports = /* @__PURE__ */ new Set();
return {
[`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ${selector$12.method}`]: (node) => imports.add(node.local.name),
[`CallExpression${selector$12.call}:has(${selector$12.argument})`]: (node) => {
if (!imports.has(node.callee.name)) return;
const [config] = node.arguments;
if (config.properties.some((prop) => prop.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement || prop.key.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier)) return;
const properties = config.properties;
const current = properties.map((prop) => prop.key.name);
if (isCorrectOrder(current)) return;
const correctOrder = TRUE_ORDER.filter((item) => current.includes(item));
const othersOrder = current.filter((item) => !TRUE_ORDER.includes(item));
const order = [...correctOrder, ...othersOrder];
const snippets = properties.toSorted((a, b) => order.indexOf(a.key.name) - order.indexOf(b.key.name)).map((prop) => source.getText(prop));
const suggestion = {
messageId: "changeOrder",
fix: (fixer) => [fixer.replaceText(config, `{ ${snippets.join(", ")} }`)]
};
const data = {
correctOrder: correctOrder.join(" -> "),
currentOrder: current.join(" -> ")
};
context.report({
node: config,
messageId: "invalidOrder",
data,
suggest: [suggestion]
});
}
};
}
});
const TRUE_ORDER = [
"clock",
"source",
"filter",
"fn",
"target",
"greedy",
"batch",
"name"
];
const selector$12 = {
method: `ImportSpecifier[imported.name=/(sample|guard)/]`,
call: `[callee.type="Identifier"][arguments.length=1]`,
argument: `ObjectExpression.arguments`
};
const isCorrectOrder = (current) => {
let seen = -1;
for (const item of current) {
const index = TRUE_ORDER.indexOf(item);
const placement = index === -1 ? Infinity : index;
if (placement <= seen) return false;
seen = placement;
}
return true;
};
//#endregion
//#region src/shared/name.ts
function functionToName(node) {
if (node.id) return node.id;
if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.VariableDeclarator && node.parent.id.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.parent.id;
if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.AssignmentExpression && node.parent.left.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.parent.left;
if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.Property && node.parent.key.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.parent.key;
if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.AssignmentPattern && node.parent.left.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.parent.left;
return null;
}
function calleeToName(callee) {
if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee;
else if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && callee.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee.property;
else return null;
}
function simpleExpressionToName(node) {
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return node.name;
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && !node.computed) return node.property.name;
return null;
}
const nameOf = {
function: functionToName,
callee: calleeToName,
expression: { simple: simpleExpressionToName }
};
//#endregion
//#region src/rules/mandatory-scope-binding/mandatory-scope-binding.ts
var mandatory_scope_binding_default = createRule({
name: "mandatory-scope-binding",
meta: {
type: "problem",
docs: { description: "Forbid `Event` and `Effect` usage without `useUnit` in React." },
messages: { useUnitNeeded: "\"{{ name }}\" must be wrapped with `useUnit` from `effector-react` before usage inside React." },
schema: []
},
defaultOptions: [],
create: (context) => {
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
const checker = services.program.getTypeChecker();
const inRender = [];
const inHook = [];
/** check if the expression is used in a context specifically expecting a unit */
const isExpectingUnit = (slot) => {
const tsnode = services.esTreeNodeToTSNodeMap.get(slot);
const type = checker.getContextualType(tsnode);
if (type) return isType.event(type, services.program) || isType.effect(type, services.program);
else return false;
};
const check = (mode, node) => {
if (!(inRender.at(-1) ?? false)) return;
const type = services.getTypeAtLocation(node);
if (!isType.event(type, services.program) && !isType.effect(type, services.program)) return;
if (mode === "call") return report(node);
const delegated = isExpectingUnit(node);
if ((mode === "jsx" || (inHook.at(-1) ?? false)) && delegated) return;
else return report(node);
};
const report = (node) => {
const name = nameOf.expression.simple(node) ?? "<expression>";
context.report({
node,
messageId: "useUnitNeeded",
data: { name }
});
};
return {
[`:matches(${selector$11.function})`]: (node) => {
if (inRender.at(-1) ?? false) return void inRender.push(true);
const name = nameOf.function(node);
if (name && UseRegex$1.test(name.name)) return void inRender.push(true);
const tsnode = services.esTreeNodeToTSNodeMap.get(node);
const signature = checker.getSignatureFromDeclaration(tsnode);
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getVoidType();
if (returnType.isUnion() ? returnType.types.some((type) => isType.jsx(type, services.program)) : isType.jsx(returnType, services.program)) return void inRender.push(true);
const inferred = typescript.default.isExpression(tsnode) && (0, _typescript_eslint_type_utils.getContextualType)(checker, tsnode) || checker.getUnknownType();
if (inferred.isUnion() ? inferred.types.some((type) => isType.component(type, services.program)) : isType.component(inferred, services.program)) return void inRender.push(true);
inRender.push(false);
},
[`:matches(${selector$11.function}):exit`]: () => void inRender.pop(),
"ClassDeclaration": () => void inRender.push(false),
"ClassDeclaration:exit": () => void inRender.pop(),
"CallExpression": (node) => {
const id = nameOf.callee(node.callee), isEnteringHook = id !== null && UseRegex$1.test(id.name);
inHook.push(isEnteringHook);
},
"CallExpression:exit": () => void inHook.pop(),
[`${selector$11.callee.direct}, ${selector$11.callee.member}`]: (node) => check("call", node),
[`${selector$11.arg.direct}, ${selector$11.arg.member}`]: (node) => check("arg", node),
[`${selector$11.prop.direct}, ${selector$11.prop.member}`]: (node) => check("prop", node),
[`${selector$11.jsx.direct}, ${selector$11.jsx.member}`]: (node) => check("jsx", node)
};
}
});
const UseRegex$1 = /^use[A-Z0-9].*$/;
const selector$11 = {
function: "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression",
callee: {
direct: "CallExpression > Identifier.callee",
member: "CallExpression > MemberExpression[computed=false].callee"
},
arg: {
direct: "CallExpression > Identifier:not(.callee)",
member: "CallExpression > MemberExpression[computed=false]:not(.callee)"
},
prop: {
direct: "CallExpression > ObjectExpression > Property > Identifier.value",
member: "CallExpression > ObjectExpression > Property > MemberExpression[computed=false].value"
},
jsx: {
direct: "JSXExpressionContainer > Identifier",
member: "JSXExpressionContainer > MemberExpression[computed=false]"
}
};
//#endregion
//#region src/shared/locate.ts
const property = (key, node) => node.properties.find((prop) => prop.type == _typescript_eslint_utils.AST_NODE_TYPES.Property && prop.key.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && prop.key.name === key);
const locate = { property };
//#endregion
//#region src/rules/no-ambiguity-target/no-ambiguity-target.ts
var no_ambiguity_target_default = createRule({
name: "no-ambiguity-target",
meta: {
type: "problem",
docs: { description: "Forbid ambiguous target in `sample` and `guard`." },
messages: { ambiguous: "Method `{{ method }}` both specifies `target` option and assigns the result to a variable. Consider removing one of them." },
schema: []
},
defaultOptions: [],
create: (context) => {
const imports = /* @__PURE__ */ new Set();
const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
const usageStack = [];
return {
"ReturnStatement": () => usageStack.push(true),
"ReturnStatement:exit": () => usageStack.pop(),
"VariableDeclarator": () => usageStack.push(true),
"VariableDeclarator:exit": () => usageStack.pop(),
"ObjectExpression": () => usageStack.push(true),
"ObjectExpression:exit": () => usageStack.pop(),
"BlockStatement": () => usageStack.push(false),
"BlockStatement:exit": () => usageStack.pop(),
[`${importSelector} > ${selector$10.method}`]: (node) => imports.add(node.local.name),
[`CallExpression[callee.type="Identifier"]`]: (node) => {
if (!imports.has(node.callee.name)) return;
if (!(usageStack.at(-1) ?? false)) return;
const [config] = node.arguments;
if (config?.type !== _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression) return;
if (!locate.property("target", config)) return;
context.report({
node,
messageId: "ambiguous",
data: { method: node.callee.name }
});
}
};
}
});
const selector$10 = { method: `ImportSpecifier[imported.name=/(sample|guard)/]` };
//#endregion
//#region src/rules/no-domain-unit-creators/no-domain-unit-creators.ts
var no_domain_unit_creators_default = createRule({
name: "no-domain-unit-creators",
meta: {
type: "suggestion",
docs: { description: "Disallow using Domain methods to create units." },
messages: { avoid: "Avoid using `.{{ method }}` on a Domain instance. Use a standard factory unit creator `{{ factory }}` with a `domain` option instead." },
schema: []
},
defaultOptions: [],
create: (context) => {
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
return { [`CallExpression:has(> ${selector$9.member})`]: (node) => {
const name = node.callee.property.name;
if (!METHODS.has(name)) return;
const type = services.getTypeAtLocation(node.callee.object);
if (!isType.domain(type, services.program)) return;
const factory = ALIAS_MAP.get(name) ?? name;
context.report({
node,
messageId: "avoid",
data: {
method: name,
factory
}
});
} };
}
});
const ALIAS_MAP = (/* @__PURE__ */ new Map()).set("event", "createEvent").set("store", "createStore").set("effect", "createEffect").set("domain", "createDomain");
const METHODS = new Set([...ALIAS_MAP.values(), ...ALIAS_MAP.keys()]);
const selector$9 = { member: `MemberExpression.callee[property.type="Identifier"]` };
//#endregion
//#region src/rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values.ts
var no_duplicate_clock_or_source_array_values_default = createRule({
name: "no-duplicate-clock-or-source-array-values",
meta: {
type: "problem",
docs: { description: "Forbid providing duplicate units in `clock` and `source` arrays in `sample` and `guard`." },
messages: {
duplicate: "`{{ field }}` contains a duplicate unit `{{ unit }}`.",
remove: "Remove duplicate unit `{{ unit }}`."
},
schema: [],
hasSuggestions: true
},
defaultOptions: [],
create: (context) => {
const imports = /* @__PURE__ */ new Set();
const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
const analyze = (node, field) => {
const seen = /* @__PURE__ */ new Map();
const entries = node.elements.filter((item) => item !== null).filter((item) => item.type !== _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement);
for (const entry of entries) {
const root = traverseToRoot$1(entry);
if (!root) continue;
const name = [root.node.name, ...root.path].join(".");
if (seen.has(name)) report(entry, name, field);
else seen.set(name, entry);
}
};
const report = (node, name, field) => {
const data = {
field,
unit: name
};
const suggestion = {
messageId: "remove",
data: { unit: name },
fix: function* (fixer) {
yield fixer.remove(node);
const before = context.sourceCode.getTokenBefore(node);
if (before?.value === ",") yield fixer.remove(before);
}
};
context.report({
node,
messageId: "duplicate",
data,
suggest: [suggestion]
});
};
return {
[`${importSelector} > ${selector$8.method}`]: (node) => imports.add(node.local.name),
[`CallExpression${selector$8.call}:has(${selector$8.argument})`]: (node) => {
if (!imports.has(node.callee.name)) return;
const [config] = node.arguments;
const clock = locate.property("clock", config);
const source = locate.property("source", config);
if (clock?.value?.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression) analyze(clock.value, "clock");
if (source?.value?.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression) analyze(source.value, "source");
}
};
}
});
const selector$8 = {
method: `ImportSpecifier[imported.name=/(sample|guard)/]`,
call: `[callee.type="Identifier"][arguments.length=1]`,
argument: `ObjectExpression.arguments`
};
function traverseToRoot$1(node, path = []) {
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return {
node,
path
};
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && node.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return traverseToRoot$1(node.object, [node.property.name, ...path]);
return null;
}
//#endregion
//#region src/rules/no-duplicate-on/no-duplicate-on.ts
var no_duplicate_on_default = createRule({
name: "no-duplicate-on",
meta: {
type: "problem",
docs: { description: "Forbid duplicate `.on` calls on Stores." },
messages: { duplicate: "Method `.on` is called on store `{{ store }}` more than once for `{{ unit }}`." },
schema: []
},
defaultOptions: [],
create: (context) => {
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
const map = /* @__PURE__ */ new Map();
return { [`CallExpression[callee.property.name="on"]`]: (node) => {
const type = services.getTypeAtLocation(node.callee.object);
if (!isType.store(type, services.program)) return;
const arg = node.arguments[0];
if (!arg || arg.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) return;
const units = arg.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression ? arg.elements.filter((item) => item !== null && item.type !== _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) : [arg];
const scope = context.sourceCode.getScope(node);
const store = identify("store", node.callee.object, scope);
if (!store) return;
const set = map.get(store.id) ?? /* @__PURE__ */ new Set();
for (const unit of units) {
const instance = identify("unit", unit, scope);
if (!instance) continue;
if (set.has(instance.id)) {
const data = {
store: store.name,
unit: instance.name
};
context.report({
messageId: "duplicate",
node: unit,
data
});
} else set.add(instance.id);
}
map.set(store.id, set);
} };
}
});
function traverseToRoot(node, path = []) {
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return {
node,
path
};
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && node.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return traverseToRoot(node.object, [node.property.name, ...path]);
return null;
}
const STORE_METHODS = ["on", "reset"];
function traverseStoreToRoot(node, path = []) {
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return {
node,
path
};
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && node.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return traverseStoreToRoot(node.object, [node.property.name, ...path]);
if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.CallExpression && node.callee.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) {
if (node.callee.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier && STORE_METHODS.includes(node.callee.property.name)) return traverseStoreToRoot(node.callee.object, path);
}
return null;
}
function raiseStoreToVariable(node) {
let current = node;
while (current.parent) {
if (current.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.VariableDeclarator) return current.parent;
if (current.parent.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || current.parent.object !== current) return null;
if (current.parent.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || !STORE_METHODS.includes(current.parent.property.name)) return null;
const grandparent = current.parent.parent;
if (grandparent?.type !== _typescript_eslint_utils.AST_NODE_TYPES.CallExpression || grandparent.callee !== current.parent) return null;
current = current.parent.parent;
}
return null;
}
function findSuitableRoot(type, node) {
if (type === "unit") return traverseToRoot(node);
const root = traverseStoreToRoot(node);
if (root) return root;
const declarator = raiseStoreToVariable(node);
if (declarator && declarator.id.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return {
node: declarator.id,
path: []
};
return null;
}
function identify(type, node, scope) {
const root = findSuitableRoot(type, node);
if (!root) return null;
const variable = _typescript_eslint_utils.ASTUtils.findVariable(scope, root.node);
if (!variable) return null;
return {
id: `${variable.$id}+${root.path.join(".")}`,
name: [variable.name, ...root.path].join(".")
};
}
//#endregion
//#region src/rules/no-forward/no-forward.ts
var no_forward_default = createRule({
name: "no-forward",
meta: {
type: "problem",
docs: { description: "Prefer `sample` over `forward`." },
messages: {
noForward: "Use `sample` operator instead of `forward` as a more universal approach.",
replaceWithSample: "Replace `forward` with `sample`."
},
hasSuggestions: true,
schema: []
},
defaultOptions: [],
create: (context) => {
let sample;
const forwards = /* @__PURE__ */ new Map();
const source = context.sourceCode;
const visitorKeys = source.visitorKeys;
const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
return {
[`${importSelector} > ${selector$7.forward}`]: (node) => forwards.set(node.local.name, node),
[`${importSelector} > ${selector$7.sample}`]: (node) => sample = node.local.name,
[`CallExpression${selector$7.call}:has(${selector$7.argument})`]: (node) => {
if (!forwards.has(node.callee.name)) return;
const config = {};
const arg = node.arguments[0];
config.clock = locate.property("from", arg)?.value;
config.target = locate.property("to", arg)?.value;
if (config.target) {
const [call] = esquery.default.match(config.target, query$2.prepend, { visitorKeys }).map((node) => node).filter((node) => node === config.target);
if (call) [config.target, config.fn] = [call.callee.object, call.arguments[0]];
}
if (config.clock && !config.fn) {
const [call] = esquery.default.match(config.clock, query$2.map, { visitorKeys }).map((node) => node).filter((node) => node === config.clock);
if (call) [config.clock, config.fn] = [call.callee.object, call.arguments[0]];
}
const code = [
"clock",
"fn",
"target"
].filter((key) => config[key] !== void 0).map((key) => `${key}: ${source.getText(config[key])}`).join(", ");
context.report({
messageId: "noForward",
node: node.callee,
suggest: [{
messageId: "replaceWithSample",
fix: function* (fixer) {
const fn = sample ?? "sample";
yield fixer.replaceText(node, `${fn}({ ${code} })`);
if (!sample) yield fixer.insertTextAfter(forwards.get(node.callee.name), `, sample`);
}
}]
});
}
};
}
});
const selector$7 = {
forward: `ImportSpecifier[imported.name="forward"]`,
sample: `ImportSpecifier[imported.name="sample"]`,
call: `[callee.type="Identifier"][arguments.length=1]`,
argument: `ObjectExpression.arguments`
};
const query$2 = {
map: esquery.default.parse("CallExpression[arguments.length=1]:has(> :first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='map']))"),
prepend: esquery.default.parse("CallExpression[arguments.length=1]:has(> :first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='prepend']))")
};
//#endregion
//#region src/rules/no-getState/no-getState.ts
var no_getState_default = createRule({
name: "no-getState",
meta: {
type: "problem",
docs: { description: "Forbid `.getState` calls on Effector stores." },
messages: {
named: "Method `.getState` used on store `{{ name }}` can lead to race conditions. Replace with with `sample` or `attach`.",
anonymous: "Method `.getState` used on store can lead to race conditions. Replace with with `sample` or `attach`."
},
schema: []
},
defaultOptions: [],
create: (context) => {
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
return { [`CallExpression[callee.type="MemberExpression"][callee.property.name="getState"]`]: (node) => {
const type = services.getTypeAtLocation(node.callee.object);
if (!isType.store(type, services.program)) return;
const name = nameOf.expression.simple(node.callee.object);
if (name) context.report({
node,
messageId: "named",
data: { name }
});
else context.report({
node,
messageId: "anonymous"
});
} };
}
});
//#endregion
//#region src/rules/no-guard/no-guard.ts
var no_guard_default = createRule({
name: "no-guard",
meta: {
type: "problem",
docs: { description: "Prefer `sample` over `guard`." },
messages: {
noGuard: "Use `sample` operator instead of `guard` as a more universal approach.",
replaceWithSample: "Replace `guard` with `sample`."
},
hasSuggestions: true,
schema: []
},
defaultOptions: [],
create: (context) => {
let sample;
const guards = /* @__PURE__ */ new Map();
const source = context.sourceCode;
const visitorKeys = source.visitorKeys;
const importSelector = `ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`;
return {
[`${importSelector} > ${selector$6.guard}`]: (node) => guards.set(node.local.name, node),
[`${importSelector} > ${selector$6.sample}`]: (node) => sample = node.local.name,
[`CallExpression${selector$6.call}`]: (node) => {
if (!guards.has(node.callee.name)) return;
const config = {};
if (node.arguments.length === 1 && node.arguments[0].type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression) {
const [arg] = node.arguments;
for (const key of [
"clock",
"source",
"filter",
"target"
]) config[key] = locate.property(key, arg)?.value;
} else if (node.arguments.length === 2 && node.arguments[1].type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression) {
const [clock, arg] = node.arguments;
config.clock = clock;
for (const key of [
"source",
"filter",
"target"
]) config[key] = locate.property(key, arg)?.value;
} else return;
if (config.target) {
const [call] = esquery.default.match(config.target, query$1.prepend, { visitorKeys }).map((node) => node).filter((node) => node === config.target);
if (call) [config.target, config.fn] = [call.callee.object, call.arguments[0]];
}
const code = [
"clock",
"source",
"filter",
"fn",
"target"
].filter((key) => config[key] !== void 0).map((key) => `${key}: ${source.getText(config[key])}`).join(", ");
context.report({
messageId: "noGuard",
node: node.callee,
suggest: [{
messageId: "replaceWithSample",
fix: function* (fixer) {
const fn = sample ?? "sample";
yield fixer.replaceText(node, `${fn}({ ${code} })`);
if (!sample) yield fixer.insertTextAfter(guards.get(node.callee.name), `, sample`);
}
}]
});
}
};
}
});
const selector$6 = {
guard: `ImportSpecifier[imported.name="guard"]`,
sample: `ImportSpecifier[imported.name="sample"]`,
call: `[callee.type="Identifier"]`
};
const query$1 = { prepend: esquery.default.parse("CallExpression[arguments.length=1]:has(:first-child:expression.arguments):has(> MemberExpression.callee:has(Identifier.property[name='prepend']))") };
//#endregion
//#region src/rules/no-patronum-debug/no-patronum-debug.ts
var no_patronum_debug_default = createRule({
name: "no-patronum-debug",
meta: {
type: "problem",
docs: { description: "Disallow the use of `patronum` `debug`." },
messages: {
unexpected: "Unexpected `debug` call.",
remove: "Remove this `debug` call."
},
schema: [],
hasSuggestions: true
},
defaultOptions: [],
create: (context) => {
const debugs = /* @__PURE__ */ new Set();
return {
[`${`ImportDeclaration[source.value=${PACKAGE_NAME}]`} > ${selector$5.debug}`]: (node) => debugs.add(node.local.name),
[`CallExpression:matches(${selector$5.call})`]: (node) => {
const name = toName(node);
if (!debugs.has(name)) return;
context.report({
messageId: "unexpected",
node: node.callee,
suggest: [{
messageId: "remove",
fix: (fixer) => {
if (node.parent.type === _typescript_eslint_utils.AST_NODE_TYPES.ExpressionStatement) return fixer.remove(node.parent);
else return fixer.replaceText(node, "undefined");
}
}]
});
}
};
}
});
const PACKAGE_NAME = /^patronum(?:\u002Fdebug)?$/;
const selector$5 = {
debug: `ImportSpecifier[imported.name="debug"]`,
call: `[callee.type=Identifier], [callee.object.type=Identifier]`
};
const toName = (node) => {
switch (node.callee.type) {
case _typescript_eslint_utils.AST_NODE_TYPES.Identifier: return node.callee.name;
case _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression: return node.callee.object.name;
}
};
//#endregion
//#region src/rules/no-units-spawn-in-render/no-units-spawn-in-render.ts
const EFFECTOR_FACTORIES = new Set([
"createStore",
"createEvent",
"createEffect",
"createDomain",
"createApi",
"restore"
]);
const EFFECTOR_OPERATORS = new Set([
"sample",
"guard",
"forward",
"merge",
"split",
"combine",
"attach"
]);
const REACT_HOOKS_SPEC = {
from: "package",
package: "react",
name: [
"useState",
"useEffect",
"useLayoutEffect",
"useCallback",
"useMemo",
"useRef",
"useReducer",
"useImperativeHandle",
"useDebugValue",
"useDeferredValue",
"useTransition",
"useId",
"useSyncExternalStore",
"useInsertionEffect",
"useContext"
]
};
const EFFECTOR_FACTORY_SPEC = {
from: "package",
package: "effector",
name: [...EFFECTOR_FACTORIES]
};
const EFFECTOR_OPERATOR_SPEC = {
from: "package",
package: "effector",
name: [...EFFECTOR_OPERATORS]
};
const EFFECTOR_FACTORIO_SHAPE = [
"useModel",
"createModel",
"Provider",
"@@unitShape"
];
var no_units_spawn_in_render_default = createRule({
name: "no-units-spawn-in-render",
meta: {
type: "problem",
docs: { description: "Forbid creating Effector units or calling operators inside React components or hooks." },
messages: {
noFactoryInRender: "Creating Effector units with \"{{ name }}\" inside React component or hook is forbidden, since it may cause memory leaks and other bugs.",
noOperatorInRender: "Using Effector operator \"{{ name }}\" inside React component or hook is forbidden, since it may cause memory leaks and other bugs.",
noCustomFactoryInRender: "Creating Effector units with \"{{ name }}\" inside React component or hook is forbidden, since it may cause memory leaks and other bugs. If this is a false positive, add \"{{ name }}\" to the allowlist in the detectCustomFactories option."
},
schema: [{
type: "object",
properties: { detectCustomFactories: { oneOf: [{ type: "boolean" }, {
type: "object",
properties: { allowlist: {
type: "array",
items: { type: "string" },
uniqueItems: true
} },
required: ["allowlist"],
additionalProperties: false
}] } },
additionalProperties: false
}]
},
defaultOptions: [{ detectCustomFactories: true }],
create: (context, [options]) => {
const services = _typescript_eslint_utils.ESLintUtils.getParserServices(context);
const checker = services.program.getTypeChecker();
const { detectCustomFactories } = options;
const allowlist = typeof detectCustomFactories === "object" ? new Set(detectCustomFactories.allowlist) : void 0;
const stack = { render: [] };
const effectorImports = /* @__PURE__ */ new Map();
return {
[`${`ImportDeclaration[source.value=${PACKAGE_NAME$1.core}]`} > ImportSpecifier[imported.type="Identifier"]`]: (node) => {
const imported = node.imported.name;
const local = node.local.name;
if (EFFECTOR_FACTORIES.has(imported)) effectorImports.set(local, "factory");
else if (EFFECTOR_OPERATORS.has(imported)) effectorImports.set(local, "operator");
},
[`FunctionDeclaration, FunctionExpression, ArrowFunctionExpression`]: (node) => {
if (stack.render.at(-1) ?? false) return void stack.render.push(true);
const name = nameOf.function(node);
if (name && UseRegex.test(name.name)) return void stack.render.push(true);
const tsnode = services.esTreeNodeToTSNodeMap.get(node);
const signature = checker.getSignatureFromDeclaration(tsnode);
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getVoidType();
if (returnType.isUnion() ? returnType.types.some((type) => isType.jsx(type, services.program)) : isType.jsx(returnType, services.program)) return void stack.render.push(true);
const inferred = (0, typescript.isExpression)(tsnode) && (0, _typescript_eslint_type_utils.getContextualType)(checker, tsnode) || checker.getUnknownType();
if (inferred.isUnion() ? inferred.types.some((type) => isType.component(type, services.program)) : isType.component(inferred, services.program)) return void stack.render.push(true);
stack.render.push(false);
},
[`:matches(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression):exit`]: () => void stack.render.pop(),
"ClassDeclaration": () => void stack.render.push(false),
"ClassDeclaration:exit": () => void stack.render.pop(),
"CallExpression": (node) => {
if (!(stack.render.at(-1) ?? false)) return;
const calleeName = getCalleeName(node.callee);
switch (calleeName ? effectorImports.get(calleeName) : void 0) {
case "factory": return context.report({
node,
messageId: "noFactoryInRender",
data: { name: calleeName }
});
case "operator": return context.report({
node,
messageId: "noOperatorInRender",
data: { name: calleeName }
});
}
if (detectCustomFactories === false) return;
const returnType = services.getTypeAtLocation(node);
if (!hasEffectorUnitInType({
node: services.esTreeNodeToTSNodeMap.get(node),
checker,
program: services.program
}, returnType)) return;
const calleeType = services.getTypeAtLocation(node.callee);
const displayName = calleeName ?? "<expression>";
if ((0, _typescript_eslint_type_utils.typeMatchesSpecifier)(calleeType, REACT_HOOKS_SPEC, services.program)) return;
if (isEffectorFactorioHook(node.callee, services.getTypeAtLocation)) return;
if ((0, _typescript_eslint_type_utils.typeMatchesSpecifier)(calleeType, EFFECTOR_FACTORY_SPEC, services.program)) return context.report({
node,
messageId: "noFactoryInRender",
data: { name: displayName }
});
if ((0, _typescript_eslint_type_utils.typeMatchesSpecifier)(calleeType, EFFECTOR_OPERATOR_SPEC, services.program)) return context.report({
node,
messageId: "noOperatorInRender",
data: { name: displayName }
});
if (allowlist && calleeName && allowlist.has(calleeName)) return;
context.report({
node,
messageId: "noCustomFactoryInRender",
data: { name: displayName }
});
}
};
}
});
const UseRegex = /^use[A-Z0-9].*$/;
function getCalleeName(callee) {
if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee.name;
if (callee.type === _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && callee.property.type === _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return callee.property.name;
else return null;
}
function hasEffectorUnitInType(ctx, type, depth = 3) {
if (isType.unit(type, ctx.program)) return true;
if (depth <= 0) return false;
if (type.isUnion()) return type.types.some((type) => hasEffectorUnitInType(ctx, type, depth));
for (const property of type.getProperties()) if (hasEffectorUnitInType(ctx, ctx.checker.getTypeOfSymbolAtLocation(property, ctx.node), depth - 1)) return true;
return false;
}
function isEffectorFactorioHook(callee, getTypeAtLocation) {
if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) return false;
if (callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return false;
if (callee.property.name !== "useModel") return false;
const objectType = getTypeAtLocation(callee.object);
const propertyNames = new Set(objectType.getProperties().map((p) => p.getName()));
return EFFECTOR_FACTORIO_SHAPE.every((name) => propertyNames.has(name));
}
//#endregion
//#region src/rules/no-unnecessary-combination/no-unnecessary-combination.ts
var no_unnecessary_combination_default = createRule({
name: "no-unnecessary-combination",
meta: {
type: "suggestion",
docs: { description: "Forbid unnecessary combinations in `clock` and `source`." },
messages: { unnecessary: "{{ method }} is used under