rules/eslint/no-inline-icon-options.eslint.rule.ts
rules/eslint/no-inline-icon-options.eslint.rule.ts is a file in GovLab Extension Host. 56 lines of code and 11 definitions.
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { isType, literalString, nameOf, nodeAt, nodesAt } from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.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 ICON_SHAPE_KEYS = ["viewBox", "children"];
const EXEMPT_BASENAME_SUFFIXES = [concernSuffix("icons"), ".test.ts", ".spec.ts"];
const isExemptFile = function isExemptFile(filename: string): boolean {
const basename = basenameOf(filename);
return EXEMPT_BASENAME_SUFFIXES.some((suffix) => basename.endsWith(suffix));
};
const propertyKeys = function propertyKeys(objectExpr: AstNode): Set<string> {
const found = new Set<string>();
for (const prop of nodesAt(objectExpr, "properties")) {
if (prop.type !== "Property") {
continue;
}
const key = nodeAt(prop, "key");
const keyName = isType(key, "Identifier") ? nameOf(key) : literalString(key);
if (keyName !== null && keyName !== "") {
found.add(keyName);
}
}
return found;
};
export default {
create(context: RuleContext): RuleListener {
if (isExemptFile(context.filename)) {
return {};
}
return listener({
objectExpression(view, node) {
const keys = propertyKeys(view);
if (!ICON_SHAPE_KEYS.every((required) => keys.has(required))) {
return;
}
context.report({ messageId: "inlineIconOpts", node });
},
});
},
meta: {
docs: {
checks: defineCheck({ detects: [], enforces: ["architecture:single-source-of-truth"] }),
description:
"An object literal carrying the structural signature of declarative asset data is banned outside the modules declared to hold it. Such data has a review axis of its own — whoever owns the asset set reviews it as a set, not scattered through the code that happens to consume each piece. This fires only where that shape actually occurs, so a project with no such assets never sees it. Tests and the declaring modules are exempt.",
},
messages: {
inlineIconOpts:
"An object literal here carries the structural signature of declarative asset data. Move it into the module declared for that concern and import it by name — the set is reviewed and audited as a set, which is impossible once its members are inlined at their call sites.",
},
schema: [],
type: "problem",
},
} satisfies LocalRule;