core/quality/eslint/no-chained-assignment.eslint.rule.ts
core/quality/eslint/no-chained-assignment.eslint.rule.ts is a file in GovLab Quality. 57 lines of code and 17 definitions.
import type { Rule } from "eslint";
import { govlabMeta } from "#core/factories/eslint.factory";
type AssignExpr = Extract<Rule.Node, { type: "AssignmentExpression" }>;
const indentOf = function indentOf(context: Rule.RuleContext, node: Rule.Node): string {
const line = context.sourceCode.lines[(node.loc?.start.line ?? 1) - 1] ?? "";
return line.slice(0, line.length - line.trimStart().length);
};
const collectTargets = function collectTargets(
context: Rule.RuleContext,
node: AssignExpr,
): { targets: string[]; rhs: string } {
const targets = [context.sourceCode.getText(node.left)];
let current = node.right;
while (current.type === "AssignmentExpression") {
targets.push(context.sourceCode.getText(current.left));
current = current.right;
}
return { rhs: context.sourceCode.getText(current), targets };
};
const splitChain = function splitChain(context: Rule.RuleContext, node: AssignExpr): string {
const { rhs, targets } = collectTargets(context, node);
const indent = indentOf(context, node);
const lines: string[] = [];
for (let i = targets.length - 1; i >= 0; i -= 1) {
const source = i + 1 < targets.length ? targets[i + 1] : rhs;
lines.push(`${targets[i]} = ${source};`);
}
return lines.join(`\n${indent}`);
};
export default {
create(context: Rule.RuleContext): Rule.RuleListener {
const onAssign = (node: Rule.Node): void => {
if (node.type !== "AssignmentExpression" || node.right.type !== "AssignmentExpression") {
return;
}
const statement = node.parent;
const fixable = statement.type === "ExpressionStatement";
context.report({
...(fixable
? { fix: (fixer): Rule.Fix => fixer.replaceText(statement, splitChain(context, node)) }
: {}),
messageId: "unexpected",
node,
});
};
const handlers: [string, (node: Rule.Node) => void][] = [["AssignmentExpression", onAssign]];
return Object.fromEntries(handlers);
},
meta: govlabMeta({
canonical: ["idiomatic-preference"],
description: "Disallow chained assignment (`a = b = c`); split into sequential statements",
fixable: "code",
messages: { unexpected: "Chained assignment is not allowed; assign each target in its own statement." },
ruleId: "no_chained_assignment",
type: "suggestion",
}),
} satisfies Rule.RuleModule;