# shared/analyzers/rule.analyzer.ts

> 98 lines of code and 22 definitions.

Tree: Governance tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/governance#file-governance-shared-analyzers-rule-analyzer-ts
Source text: https://banes-lab.com/source/governance/shared/analyzers/rule.analyzer.ts.txt

Listed in [shared/analyzers](https://banes-lab.com/api/source/governance/shared/analyzers.md), after [shared/analyzers/location.analyzer.ts](https://banes-lab.com/source/governance/shared/analyzers/location.analyzer.ts.md) and before [shared/analyzers/segment.analyzer.ts](https://banes-lab.com/source/governance/shared/analyzers/segment.analyzer.ts.md).

## Definitions

- `textOf` (lexical_declaration, line 19, exported)
- `propertyNamed` (lexical_declaration, line 34, exported)
- `propertiesOf` (lexical_declaration, line 30, exported)
- `isNonEmptyString` (lexical_declaration, line 38, exported)
- `keyNameOf` (lexical_declaration, line 26)
- `declaredIds` (lexical_declaration, line 98, exported)
- `reportedIds` (lexical_declaration, line 83, exported)
- `collectRuleFile` (lexical_declaration, line 54, exported)
- `collectRuleObject` (lexical_declaration, line 78, exported)
- `GUARD_IMPORTS` (lexical_declaration, line 15)
- `ANY_KEYWORD` (lexical_declaration, line 16)
- `REPORT_METHOD` (lexical_declaration, line 17)
- `anyNodes` (lexical_declaration, line 55, exported)
- `guards` (lexical_declaration, line 56, exported)
- `ruleObjects` (lexical_declaration, line 57, exported)
- `names` (lexical_declaration, line 70, exported)
- `metaProperty` (lexical_declaration, line 79, exported)
- `messageId` (lexical_declaration, line 89, exported)
- `value` (lexical_declaration, line 90, exported)
- `ids` (lexical_declaration, line 99, exported)
- `meta` (lexical_declaration, line 101, exported)
- `messages` (lexical_declaration, line 102, exported)

## Uses

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

## Used by

- [rules/eslint/closure-rule-shape.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-rule-shape.eslint.rule.ts.md)

## Source

```typescript
import type { RuleFileShape, RuleObjectShape } from "../../types/rule.types.ts";
import {
    argumentAt,
    calleeName,
    literalString,
    nameOf,
    nodeAt,
    nodesAt,
    recordAt,
    stringIn,
    walk,
} from "../selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";

const GUARD_IMPORTS = new Set(["isGovernedFile"]);
const ANY_KEYWORD = "TSAnyKeyword";
const REPORT_METHOD = "report";

export const textOf = function textOf(node: AstNode | null): string {
    if (node === null) {
        return "";
    }
    return node.type === "Identifier" ? nameOf(node) : (literalString(node) ?? "");
};

const keyNameOf = function keyNameOf(property: AstNode): string {
    return textOf(nodeAt(property, "key"));
};

export const propertiesOf = function propertiesOf(node: AstNode | null): AstNode[] {
    return nodesAt(node, "properties").filter((property) => property.type === "Property");
};

export const propertyNamed = function propertyNamed(node: AstNode | null, name: string): AstNode | null {
    return propertiesOf(node).find((property) => keyNameOf(property) === name) ?? null;
};

export const isNonEmptyString = function isNonEmptyString(node: AstNode | null): boolean {
    if (node === null) {
        return false;
    }
    if (node.type === "Literal") {
        return (literalString(node) ?? "").trim().length > 0;
    }
    if (node.type === "TemplateLiteral") {
        return nodesAt(node, "quasis").some((quasi) => stringIn(recordAt(quasi, "value"), "cooked").trim().length > 0);
    }
    if (node.type === "BinaryExpression") {
        return isNonEmptyString(nodeAt(node, "left")) || isNonEmptyString(nodeAt(node, "right"));
    }
    return false;
};

export const collectRuleFile = function collectRuleFile(program: AstNode): RuleFileShape {
    const anyNodes: AstNode[] = [];
    const guards: AstNode[] = [];
    const ruleObjects: AstNode[] = [];
    walk(program, (node) => {
        if (node.type === ANY_KEYWORD) {
            anyNodes.push(node);
            return;
        }
        if (node.type === "ImportSpecifier" && GUARD_IMPORTS.has(textOf(nodeAt(node, "imported")))) {
            guards.push(node);
            return;
        }
        if (node.type !== "ObjectExpression") {
            return;
        }
        const names = new Set(propertiesOf(node).map(keyNameOf));
        if (names.has("create") && names.has("meta")) {
            ruleObjects.push(node);
        }
    });
    return { anyNodes, guards, ruleObjects };
};

export const collectRuleObject = function collectRuleObject(ruleObject: AstNode): RuleObjectShape {
    const metaProperty = propertyNamed(ruleObject, "meta");
    return { meta: nodeAt(metaProperty, "value"), metaProperty };
};

export const reportedIds = function reportedIds(program: AstNode): Set<string> {
    const ids = new Set<string>();
    walk(program, (node) => {
        if (node.type !== "CallExpression" || calleeName(node) !== REPORT_METHOD) {
            return;
        }
        const messageId = propertyNamed(argumentAt(node, 0), "messageId");
        const value = messageId === null ? null : literalString(nodeAt(messageId, "value"));
        if (value !== null && value !== "") {
            ids.add(value);
        }
    });
    return ids;
};

export const declaredIds = function declaredIds(ruleObjects: readonly AstNode[]): Set<string> {
    const ids = new Set<string>();
    for (const ruleObject of ruleObjects) {
        const meta = nodeAt(propertyNamed(ruleObject, "meta"), "value");
        const messages = meta === null ? null : propertyNamed(meta, "messages");
        for (const name of propertiesOf(nodeAt(messages, "value")).map(keyNameOf)) {
            ids.add(name);
        }
    }
    return ids;
};
```
