rules/eslint/no-inline-asset-path.eslint.rule.ts

rules/eslint/no-inline-asset-path.eslint.rule.ts is a file in GovLab Extension Host. 102 lines of code and 19 definitions.

import { ASSET_MODULE_NAME, ASSET_PATH_HINTS } from "../../shared/manifests/asset.manifest.ts";
import {
    BUILD_SCRIPT_ROOT_SEGMENT,
    GOVERNED_ROOT,
    SCRIPT_ROOT_SEGMENT,
    basenameOf,
    normalizePath,
} from "../../shared/resolvers/anchor.resolver.ts";
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { isModuleSpecifier, isStandaloneScript } from "../../shared/predicates/location.predicate.ts";
import {
    isType,
    literalString,
    locOf,
    nameOf,
    nodeAt,
    recordAt,
    stringIn,
} from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { concernSuffix } from "../../shared/manifests/taxonomy.manifest.ts";
import { containerPath } from "../../shared/resolvers/container.resolver.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";

const CORE_CONTAINER = containerPath("core", GOVERNED_ROOT);
const ASSET_MODULE_PREFIX = `/${CORE_CONTAINER.slice(CORE_CONTAINER.indexOf("/") + 1)}${ASSET_MODULE_NAME}/`;
const REGISTRY_SUFFIX = concernSuffix("manifest");

const ENV_BASE_URL = "import.meta.env.BASE_URL";
const ENV_NAMESPACE = "env";
const BASE_URL_PROPERTY = "BASE_URL";

const ALLOWED_SEGMENTS = [ASSET_MODULE_PREFIX, SCRIPT_ROOT_SEGMENT, BUILD_SCRIPT_ROOT_SEGMENT];

const isAllowedFile = function isAllowedFile(filename: string): boolean {
    const norm = normalizePath(filename);
    if (ALLOWED_SEGMENTS.some((segment) => norm.includes(segment))) {
        return true;
    }
    return isStandaloneScript(norm) || basenameOf(norm).endsWith(REGISTRY_SUFFIX);
};

const pathHintIn = function pathHintIn(value: string): string | null {
    return ASSET_PATH_HINTS.find((hint) => value.includes(hint)) ?? null;
};

const isImportMeta = function isImportMeta(node: AstNode | null): boolean {
    if (!isType(node, "MetaProperty")) {
        return false;
    }
    return nameOf(nodeAt(node, "meta")) === "import" && nameOf(nodeAt(node, "property")) === "meta";
};

const isEnvBaseUrl = function isEnvBaseUrl(node: AstNode): boolean {
    if (node.type !== "MemberExpression" || nameOf(nodeAt(node, "property")) !== BASE_URL_PROPERTY) {
        return false;
    }
    const object = nodeAt(node, "object");
    if (!isType(object, "MemberExpression") || nameOf(nodeAt(object, "property")) !== ENV_NAMESPACE) {
        return false;
    }
    return isImportMeta(nodeAt(object, "object"));
};

export default {
    create(context: RuleContext): RuleListener {
        if (isAllowedFile(context.filename)) {
            return {};
        }
        return listener({
            literal(view) {
                const value = literalString(view);
                const hint = value === null || isModuleSpecifier(view) ? null : pathHintIn(value);
                if (hint !== null) {
                    context.report({ data: { hint }, loc: locOf(view), messageId: "pathLiteral" });
                }
            },
            memberExpression(view, node) {
                if (isEnvBaseUrl(view)) {
                    context.report({ data: { expr: ENV_BASE_URL }, messageId: "envBaseUrl", node });
                }
            },
            templateElement(view) {
                const hint = pathHintIn(stringIn(recordAt(view, "value"), "cooked"));
                if (hint !== null) {
                    context.report({ data: { hint }, loc: locOf(view), messageId: "pathLiteral" });
                }
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({
                detects: ["architecture:hardcoded-configuration"],
                enforces: ["architecture:single-source-of-truth"],
            }),
            description:
                "An asset location, an environment import, a URL or a route written as a literal at a call site is banned; each is declared once in the asset catalog and reached through its accessor. These are deployment facts, not code facts — they change on a different schedule than the code that consumes them, and a literal ties the two together. The location shapes the rule looks for are data in the asset manifest, and a registry module is exempt because it is where such a shape is declared. A standalone server script ships as one file that can import nothing, so it is its own catalog.",
        },
        messages: {
            envBaseUrl:
                "Direct `{{expr}}` access is banned outside the asset catalog. The catalog's accessors apply the deployment base internally, so reaching for the environment here re-derives a fact the catalog already owns.",
            pathLiteral:
                "Inline location '{{hint}}' is banned outside the asset catalog. Declare it there once and reach it through the catalog's accessor — a deployment fact spelled at a call site changes on a different schedule than the code around it.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;