test.rules/codemods/analyzers/sink.analyzer.test.ts
test.rules/codemods/analyzers/sink.analyzer.test.ts is a file in Codebase Testing. 93 lines of code and 0 definitions.
import { describe, expect, it } from "vitest";
import { disjoint, splice } from "@ssot/govlab/codemods/selectors/edit.selector.ts";
import type { Edit } from "@ssot/govlab/types/codemod.types.ts";
import type { WriteFinding } from "@ssot/govlab/types/analyzer.types.ts";
import { scanSourceFile } from "@ssot/govlab/codemods/analyzers/sink.analyzer.ts";
import ts from "typescript";
const FILE = "probe.ts";
const OWNER = "owner.ts";
const scanAs = function scanAs(file: string, code: string, declared: boolean): WriteFinding[] {
const source = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true);
const host: ts.CompilerHost = {
fileExists: (name) => name === file,
getCanonicalFileName: (name) => name,
getCurrentDirectory: () => "",
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
getNewLine: () => "\n",
getSourceFile: (name) => (name === file ? source : undefined),
readFile: (name) => (name === file ? code : undefined),
useCaseSensitiveFileNames: () => true,
writeFile: () => {},
};
const program = ts.createProgram({ host, options: {}, rootNames: [file] });
return scanSourceFile(program.getTypeChecker(), program.getSourceFile(file) ?? source, {
declaresWriter: () => declared,
owners: new Set([OWNER]),
});
};
const scan = function scan(code: string): WriteFinding[] {
return scanAs(FILE, code, true);
};
const rewrite = function rewrite(code: string): string {
const findings = scan(code).filter((finding) => finding.reason === null);
const edits: Edit[] = [
...(findings[0]?.importEdits ?? []),
...findings.map((finding) => ({ end: finding.end, replacement: finding.replacement, start: finding.start })),
];
return splice(code, disjoint(edits.toSorted((a, b) => b.start - a.start)));
};
describe("scanSourceFile", () => {
it("routes a sole raw write through the owner's writer and replaces the primitive import", () => {
const code = 'import { writeFileSync } from "node:fs";\nwriteFileSync(target, "x");\n';
expect(rewrite(code)).toBe(
'import { writeVerbatim } from "@govlab/canonical-write";\nwriteVerbatim(target, "x");\n',
);
});
it("keeps the other primitive imports and drops a text encoding argument", () => {
const code =
'import { readFileSync, writeFileSync } from "node:fs";\nwriteFileSync(target, readFileSync(source), "utf8");\n';
expect(rewrite(code)).toBe(
'import { writeVerbatim } from "@govlab/canonical-write";\nimport { readFileSync } from "node:fs";\nwriteVerbatim(target, readFileSync(source));\n',
);
});
it("blocks a write whose encoding is not text", () => {
const code = 'import { writeFileSync } from "node:fs";\nwriteFileSync(target, "x", "latin1");\n';
expect(scan(code)[0]?.reason).toContain("'latin1' encoding");
});
it("extends an existing import of the owner's package instead of adding a second one", () => {
const code =
'import { writeCanonicalJson } from "@govlab/canonical-write";\nimport { writeFileSync } from "node:fs";\nwriteFileSync(target, "x");\n';
expect(rewrite(code)).toBe(
'import { writeCanonicalJson, writeVerbatim } from "@govlab/canonical-write";\nwriteVerbatim(target, "x");\n',
);
});
it("blocks a write whose options object the owner's writer does not take, and keeps the import", () => {
const code = 'import { writeFileSync } from "node:fs";\nwriteFileSync(target, "x", { flag: "a" });\n';
const [finding] = scan(code);
expect(finding?.reason).toContain("3 arguments");
expect(rewrite(code)).toBe(code);
});
it("keeps the primitive import while a reference other than a call remains", () => {
const code =
'import { writeFileSync } from "node:fs";\nconst write = writeFileSync;\nwriteFileSync(target, "x");\n';
expect(rewrite(code)).toBe(
'import { writeVerbatim } from "@govlab/canonical-write";\nimport { writeFileSync } from "node:fs";\nconst write = writeFileSync;\nwriteVerbatim(target, "x");\n',
);
});
it("blocks every write in a file whose package does not declare the owner's package", () => {
const [finding] = scanAs(
FILE,
'import { writeFileSync } from "node:fs";\nwriteFileSync(target, "x");\n',
false,
);
expect(finding?.reason).toContain("does not declare");
});
it("leaves the owner module alone", () => {
expect(
scanAs(OWNER, 'import { writeFileSync } from "node:fs";\nwriteFileSync(target, "x");\n', true),
).toStrictEqual([]);
});
it("finds nothing when the primitive is not imported by name", () => {
expect(scan('import fs from "node:fs";\nfs.readFileSync(target);\n')).toStrictEqual([]);
});
});