test.rules/codemods/selectors/heritage.selector.test.ts

test.rules/codemods/selectors/heritage.selector.test.ts is a file in Codebase Testing. 82 lines of code and 0 definitions.

import {
    baseChain,
    clausesOfToken,
    implementsClauses,
    memberNamesOf,
} from "@ssot/govlab/codemods/selectors/heritage.selector.ts";
import { describe, expect, it } from "vitest";
import ts from "typescript";

const programOf = function programOf(code: string): { checker: ts.TypeChecker; source: ts.SourceFile } {
    const fileName = "probe.ts";
    const source = ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true);
    const host: ts.CompilerHost = {
        fileExists: (name) => name === fileName,
        getCanonicalFileName: (name) => name,
        getCurrentDirectory: () => "",
        getDefaultLibFileName: () => "lib.d.ts",
        getNewLine: () => "\n",
        getSourceFile: (name) => (name === fileName ? source : undefined),
        readFile: (name) => (name === fileName ? code : undefined),
        useCaseSensitiveFileNames: () => true,
        writeFile: () => {},
    };
    const program = ts.createProgram({ host, options: {}, rootNames: [fileName] });
    return { checker: program.getTypeChecker(), source: program.getSourceFile(fileName) ?? source };
};

const classesIn = function classesIn(source: ts.SourceFile): ts.ClassDeclaration[] {
    return source.statements.filter((statement): statement is ts.ClassDeclaration => ts.isClassDeclaration(statement));
};

describe("clausesOfToken", () => {
    it("separates the extends clause from the implements clauses", () => {
        const { source } = programOf("interface Shape {}\nclass Base {}\nclass Leaf extends Base implements Shape {}");
        const leaf = classesIn(source).find((node) => node.name?.getText() === "Leaf");
        const target = leaf ?? classesIn(source)[0];
        if (target === undefined) {
            throw new Error("no class parsed");
        }
        expect(clausesOfToken(target, ts.SyntaxKind.ExtendsKeyword)).toHaveLength(1);
        expect(clausesOfToken(target, ts.SyntaxKind.ImplementsKeyword)).toHaveLength(1);
    });

    it("returns nothing for a class with no heritage", () => {
        const { source } = programOf("class Lone {}");
        const [lone] = classesIn(source);
        expect(lone === undefined ? [] : clausesOfToken(lone, ts.SyntaxKind.ExtendsKeyword)).toStrictEqual([]);
    });
});

describe("baseChain", () => {
    it("walks the full extends chain, nearest base first", () => {
        const { checker, source } = programOf(
            "class Root {}\nclass Middle extends Root {}\nclass Leaf extends Middle {}",
        );
        const leaf = classesIn(source).find((node) => node.name?.getText() === "Leaf");
        if (leaf === undefined) {
            throw new Error("Leaf did not parse");
        }
        expect(baseChain(checker, leaf).map((node) => node.name?.getText())).toStrictEqual(["Middle", "Root"]);
    });

    it("returns nothing for a class that extends nothing", () => {
        const { checker, source } = programOf("class Lone {}");
        const [lone] = classesIn(source);
        expect(lone === undefined ? [] : baseChain(checker, lone)).toStrictEqual([]);
    });
});

describe("implementsClauses", () => {
    it("gathers the class's own clauses and every one inherited up the chain", () => {
        const code =
            "interface A {}\ninterface B {}\nclass Base implements A {}\nclass Leaf extends Base implements B {}";
        const { checker, source } = programOf(code);
        const leaf = classesIn(source).find((node) => node.name?.getText() === "Leaf");
        if (leaf === undefined) {
            throw new Error("Leaf did not parse");
        }
        const names = implementsClauses(checker, leaf).map((clause) => clause.expression.getText());
        expect(names.toSorted()).toStrictEqual(["A", "B"]);
    });
});

describe("memberNamesOf", () => {
    it("names every member that has a name", () => {
        const { source } = programOf("class Holder { id = 1; run() {} }");
        const [holder] = classesIn(source);
        expect(holder === undefined ? [] : memberNamesOf(holder)).toStrictEqual(["id", "run"]);
    });
});