# rules/eslint/markup-through-builder.eslint.rule.ts

> 53 lines of code and 7 definitions.

Tree: GovLab Extension Host
Language: typescript
Canonical: https://banes-lab.com/anatomy/governance#file-governance-rules-eslint-markup-through-builder-eslint-rule-ts
Source text: https://banes-lab.com/source/governance/rules/eslint/markup-through-builder.eslint.rule.ts.txt

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/file-length.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/file-length.eslint.rule.ts.md) and before [rules/eslint/no-comments.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/no-comments.eslint.rule.ts.md).

## Definitions

- `buildersSpelled` (lexical_declaration, line 12)
- `create` (method_definition, line 23, exported)
- `program` (method_definition, line 29, exported)
- `templateLiteral` (method_definition, line 32, exported)
- `COPY_SUFFIX` (lexical_declaration, line 10)
- `text` (lexical_declaration, line 16)
- `declared` (lexical_declaration, line 27, exported)

## Contained in

- [rules/eslint](https://banes-lab.com/anatomy/governance/folder-governance-rules-eslint.md)

## 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)

## Enforces

- [Single Source of Truth](https://banes-lab.com/records/architecture/single-source-of-truth.md)

## Detector for

- [Do Not Repeat Yourself](https://banes-lab.com/records/architecture/duplicate-code.md)

## Linked from

- [rules/eslint](https://banes-lab.com/anatomy/governance/folder-governance-rules-eslint.md)
- [shared/factories](https://banes-lab.com/anatomy/governance/folder-governance-shared-factories.md)
- [shared/selectors](https://banes-lab.com/anatomy/governance/folder-governance-shared-selectors.md)

## Source

```typescript
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { declaredAtTop, nodesAt, recordAt, stringIn } from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { MARKUP_BUILDERS } from "../../shared/manifests/invariant.manifest.ts";
import { basenameOf } from "../../shared/resolvers/anchor.resolver.ts";
import { concernSuffix } from "../../shared/manifests/taxonomy.manifest.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";

const COPY_SUFFIX = concernSuffix("strings");

const buildersSpelled = function buildersSpelled(view: AstNode): string[] {
    if (nodesAt(view, "expressions").length === 0) {
        return [];
    }
    const text = nodesAt(view, "quasis")
        .map((quasi) => stringIn(recordAt(quasi, "value"), "cooked"))
        .join("");
    return [...MARKUP_BUILDERS].filter(([opening]) => text.includes(opening)).map(([, builder]) => builder);
};

export default {
    create(context: RuleContext): RuleListener {
        if (basenameOf(context.filename).endsWith(COPY_SUFFIX)) {
            return {};
        }
        let declared: ReadonlySet<string> = new Set();
        return listener({
            program(view) {
                declared = declaredAtTop(view);
            },
            templateLiteral(view, node) {
                for (const builder of buildersSpelled(view)) {
                    if (!declared.has(builder)) {
                        context.report({ data: { builder }, messageId: "inlineMarkup", node });
                    }
                }
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({
                detects: ["architecture:duplicate-code"],
                enforces: ["architecture:single-source-of-truth"],
            }),
            description:
                "Code that builds a markup element from computed values calls the one builder declared for that element. The elements and their builders are data in the markup builders registry. A template that interpolates values into the element by hand is a second copy of the builder. Copy in a strings module is authored text and is not checked.",
        },
        messages: {
            inlineMarkup: "This template builds markup that '{{builder}}' builds. Call '{{builder}}' with the values instead.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
