# rules/eslint/no-swallowed-failure.eslint.rule.ts

> 93 lines of code and 14 definitions.

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

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/no-string-literal-ids.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/no-string-literal-ids.eslint.rule.ts.md) and before [rules/eslint/no-test-without-subject.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/no-test-without-subject.eslint.rule.ts.md).

## Definitions

- `dropsFailure` (lexical_declaration, line 41)
- `readsIn` (lexical_declaration, line 31)
- `throwsIn` (lexical_declaration, line 21)
- `handler` (lexical_declaration, line 65, exported)
- `catchClause` (method_definition, line 74, exported)
- `callExpression` (method_definition, line 64, exported)
- `rejectionHandlerOf` (lexical_declaration, line 51)
- `create` (method_definition, line 62, exported)
- `HANDLER_TYPES` (lexical_declaration, line 15)
- `REJECTION_SLOTS` (lexical_declaration, line 16)
- `found` (lexical_declaration, line 32)
- `callee` (lexical_declaration, line 52)
- `slot` (lexical_declaration, line 53)
- `[binding = null]` (lexical_declaration, line 69, 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)

## Used by

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

## Records this file checks

- [Error Handling](https://banes-lab.com/records/architecture/error-handling.md)
- [Unobservable Failure](https://banes-lab.com/records/architecture/unobservable-failure.md)

## Source

```typescript
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import {
    argumentAt,
    calleeName,
    isType,
    nameOf,
    nodeAt,
    nodesAt,
    walk,
} from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";

const HANDLER_TYPES: ReadonlySet<string> = new Set(["ArrowFunctionExpression", "FunctionExpression"]);
const REJECTION_SLOTS: ReadonlyMap<string, number> = new Map([
    ["catch", 0],
    ["then", 1],
]);

const throwsIn = function throwsIn(body: AstNode | null): boolean {
    let found = false;
    if (body !== null) {
        walk(body, (node) => {
            found ||= node.type === "ThrowStatement";
        });
    }
    return found;
};

const readsIn = function readsIn(body: AstNode | null, name: string): boolean {
    let found = false;
    if (body !== null) {
        walk(body, (node) => {
            found ||= isType(node, "Identifier") && nameOf(node) === name;
        });
    }
    return found;
};

const dropsFailure = function dropsFailure(binding: AstNode | null, body: AstNode | null): boolean {
    if (throwsIn(body)) {
        return false;
    }
    if (binding === null) {
        return true;
    }
    return isType(binding, "Identifier") && !readsIn(body, nameOf(binding));
};

const rejectionHandlerOf = function rejectionHandlerOf(call: AstNode): AstNode | null {
    const callee = nodeAt(call, "callee");
    const slot = REJECTION_SLOTS.get(calleeName(call));
    if (!isType(callee, "MemberExpression") || slot === undefined) {
        return null;
    }
    const handler = argumentAt(call, slot);
    return handler !== null && HANDLER_TYPES.has(handler.type) ? handler : null;
};

export default {
    create(context: RuleContext): RuleListener {
        return listener({
            callExpression(view, node) {
                const handler = rejectionHandlerOf(view);
                if (handler === null) {
                    return;
                }
                const [binding = null] = nodesAt(handler, "params");
                if (dropsFailure(binding, nodeAt(handler, "body"))) {
                    context.report({ messageId: "droppedRejection", node });
                }
            },
            catchClause(view, node) {
                if (dropsFailure(nodeAt(view, "param"), nodeAt(view, "body"))) {
                    context.report({ messageId: "droppedFailure", node });
                }
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({
                detects: ["architecture:unobservable-failure"],
                enforces: ["architecture:error-handling"],
            }),
            description:
                "A caught failure is rethrown or used. A catch clause or rejection handler that neither throws nor reads what it caught turns a defect into silence, and silence cannot be fixed.",
        },
        messages: {
            droppedFailure:
                "This catch drops the failure it caught: it neither rethrows nor reads the error. Rethrow it with context, report it through the module's reporter with the error attached, or remove the catch so the failure surfaces.",
            droppedRejection:
                "This rejection handler drops the failure it received: it neither rethrows nor reads the reason. Take the reason as a parameter and report it with the error attached, rethrow it, or let the rejection propagate.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
