rules/eslint/markup-through-builder.eslint.rule.ts
rules/eslint/markup-through-builder.eslint.rule.ts is a file in GovLab Extension Host. 53 lines of code and 7 definitions.
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;