# rules/eslint/strings-vocabulary.eslint.rule.ts

> 96 lines of code and 21 definitions.

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

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

## Definitions

- `report` (lexical_declaration, line 52, exported)
- `quasiTexts` (lexical_declaration, line 23)
- `reportKnown` (lexical_declaration, line 39, exported)
- `templateLiteral` (method_definition, line 77, exported)
- `create` (method_definition, line 33, exported)
- `literal` (method_definition, line 68, exported)
- `program` (method_definition, line 74, exported)
- `STRINGS_SUFFIX` (lexical_declaration, line 18)
- `SUBJECT_MARK` (lexical_declaration, line 19)
- `RETIRED_TERMS` (lexical_declaration, line 20)
- `KNOWN_TERMS` (lexical_declaration, line 21)
- `subjectOf` (lexical_declaration, line 27)
- `base` (lexical_declaration, line 28)
- `mapped` (lexical_declaration, line 37, exported)
- `code` (lexical_declaration, line 38, exported)
- `known` (lexical_declaration, line 40, exported)
- `source` (lexical_declaration, line 44, exported)
- `term` (lexical_declaration, line 56, exported)
- `retired` (lexical_declaration, line 60, exported)
- `canonical` (lexical_declaration, line 62, exported)
- `text` (lexical_declaration, line 69, exported)

## Uses

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

## Records this file checks

- [Semantic Consistency](https://banes-lab.com/records/architecture/semantic-consistency.md)

## Source

```typescript
import {
    BANNED_TERMS,
    KNOWN_VIOLATIONS,
    LEGAL_SUBJECTS,
    PROPER_NAMES,
    RETIRED_SYNONYMS,
} from "../../shared/manifests/vocabulary.manifest.ts";
import type { LocalRule, RuleContext, RuleListener, RuleNode } from "../../types/rule.types.ts";
import { firstKnownIn, firstTermIn, replaceKnown } from "../../shared/matchers/vocabulary.matcher.ts";
import { literalString, locOf, nodesAt, recordAt, stringIn } from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { basenameOf } from "../../shared/resolvers/anchor.resolver.ts";
import { codeLiteralsOf } from "../../shared/selectors/literal.selector.ts";
import { concernSuffix } from "../../shared/manifests/taxonomy.manifest.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";

const STRINGS_SUFFIX = concernSuffix("strings");
const SUBJECT_MARK = ".";
const RETIRED_TERMS: readonly string[] = [...RETIRED_SYNONYMS.keys()];
const KNOWN_TERMS: readonly string[] = [...KNOWN_VIOLATIONS.keys()];

const quasiTexts = function quasiTexts(node: AstNode): string[] {
    return nodesAt(node, "quasis").map((part) => stringIn(recordAt(part, "value"), "cooked"));
};

const subjectOf = function subjectOf(filename: string): string {
    const base = basenameOf(filename);
    return base.slice(0, base.indexOf(SUBJECT_MARK));
};

export default {
    create(context: RuleContext): RuleListener {
        if (!basenameOf(context.filename).endsWith(STRINGS_SUFFIX)) {
            return {};
        }
        const mapped = !LEGAL_SUBJECTS.has(subjectOf(context.filename));
        let code: ReadonlySet<AstNode> = new Set();
        const reportKnown = function reportKnown(node: AstNode, raw: RuleNode, text: string): void {
            const known = mapped ? firstKnownIn(text, KNOWN_TERMS, PROPER_NAMES) : null;
            if (known === null) {
                return;
            }
            const source = context.sourceCode.getText(raw);
            context.report({
                data: { canonical: KNOWN_VIOLATIONS.get(known) ?? known, term: known },
                fix: (fixer) => fixer.replaceText(raw, replaceKnown(source, KNOWN_VIOLATIONS, PROPER_NAMES)),
                loc: locOf(node),
                messageId: "knownViolation",
            });
        };
        const report = function report(node: AstNode, raw: RuleNode, text: string): void {
            if (code.has(node)) {
                return;
            }
            const term = firstTermIn(text, BANNED_TERMS);
            if (term !== null) {
                context.report({ data: { term }, loc: locOf(node), messageId: "bannedTerm" });
            }
            const retired = firstTermIn(text, RETIRED_TERMS);
            if (retired !== null) {
                const canonical = RETIRED_SYNONYMS.get(retired) ?? retired;
                context.report({ data: { canonical, term: retired }, loc: locOf(node), messageId: "retiredSynonym" });
            }
            reportKnown(node, raw, text);
        };
        return listener({
            literal(view, raw) {
                const text = literalString(view);
                if (text !== null) {
                    report(view, raw, text);
                }
            },
            program(view) {
                code = codeLiteralsOf(view);
            },
            templateLiteral(view, raw) {
                report(view, raw, quasiTexts(view).join(" "));
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({ detects: [], enforces: ["architecture:semantic-consistency"] }),
            description:
                "A user-visible string carries no term the vocabulary registry bans, no retired name for a concept, and no known violation. The registry holds the writing constitution as data. The banned terms are praise and marketing words, reported for a rewrite. The retired synonyms are names whose replacement is not a drop-in, reported with the canonical term. The known violations are exact phrases with an exact replacement, such as the names of the two parties: the model and the developer. They are fixed in place at word boundaries, matched without regard to case, with the leading capital kept. Legal documents keep their legal parties, so the subjects the registry declares legal are outside the known-violation mapping. A literal consumed as a code sample is code, not prose, and is not held to the registry.",
        },
        fixable: "code",
        messages: {
            bannedTerm:
                "The vocabulary registry bans '{{term}}' in user-visible copy. Restate the sentence without the term; the registry is data, so widening it is a maintainer edit, never a rewrite of this rule.",
            knownViolation:
                "'{{term}}' is a known violation with one exact replacement: '{{canonical}}'. The fix rewrites it in place; the mapping is data in the vocabulary registry.",
            retiredSynonym:
                "'{{term}}' is a retired name for a concept that carries one name across the site: write '{{canonical}}'. One concept with two names reads as two concepts; the synonym registry is data, so adding a retired name is a maintainer edit.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
