rules/eslint/validated-construction.eslint.rule.ts
rules/eslint/validated-construction.eslint.rule.ts is a file in GovLab Extension Host. 63 lines of code and 7 definitions.
import type { LocalRule, RuleContext, RuleListener, RuleNode } from "../../types/rule.types.ts";
import { asNode, isType, nameOf, nodeAt } from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { VALIDATING_CLASSES } from "../../shared/manifests/invariant.manifest.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";
const isMethodCallOn = function isMethodCallOn(member: AstNode | null, object: AstNode, method: string): boolean {
return (
isType(member, "MemberExpression") &&
nodeAt(member, "object") === object &&
nameOf(nodeAt(member, "property")) === method &&
isType(nodeAt(member, "parent"), "CallExpression")
);
};
const bindingIsValidated = function bindingIsValidated(
context: RuleContext,
view: AstNode,
raw: RuleNode,
method: string,
): boolean {
const declarator = nodeAt(view, "parent");
if (!isType(declarator, "VariableDeclarator") || nodeAt(declarator, "init") !== view || raw.parent === null) {
return false;
}
return context.sourceCode.getDeclaredVariables(raw.parent).some((variable) =>
variable.references.some((reference) => {
const identifier = asNode(reference.identifier);
return identifier !== null && isMethodCallOn(nodeAt(identifier, "parent"), identifier, method);
}),
);
};
export default {
create(context: RuleContext): RuleListener {
return listener({
newExpression(view, raw) {
const method = VALIDATING_CLASSES.get(nameOf(nodeAt(view, "callee")));
if (method === undefined) {
return;
}
if (
isMethodCallOn(nodeAt(view, "parent"), view, method) ||
bindingIsValidated(context, view, raw, method)
) {
return;
}
context.report({ data: { method }, messageId: "unvalidated", node: raw });
},
});
},
meta: {
docs: {
checks: defineCheck({ detects: [], enforces: ["architecture:invariant"] }),
description:
"An instance of a class that checks its invariants in a separate method is validated before it is used. Construction alone runs none of the checks, so an unvalidated instance is indistinguishable from a valid one until something downstream misreads it.",
},
messages: {
unvalidated:
"This instance is constructed without its validation method '{{method}}' running on it. Call '{{method}}()' on the new instance, chained or on the binding it is assigned to, before the instance leaves the construction site.",
},
schema: [],
type: "problem",
},
} satisfies LocalRule;