Files
Aiden Bai 8c2f03aea9 feat: make React cleanup first-class (#1624)
* feat: make React cleanup first-class

* refactor: remove editor integrations

* fix: harden React cleanup analysis

* fix: detect default export duplication roots

* fix: unwrap typed duplication roots

* feat: add opt-in project analysis rules

* fix: canonicalize project analysis paths

* fix: harden project analysis precision

* fix: recognize cross-platform project entries

* fix: eliminate project analysis false positives

* fix: harden project analysis reachability

* fix: canonicalize project analysis inputs

* fix: resolve project analysis review findings

* fix: eliminate residual project analysis false positives

* fix: ignore commented registry previews

* fix: eliminate project analysis false positives

* fix: normalize project analysis paths across platforms

* fix: normalize Nextra theme path identity

* test: canonicalize convention fixture paths

* fix: preserve project analysis provenance

* fix: harden project analysis precision

* fix: honor project analysis boundaries

* fix: recognize conditional config plugins

* fix: recognize executable project references

* fix: recognize Stencil tool contracts

* fix: recognize nested tool references

* fix: recognize project setup contracts

* fix: recognize generated and local package consumers

* fix: recognize static template package references

* fix: recognize nested package runtime contracts

* fix: close project analysis parser gaps

* fix: parse project conventions structurally

* refactor: replace structural scanners with parsers

* fix: recognize functional Next CSS config

* fix: close remaining project analysis gaps

* fix: apply tag filters to project analysis

* fix: preserve embedded source positions

* fix: validate static config helper bindings

* fix: bound runtime directory discovery

* fix: close final dependency analysis gaps

* fix: preserve declaration dependency references

* chore: refresh generated rule metadata

* fix: make project analysis portable and bounded

* test: stabilize cleanup scaling guard

* refactor: parse project syntax with oxc

* fix: normalize native filesystem paths

* fix: separate path identity from report paths

* fix: match project files by filesystem identity

* fix: match build glob files by package identity

* fix: use native path keys for file identity

* fix: canonicalize Windows file identities

* fix: canonicalize package ownership paths

* test: inspect Windows path identities

* test: trace Windows package ownership

* fix: keep Windows path identities consistent

* fix: classify test contracts by normalized path

* fix: scope test contracts by canonical package path

* fix: keep test package graphs conservative

* test: keep React complexity advisory
2026-08-13 16:26:08 -07:00

664 lines
24 KiB
TypeScript

import * as fs from "node:fs";
import os from "node:os";
import * as path from "node:path";
import { afterAll, describe, expect, it, vi } from "vite-plus/test";
import { inspect } from "../src/inspect.js";
import { clearConfigCache } from "@react-doctor/core";
import {
commitAll,
initGitRepo,
setupReactProject,
writeFile,
writeJson,
} from "./regressions/_helpers.js";
const FIXTURES_DIRECTORY = path.resolve(
import.meta.dirname,
"..",
"..",
"core",
"tests",
"fixtures",
);
const buildDuplicateCard = (
componentName: string,
title: string,
valueName: string,
rootElementName = "section",
): string => `
export const ${componentName} = () => (
<${rootElementName} className="card">
<header><h2>${title}</h2></header>
<main><Value value={${valueName}} /></main>
<footer><Button /></footer>
</${rootElementName}>
);
`;
vi.mock("ora", () => ({
default: () => ({
text: "",
start: function () {
return this;
},
stop: function () {
return this;
},
succeed: () => {},
fail: () => {},
}),
}));
const noReactTempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-test-"));
fs.writeFileSync(
path.join(noReactTempDirectory, "package.json"),
JSON.stringify({ name: "no-react", dependencies: {} }),
);
afterAll(() => {
fs.rmSync(noReactTempDirectory, { recursive: true, force: true });
});
describe("inspect", () => {
it("completes without throwing on a valid React project", async () => {
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
try {
await inspect(path.join(FIXTURES_DIRECTORY, "basic-react"), {
lint: true,
deadCode: false,
});
} finally {
consoleSpy.mockRestore();
}
});
it("lints every supported source extension in an explicit-path scan", async () => {
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-explicit-sources-"));
try {
const projectDirectory = setupReactProject(projectRoot, "app");
const extensions = ["ts", "tsx", "js", "jsx", "mts", "mjs"];
const includePaths = extensions.map((extension) => `src/conditional-hook.${extension}`);
for (const relativePath of includePaths) {
writeFile(
path.join(projectDirectory, relativePath),
'import { useState } from "react";\nexport const useConditional = (enabled) => { if (enabled) useState(0); };\n',
);
}
const result = await inspect(projectDirectory, {
lint: true,
deadCode: false,
noScore: true,
silent: true,
includePaths,
});
expect(result.scannedFileCount).toBe(includePaths.length);
expect(result.analyzedFiles?.toSorted()).toEqual(includePaths.toSorted());
expect(
result.diagnostics
.filter((diagnostic) => diagnostic.rule === "rules-of-hooks")
.map((diagnostic) => path.extname(diagnostic.filePath))
.toSorted(),
).toEqual(extensions.map((extension) => `.${extension}`).toSorted());
} finally {
consoleSpy.mockRestore();
fs.rmSync(projectRoot, { recursive: true, force: true });
}
});
it("throws when React dependency is missing", async () => {
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
try {
await expect(inspect(noReactTempDirectory, { lint: true })).rejects.toThrow(
"No React dependency found",
);
} finally {
consoleSpy.mockRestore();
}
});
// Regression (#552): a Preact project has no `react` package, so the run
// gate must let it through instead of aborting with "No React dependency".
it("does NOT throw for a Preact project without a react dependency", async () => {
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const preactDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-preact-"));
try {
fs.writeFileSync(
path.join(preactDirectory, "package.json"),
JSON.stringify({ name: "preact-app", dependencies: { preact: "^10.22.0" } }),
);
fs.writeFileSync(
path.join(preactDirectory, "App.tsx"),
"export function App() { return <div>hi</div>; }\n",
);
const result = await inspect(preactDirectory, { lint: true });
expect(result.project.preactVersion).toBe("^10.22.0");
expect(result.project.reactVersion).toBe(null);
} finally {
consoleSpy.mockRestore();
fs.rmSync(preactDirectory, { recursive: true, force: true });
}
});
it("skips lint when option is disabled", async () => {
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
try {
await inspect(path.join(FIXTURES_DIRECTORY, "basic-react"), {
lint: false,
deadCode: false,
});
} finally {
consoleSpy.mockRestore();
}
});
it("completes lint within the timeout budget", async () => {
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
try {
const startTime = performance.now();
await inspect(path.join(FIXTURES_DIRECTORY, "basic-react"), {
lint: true,
deadCode: false,
});
const elapsedMilliseconds = performance.now() - startTime;
expect(elapsedMilliseconds).toBeLessThan(30_000);
} finally {
consoleSpy.mockRestore();
}
});
// Regression: when the CLI passes `configOverride`, inspect() must trust
// the directory it was given and skip the rootDir redirect — otherwise
// an ancestor config with `rootDir: "apps/web"` would re-route every
// workspace-package scan back to apps/web. (Bugbot review #200.)
it("does NOT re-apply rootDir redirect when configOverride is supplied", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-rootdir-override-"));
try {
const adminProjectDirectory = setupReactProject(tempDirectory, "admin");
setupReactProject(tempDirectory, "web");
fs.writeFileSync(
path.join(tempDirectory, "doctor.config.json"),
JSON.stringify({ rootDir: "web" }),
);
const result = await inspect(adminProjectDirectory, {
lint: false,
deadCode: false,
configOverride: null,
});
expect(result.project.rootDirectory).toBe(adminProjectDirectory);
} finally {
consoleSpy.mockRestore();
fs.rmSync(tempDirectory, { recursive: true, force: true });
}
});
// Counterpart: when no configOverride is supplied (direct programmatic
// inspect() call), rootDir redirection IS honored — same contract as
// diagnose().
it("DOES apply rootDir redirect when called without configOverride", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-rootdir-honor-"));
try {
const webProjectDirectory = setupReactProject(tempDirectory, "web");
setupReactProject(tempDirectory, "admin");
fs.writeFileSync(
path.join(tempDirectory, "doctor.config.json"),
JSON.stringify({ rootDir: "web" }),
);
const result = await inspect(tempDirectory, {
lint: false,
deadCode: false,
});
expect(result.project.rootDirectory).toBe(webProjectDirectory);
} finally {
consoleSpy.mockRestore();
fs.rmSync(tempDirectory, { recursive: true, force: true });
}
});
// Regression: `resolveCliInspectOptions` leaves `noScore` undefined unless a
// flag opted out, so a `doctor.config` opt-out must be honored by inspect()'s
// merge layer (`inputOptions.noScore ?? userConfig?.noScore`). Reverting that
// merge to ignore config would silently re-enable scoring for opted-out users.
it("honors config-file noScore (score skipped) when no flag is passed", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-noscore-config-"));
try {
const projectDirectory = setupReactProject(tempDirectory, "app");
const scanOptions = { lint: false, deadCode: false, silent: true } as const;
// Control: no config → scoring on (the merge layer's `?? false` default).
const withScore = await inspect(projectDirectory, scanOptions);
expect(withScore.score).not.toBeNull();
// Config opt-out with NO flag → inherited by the merge layer → score skipped.
clearConfigCache();
writeJson(path.join(projectDirectory, "doctor.config.json"), { noScore: true });
const withoutScore = await inspect(projectDirectory, scanOptions);
expect(withoutScore.score).toBeNull();
} finally {
consoleSpy.mockRestore();
fs.rmSync(tempDirectory, { recursive: true, force: true });
}
});
// Regression: the baseline base scan materializes only the changed source +
// config into a temp tree (no node_modules / plugin files), so it MUST resolve
// a relative `config.plugins` entry from the REAL config dir. When that
// threading was hardcoded to null, the base side dropped the plugin, its
// finding vanished from `baseTotalCount`, and head's finding was mislabeled as
// newly introduced (gating CI on a pre-existing issue).
it("resolves config.plugins from the real config dir for the baseline base scan", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-baseline-plugin-"));
const forbiddenWordPlugin = `
module.exports = {
meta: { name: "team-conventions" },
rules: {
"no-forbidden-word": {
create: (context) => ({
JSXText(node) {
if (typeof node.value === "string" && node.value.includes("FORBIDDEN")) {
context.report({ node, message: "'FORBIDDEN' is not allowed" });
}
},
}),
},
},
};
`;
try {
const projectDir = setupReactProject(projectRoot, "app", {
files: {
"src/App.tsx": "export const App = () => <div>FORBIDDEN base</div>;\n",
"lint/team-conventions.cjs": forbiddenWordPlugin,
},
});
writeJson(path.join(projectDir, "doctor.config.json"), {
plugins: ["./lint/team-conventions.cjs"],
rules: { "team-conventions/no-forbidden-word": "error" },
});
initGitRepo(projectDir);
const baseRef = commitAll(projectDir, "base carries the plugin finding");
// Head keeps the finding but edits the file so it's in the scanned diff.
writeFile(
path.join(projectDir, "src/App.tsx"),
"export const App = () => <div>FORBIDDEN head</div>;\n",
);
const result = await inspect(projectDir, {
lint: true,
deadCode: false,
silent: true,
includePaths: ["src/App.tsx"],
baseline: { ref: baseRef },
});
// baseTotalCount > 0 proves the base scan loaded the relative plugin from
// the real config dir; the pre-fix null would leave it at 0.
expect(result.baselineDelta?.baseTotalCount).toBeGreaterThan(0);
} finally {
consoleSpy.mockRestore();
fs.rmSync(projectRoot, { recursive: true, force: true });
}
});
it("reuses head project metadata for baseline scans of leaf-only pnpm catalog monorepos", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const monorepoDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-catalog-"));
try {
writeFile(
path.join(monorepoDirectory, "pnpm-workspace.yaml"),
'packages:\n - "apps/*"\n\ncatalog:\n react: ^19.0.0\n',
);
writeJson(path.join(monorepoDirectory, "package.json"), {
name: "monorepo-root",
private: true,
});
writeJson(path.join(monorepoDirectory, "apps", "web", "package.json"), {
name: "web",
dependencies: { react: "catalog:" },
});
writeFile(
path.join(monorepoDirectory, "apps", "web", "src", "App.tsx"),
"export const App = () => <main>Base</main>;\n",
);
initGitRepo(monorepoDirectory);
const baseRef = commitAll(monorepoDirectory, "initial catalog workspace");
writeFile(
path.join(monorepoDirectory, "apps", "web", "src", "App.tsx"),
"export const App = () => <main>Head</main>;\n",
);
const result = await inspect(monorepoDirectory, {
lint: true,
deadCode: false,
noScore: true,
silent: true,
includePaths: ["apps/web/src/App.tsx"],
baseline: { ref: baseRef },
});
expect(result.project.reactVersion).toBe("^19.0.0");
expect(result.skippedChecks).not.toContain("lint");
expect(result.baselineDelta?.baseTotalCount).toBe(0);
} finally {
consoleSpy.mockRestore();
fs.rmSync(monorepoDirectory, { recursive: true, force: true });
}
});
it("compares focused JSX duplication against the complete baseline source tree", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-baseline-jsx-"));
try {
const projectDirectory = setupReactProject(projectRoot, "app", {
files: {
"src/AccountCard.tsx": buildDuplicateCard("AccountCard", "Account", "account"),
"src/UserCard.tsx": buildDuplicateCard("UserCard", "User", "user"),
},
});
initGitRepo(projectDirectory);
const baseRef = commitAll(projectDirectory, "base includes duplicated cards");
writeFile(
path.join(projectDirectory, "src", "AccountCard.tsx"),
buildDuplicateCard("AccountCard", "Account", "customer"),
);
const result = await inspect(projectDirectory, {
lint: true,
deadCode: true,
noScore: true,
silent: true,
includePaths: ["src/AccountCard.tsx"],
baseline: { ref: baseRef },
});
expect(result.baselineDelta?.baseTotalCount).toBeGreaterThan(0);
expect(
result.diagnostics.filter((diagnostic) => diagnostic.rule === "duplicate-jsx-subtree"),
).toEqual([]);
} finally {
consoleSpy.mockRestore();
fs.rmSync(projectRoot, { recursive: true, force: true });
}
});
it("surfaces a structurally different duplicate family with the same summary", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-baseline-jsx-change-"));
try {
const projectDirectory = setupReactProject(projectRoot, "app", {
files: {
"src/AccountCard.tsx": buildDuplicateCard("AccountCard", "Account", "account"),
"src/UserCard.tsx": buildDuplicateCard("UserCard", "User", "user"),
},
});
initGitRepo(projectDirectory);
const baseRef = commitAll(projectDirectory, "base includes duplicated sections");
writeFile(
path.join(projectDirectory, "src", "AccountCard.tsx"),
buildDuplicateCard("AccountCard", "Account", "account", "article"),
);
writeFile(
path.join(projectDirectory, "src", "UserCard.tsx"),
buildDuplicateCard("UserCard", "User", "user", "article"),
);
const result = await inspect(projectDirectory, {
lint: true,
deadCode: true,
noScore: true,
silent: true,
includePaths: ["src/AccountCard.tsx", "src/UserCard.tsx"],
baseline: { ref: baseRef },
});
expect(
result.diagnostics.filter((diagnostic) => diagnostic.rule === "duplicate-jsx-subtree"),
).toHaveLength(1);
} finally {
consoleSpy.mockRestore();
fs.rmSync(projectRoot, { recursive: true, force: true });
}
});
it("surfaces a new occurrence added to an existing duplicate family", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-baseline-jsx-growth-"));
try {
const projectDirectory = setupReactProject(projectRoot, "app", {
files: {
"src/AccountCard.tsx": buildDuplicateCard("AccountCard", "Account", "account"),
"src/UserCard.tsx": buildDuplicateCard("UserCard", "User", "user"),
},
});
initGitRepo(projectDirectory);
const baseRef = commitAll(projectDirectory, "base includes two duplicated cards");
writeFile(
path.join(projectDirectory, "src", "TeamCard.tsx"),
buildDuplicateCard("TeamCard", "Team", "team"),
);
const result = await inspect(projectDirectory, {
lint: true,
deadCode: true,
noScore: true,
silent: true,
includePaths: ["src/TeamCard.tsx"],
baseline: { ref: baseRef },
});
expect(
result.diagnostics.filter((diagnostic) => diagnostic.rule === "duplicate-jsx-subtree"),
).toHaveLength(1);
} finally {
consoleSpy.mockRestore();
fs.rmSync(projectRoot, { recursive: true, force: true });
}
});
it("keeps a pure file addition in baseline diff mode", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-baseline-add-"));
try {
const projectDirectory = setupReactProject(projectRoot, "app", {
files: {
"src/AccountCard.tsx": buildDuplicateCard("AccountCard", "Account", "account"),
"src/UserCard.tsx": buildDuplicateCard("UserCard", "User", "user"),
},
});
initGitRepo(projectDirectory);
const baseRef = commitAll(projectDirectory, "base includes unrelated duplication");
writeFile(
path.join(projectDirectory, "src", "Added.tsx"),
"export const Added = () => <main><p>New page</p></main>;\n",
);
const result = await inspect(projectDirectory, {
lint: true,
deadCode: true,
noScore: true,
silent: true,
includePaths: ["src/Added.tsx"],
baseline: { ref: baseRef },
});
expect(result.baselineDelta?.baseTotalCount).toBe(0);
expect(
result.diagnostics.filter((diagnostic) => diagnostic.rule === "duplicate-jsx-subtree"),
).toEqual([]);
} finally {
consoleSpy.mockRestore();
fs.rmSync(projectRoot, { recursive: true, force: true });
}
});
it("keeps workspace-owned maintainability baselines across excluded projects", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const projectDirectory = fs.mkdtempSync(
path.join(os.tmpdir(), "react-doctor-baseline-workspace-jsx-"),
);
const nestedProjectDirectory = path.join(projectDirectory, "packages", "web");
try {
writeJson(path.join(projectDirectory, "package.json"), {
name: "workspace-root",
dependencies: { react: "^19.0.0", "react-dom": "^19.0.0" },
});
writeFile(
path.join(nestedProjectDirectory, "src", "AccountCard.tsx"),
buildDuplicateCard("AccountCard", "Account", "account"),
);
writeFile(
path.join(nestedProjectDirectory, "src", "UserCard.tsx"),
buildDuplicateCard("UserCard", "User", "user"),
);
initGitRepo(projectDirectory);
const baseRef = commitAll(projectDirectory, "base includes nested duplication");
const changedPath = "packages/web/src/AccountCard.tsx";
writeFile(
path.join(projectDirectory, changedPath),
buildDuplicateCard("AccountCard", "Account", "customer"),
);
const result = await inspect(projectDirectory, {
lint: true,
deadCode: true,
noScore: true,
silent: true,
includePaths: [changedPath],
baseline: { ref: baseRef },
excludedProjectDirectories: [nestedProjectDirectory],
retainExcludedProjectDeadCodeDiagnostics: true,
});
expect(result.baselineDelta?.baseTotalCount).toBeGreaterThan(0);
expect(
result.diagnostics.filter((diagnostic) => diagnostic.rule === "duplicate-jsx-subtree"),
).toEqual([]);
} finally {
consoleSpy.mockRestore();
fs.rmSync(projectDirectory, { recursive: true, force: true });
}
});
it("keeps baseline comparisons complete when descendant projects are excluded", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const projectDirectory = fs.mkdtempSync(
path.join(os.tmpdir(), "react-doctor-baseline-exclude-"),
);
const nestedProjectDirectory = path.join(projectDirectory, "packages", "web");
try {
writeJson(path.join(projectDirectory, "package.json"), {
name: "baseline-exclusion-root",
dependencies: { react: "^19.0.0", "react-dom": "^19.0.0" },
});
writeFile(
path.join(projectDirectory, "src", "App.tsx"),
"export const App = () => <main>Base root</main>;\n",
);
writeFile(
path.join(nestedProjectDirectory, "src", "App.tsx"),
"export const App = () => <main>Base nested</main>;\n",
);
initGitRepo(projectDirectory);
const baseRef = commitAll(projectDirectory, "initial workspace");
writeFile(
path.join(projectDirectory, "src", "App.tsx"),
"export const App = () => <main>Head root</main>;\n",
);
writeFile(
path.join(nestedProjectDirectory, "src", "App.tsx"),
"export const App = () => <main>Head nested</main>;\n",
);
const changedFiles = ["src/App.tsx", "packages/web/src/App.tsx"];
const result = await inspect(projectDirectory, {
lint: true,
deadCode: false,
noScore: true,
silent: true,
includePaths: changedFiles,
baseline: { ref: baseRef, baseFiles: changedFiles, headFiles: changedFiles },
excludedProjectDirectories: [nestedProjectDirectory],
});
expect(result.analyzedFiles).toEqual(["src/App.tsx"]);
expect(result.baselineDelta).toBeDefined();
} finally {
consoleSpy.mockRestore();
fs.rmSync(projectDirectory, { recursive: true, force: true });
}
});
it("degrades baseline to a plain diff when the head lint only partially ran", async () => {
clearConfigCache();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
const projectDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-partial-"));
try {
writeJson(path.join(projectDirectory, "package.json"), {
name: "partial-head",
dependencies: { react: "^19.0.0", "react-dom": "^19.0.0" },
});
writeFile(
path.join(projectDirectory, "src", "App.tsx"),
"export const App = () => <main>Base</main>;\n",
);
initGitRepo(projectDirectory);
const baseRef = commitAll(projectDirectory, "initial");
writeFile(
path.join(projectDirectory, "src", "App.tsx"),
"export const App = () => <main>Head</main>;\n",
);
// A 1ms budget is spent before any lint batch spawns, so every file is
// deadline-skipped: the head lint is incomplete but `didLintFail` stays
// false. Baseline must not diff an incomplete head against a full base —
// it degrades to a plain diff (no delta) instead.
const result = await inspect(projectDirectory, {
lint: true,
deadCode: false,
noScore: true,
silent: true,
includePaths: ["src/App.tsx"],
baseline: { ref: baseRef },
maxDurationMs: 1,
});
// A "lint:partial" reason (not a "lint" skip) proves the head lint ran
// but was truncated, and the baseline degraded to a plain diff rather
// than diffing an incomplete head against a full base.
expect(result.skippedCheckReasons?.["lint:partial"]).toContain("max scan duration reached");
expect(result.skippedChecks).not.toContain("lint");
expect(result.baselineDelta).toBeUndefined();
} finally {
consoleSpy.mockRestore();
fs.rmSync(projectDirectory, { recursive: true, force: true });
}
});
});