# rules/eslint/validated-construction.eslint.rule.ts

> 63 lines of code and 7 definitions.

Tree: Governance tree
Language: typescript
Canonical: https://banes-lab.com/anatomy/governance#file-governance-rules-eslint-validated-construction-eslint-rule-ts
Source text: https://banes-lab.com/source/governance/rules/eslint/validated-construction.eslint.rule.ts.txt

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/strings-vocabulary.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/strings-vocabulary.eslint.rule.ts.md).

## Definitions

- `isMethodCallOn` (lexical_declaration, line 8)
- `bindingIsValidated` (lexical_declaration, line 17)
- `newExpression` (method_definition, line 38, exported)
- `create` (method_definition, line 36, exported)
- `declarator` (lexical_declaration, line 23)
- `identifier` (lexical_declaration, line 29)
- `method` (lexical_declaration, line 39, exported)

## Uses

- [shared/factories/listener.factory.ts](https://banes-lab.com/source/governance/shared/factories/listener.factory.ts.md)
- [shared/selectors/syntax.selector.ts](https://banes-lab.com/source/governance/shared/selectors/syntax.selector.ts.md)

## Records this file checks

- [Invariant](https://banes-lab.com/records/architecture/invariant.md)

## Source

```typescript
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;
```
