core/quality/eslint/no-inline-types.eslint.rule.ts

core/quality/eslint/no-inline-types.eslint.rule.ts is a file in GovLab Quality. 65 lines of code and 13 definitions.

import type { Rule } from "eslint";
import { govlabMeta } from "#core/factories/eslint.factory";
import { inHostScope } from "#core/predicates/eslint.predicate";

const TYPE_TAGS = [
    "@type",
    "@typedef",
    "@interface",
    "@callback",
    "@enum",
    "@template",
    "@property",
    "@param",
    "@returns",
    "@return",
    "@throws",
    "@yields",
    "@yield",
];
const BOUNDARY_CHARS = new Set([" ", "\t", "{", "\n"]);

const tagMatches = function tagMatches(text: string, tag: string): boolean {
    let searchFrom = 0;
    while (searchFrom < text.length) {
        const idx = text.indexOf(tag, searchFrom);
        if (idx === -1) {
            return false;
        }
        const after = text.at(idx + tag.length);
        if (typeof after !== "string" || BOUNDARY_CHARS.has(after)) {
            return true;
        }
        searchFrom = idx + tag.length;
    }
    return false;
};

const firstTypeTag = function firstTypeTag(text: string): string | null {
    return TYPE_TAGS.find((tag) => tagMatches(text, tag)) ?? null;
};

export default {
    create(context: Rule.RuleContext): Rule.RuleListener {
        if (!inHostScope(context)) {
            return {};
        }
        const { sourceCode } = context;
        const onProgram = (): void => {
            for (const comment of sourceCode.getAllComments()) {
                const tag = firstTypeTag(comment.value);
                const { loc } = comment;
                if (tag !== null && loc) {
                    context.report({ data: { tag }, loc, messageId: "inlineType" });
                }
            }
        };
        const handlers: [string, () => void][] = [["Program", onProgram]];
        return Object.fromEntries(handlers);
    },
    meta: govlabMeta({
        canonical: ["type-safety"],
        description: "Forbid inline JSDoc type annotations; declare types at the source signature/interface",
        messages: {
            inlineType:
                'Inline JSDoc type tag "{{tag}}" detected. The type pipeline strips inline type comments and corrupts the file — declare the type at the function signature or interface, not in a comment.',
        },
        ruleId: "no_inline_types",
    }),
} satisfies Rule.RuleModule;