test.rules/codemods/analyzers/identifier.analyzer.test.ts
test.rules/codemods/analyzers/identifier.analyzer.test.ts is a file in Codebase Testing. 61 lines of code and 0 definitions.
import { describe, expect, it } from "vitest";
import type { IdentifierFinding as Finding } from "@ssot/govlab/types/analyzer.types.ts";
import { scanProgram } from "@ssot/govlab/codemods/analyzers/identifier.analyzer.ts";
import { sep } from "node:path";
import ts from "typescript";
const FILE = `${process.cwd().split(sep).join("/")}/probe.ts`;
const scan = function scan(code: string, fileName = FILE): Finding[] {
const source = ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true);
const host: ts.CompilerHost = {
fileExists: (name) => name === fileName,
getCanonicalFileName: (name) => name,
getCurrentDirectory: () => process.cwd(),
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
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] });
const resolved = program.getSourceFile(fileName) ?? source;
return scanProgram(program, [resolved]);
};
describe("scanProgram", () => {
it("finds a pascal-cased const and proposes the camel-cased name", () => {
const [finding] = scan("const WidgetFactory = () => 1;\nexport const use = WidgetFactory;");
expect(finding?.from).toBe("WidgetFactory");
expect(finding?.to).toBe("widgetFactory");
expect(finding?.reason).toBeNull();
});
it("leaves a constant-cased const alone, which is a different convention", () => {
expect(scan("const MAX_RETRIES = 3;\nexport const use = MAX_RETRIES;")).toStrictEqual([]);
});
it("leaves an already camel-cased const alone", () => {
expect(scan("const widgetFactory = () => 1;\nexport const use = widgetFactory;")).toStrictEqual([]);
});
it("collects every reference to the declaration as a rename location", () => {
const [finding] = scan(
"const WidgetFactory = () => 1;\nconst a = WidgetFactory;\nexport const b = [a, WidgetFactory];",
);
expect((finding?.locations.length ?? 0) >= 3).toBe(true);
});
it("blocks a rename whose target name is already bound in the file", () => {
const code =
"const widgetFactory = 1;\nconst WidgetFactory = 2;\nexport const use = [widgetFactory, WidgetFactory];";
const finding = scan(code).find((entry) => entry.from === "WidgetFactory");
expect(finding?.reason).toContain("already declares");
});
it("blocks a rename used in a shorthand property, where it would change the key", () => {
const code = "const WidgetFactory = 1;\nexport const bag = { WidgetFactory };";
const [finding] = scan(code);
expect(finding?.reason).toContain("shorthand property");
});
it("skips a generated file, whose source is regenerated rather than edited", () => {
const generated = `${process.cwd().split(sep).join("/")}/probe.generated.ts`;
expect(scan("const WidgetFactory = () => 1;", generated)).toStrictEqual([]);
});
it("ignores a mutable binding, since the convention governs constants", () => {
expect(scan("let WidgetFactory = 1;\nexport const use = WidgetFactory;")).toStrictEqual([]);
});
});