core/quality/eslint/function-style.eslint.rule.ts

core/quality/eslint/function-style.eslint.rule.ts is a file in GovLab Quality. 47 lines of code and 11 definitions.

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

type FnDecl = Extract<Rule.Node, { type: "FunctionDeclaration" }>;

const usedBeforeDefined = (context: Rule.RuleContext, node: FnDecl): boolean => {
    const declared = context.sourceCode.getDeclaredVariables(node).at(0);
    if (!declared) {
        return false;
    }
    const start = node.range?.[0] ?? 0;
    return declared.references.some((ref) => {
        const { range } = ref.identifier;
        return range ? range[0] < start : false;
    });
};

const isOverloadImplementation = (context: Rule.RuleContext, node: FnDecl): boolean => {
    const declared = context.sourceCode.getDeclaredVariables(node).at(0);
    return declared?.defs.some((def) => ["TSDeclareFunction"].includes(def.node.type)) === true;
};

export default {
    create(context: Rule.RuleContext): Rule.RuleListener {
        const onFunction = (node: Rule.Node): void => {
            if (node.type !== "FunctionDeclaration" || node.parent.type === "ExportDefaultDeclaration") {
                return;
            }
            if (isOverloadImplementation(context, node)) {
                return;
            }
            const name = context.sourceCode.getDeclaredVariables(node).at(0)?.name;
            const fix =
                typeof name === "string" && !usedBeforeDefined(context, node)
                    ? (fixer: Rule.RuleFixer): Rule.Fix =>
                          fixer.replaceText(node, `const ${name} = ${context.sourceCode.getText(node)};`)
                    : null;
            context.report({ ...(fix === null ? {} : { fix }), messageId: "expression", node });
        };
        const handlers: [string, (node: Rule.Node) => void][] = [["FunctionDeclaration", onFunction]];
        return Object.fromEntries(handlers);
    },
    meta: govlabMeta({
        canonical: ["idiomatic-preference"],
        description: "Require a function expression over a function declaration",
        fixable: "code",
        messages: { expression: "Expected a function expression." },
        ruleId: "function_style",
        type: "suggestion",
    }),
} satisfies Rule.RuleModule;