react-doctor 0.6.3 → 0.7.0-dev.574cba6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1022 -288
- package/dist/index.d.ts +26 -0
- package/dist/index.js +646 -79
- package/dist/lsp.js +650 -85
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5c52dece-7b5e-5102-9381-7d05fa45734f")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import * as NodeChildProcess from "node:child_process";
|
|
10
10
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import * as ts from "typescript";
|
|
12
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
13
13
|
import * as OS from "node:os";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -18,6 +18,7 @@ import { fileURLToPath } from "node:url";
|
|
|
18
18
|
import { createJiti } from "jiti";
|
|
19
19
|
import * as Crypto from "node:crypto";
|
|
20
20
|
import crypto, { createHash } from "node:crypto";
|
|
21
|
+
import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
|
|
21
22
|
import { gzipSync } from "node:zlib";
|
|
22
23
|
//#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
|
|
23
24
|
/**
|
|
@@ -32927,10 +32928,15 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32927
32928
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32928
32929
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32929
32930
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32931
|
+
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32932
|
+
const CORE_PACKAGE_VERSION = "0.7.0";
|
|
32933
|
+
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32934
|
+
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32930
32935
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
32931
32936
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32932
32937
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
32933
32938
|
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
32939
|
+
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
32934
32940
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
32935
32941
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
32936
32942
|
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
@@ -35613,6 +35619,48 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35613
35619
|
if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
|
|
35614
35620
|
return true;
|
|
35615
35621
|
} }) {};
|
|
35622
|
+
/**
|
|
35623
|
+
* Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
|
|
35624
|
+
* active. Defaults ON — warm rescans replay the cross-file rules' cached
|
|
35625
|
+
* diagnostics for every file whose dependency fingerprint (the probe set the
|
|
35626
|
+
* plugin's collectors recorded) still matches the tree, and re-lint only the
|
|
35627
|
+
* rest. Only reachable when the per-file lint cache itself is on (the
|
|
35628
|
+
* sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
|
|
35629
|
+
* `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
|
|
35630
|
+
*
|
|
35631
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35632
|
+
* - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
|
|
35633
|
+
* per-file cache but run the always-fresh sidecar over every hit
|
|
35634
|
+
* (the pre-cache behavior).
|
|
35635
|
+
*
|
|
35636
|
+
* Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
|
|
35637
|
+
*/
|
|
35638
|
+
var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
|
|
35639
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35640
|
+
const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
|
|
35641
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35642
|
+
if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
|
|
35643
|
+
return true;
|
|
35644
|
+
} }) {};
|
|
35645
|
+
/**
|
|
35646
|
+
* Whether the whole-project dead-code result cache
|
|
35647
|
+
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35648
|
+
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
35649
|
+
* unchanged replays the stored diagnostics instead of re-running the
|
|
35650
|
+
* analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
|
|
35651
|
+
*
|
|
35652
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35653
|
+
* - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
|
|
35654
|
+
*
|
|
35655
|
+
* Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
|
|
35656
|
+
*/
|
|
35657
|
+
var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
|
|
35658
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35659
|
+
const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
|
|
35660
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35661
|
+
if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
|
|
35662
|
+
return true;
|
|
35663
|
+
} }) {};
|
|
35616
35664
|
const DIAGNOSTIC_SURFACES = [
|
|
35617
35665
|
"cli",
|
|
35618
35666
|
"prComment",
|
|
@@ -37301,6 +37349,146 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37301
37349
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37302
37350
|
};
|
|
37303
37351
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37352
|
+
const atomicWriteJson = (filePath, value) => {
|
|
37353
|
+
try {
|
|
37354
|
+
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
37355
|
+
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
37356
|
+
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
37357
|
+
NFS.renameSync(temporaryPath, filePath);
|
|
37358
|
+
} catch {
|
|
37359
|
+
return;
|
|
37360
|
+
}
|
|
37361
|
+
};
|
|
37362
|
+
const failOpenReadJson = (filePath, fallback) => {
|
|
37363
|
+
try {
|
|
37364
|
+
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
37365
|
+
} catch {
|
|
37366
|
+
return fallback;
|
|
37367
|
+
}
|
|
37368
|
+
};
|
|
37369
|
+
const hashFileContents = (filePath) => {
|
|
37370
|
+
try {
|
|
37371
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
37372
|
+
} catch {
|
|
37373
|
+
return null;
|
|
37374
|
+
}
|
|
37375
|
+
};
|
|
37376
|
+
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
37377
|
+
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
37378
|
+
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
37379
|
+
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
37380
|
+
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
37381
|
+
/**
|
|
37382
|
+
* Stat snapshot of every file the analysis reads, keyed by root-relative
|
|
37383
|
+
* `/`-separated path. Taken BEFORE the (long) analysis so a stored result is
|
|
37384
|
+
* verified against the tree it started from.
|
|
37385
|
+
*/
|
|
37386
|
+
const collectAnalyzedFileStats = (rootDirectory) => {
|
|
37387
|
+
const statByRelativePath = /* @__PURE__ */ new Map();
|
|
37388
|
+
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
37389
|
+
if (!isFingerprintedFile(name)) continue;
|
|
37390
|
+
try {
|
|
37391
|
+
const fileStat = NFS.statSync(absolutePath);
|
|
37392
|
+
const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
|
|
37393
|
+
statByRelativePath.set(relativePath, {
|
|
37394
|
+
mtimeMs: fileStat.mtimeMs,
|
|
37395
|
+
size: fileStat.size
|
|
37396
|
+
});
|
|
37397
|
+
} catch {}
|
|
37398
|
+
}
|
|
37399
|
+
return statByRelativePath;
|
|
37400
|
+
};
|
|
37401
|
+
const bundledRequire$2 = createRequire(import.meta.url);
|
|
37402
|
+
const resolveDeslopVersion = () => {
|
|
37403
|
+
try {
|
|
37404
|
+
const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
|
|
37405
|
+
return isRecord(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
37406
|
+
} catch {
|
|
37407
|
+
return "unknown";
|
|
37408
|
+
}
|
|
37409
|
+
};
|
|
37410
|
+
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
37411
|
+
schemaVersion: 2,
|
|
37412
|
+
coreVersion: input.coreVersion,
|
|
37413
|
+
deslopVersion: resolveDeslopVersion(),
|
|
37414
|
+
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
37415
|
+
entryPatterns: input.entryPatterns,
|
|
37416
|
+
ignorePatterns: input.ignorePatterns,
|
|
37417
|
+
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
|
|
37418
|
+
})).digest("hex");
|
|
37419
|
+
const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
|
|
37420
|
+
const decodeCachedDiagnostics = (raw) => {
|
|
37421
|
+
try {
|
|
37422
|
+
for (const entry of raw) validateDiagnostic$2(entry);
|
|
37423
|
+
return raw;
|
|
37424
|
+
} catch {
|
|
37425
|
+
return null;
|
|
37426
|
+
}
|
|
37427
|
+
};
|
|
37428
|
+
const isPersistedFileIdentity = (value) => Array.isArray(value) && value.length === 3 && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "string";
|
|
37429
|
+
const lookupDeadCodeResultCache = (input) => {
|
|
37430
|
+
const cacheFilePath = Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME);
|
|
37431
|
+
const persisted = failOpenReadJson(cacheFilePath, null);
|
|
37432
|
+
if (persisted === null || !isRecord(persisted) || persisted.version !== 2 || persisted.key !== input.cacheKey || !isRecord(persisted.files) || !Array.isArray(persisted.diagnostics)) return null;
|
|
37433
|
+
const storedFileEntries = Object.entries(persisted.files);
|
|
37434
|
+
if (storedFileEntries.length !== input.currentFileStats.size) return null;
|
|
37435
|
+
const repairedFiles = {};
|
|
37436
|
+
let repairedCount = 0;
|
|
37437
|
+
for (const [relativePath, storedIdentity] of storedFileEntries) {
|
|
37438
|
+
if (!isPersistedFileIdentity(storedIdentity)) return null;
|
|
37439
|
+
const currentStat = input.currentFileStats.get(relativePath);
|
|
37440
|
+
if (currentStat === void 0) return null;
|
|
37441
|
+
const [storedMtimeMs, storedSize, storedContentHash] = storedIdentity;
|
|
37442
|
+
if (currentStat.mtimeMs === storedMtimeMs && currentStat.size === storedSize) {
|
|
37443
|
+
repairedFiles[relativePath] = storedIdentity;
|
|
37444
|
+
continue;
|
|
37445
|
+
}
|
|
37446
|
+
if (currentStat.size !== storedSize) return null;
|
|
37447
|
+
const currentContentHash = hashFileContents(Path.join(input.rootDirectory, relativePath));
|
|
37448
|
+
if (currentContentHash === null || currentContentHash !== storedContentHash) return null;
|
|
37449
|
+
repairedFiles[relativePath] = [
|
|
37450
|
+
currentStat.mtimeMs,
|
|
37451
|
+
currentStat.size,
|
|
37452
|
+
storedContentHash
|
|
37453
|
+
];
|
|
37454
|
+
repairedCount += 1;
|
|
37455
|
+
}
|
|
37456
|
+
const diagnostics = decodeCachedDiagnostics(persisted.diagnostics);
|
|
37457
|
+
if (diagnostics === null) return null;
|
|
37458
|
+
if (repairedCount > 0) atomicWriteJson(cacheFilePath, {
|
|
37459
|
+
version: 2,
|
|
37460
|
+
key: input.cacheKey,
|
|
37461
|
+
files: repairedFiles,
|
|
37462
|
+
diagnostics: persisted.diagnostics
|
|
37463
|
+
});
|
|
37464
|
+
return diagnostics;
|
|
37465
|
+
};
|
|
37466
|
+
const storeDeadCodeResultCache = (input) => {
|
|
37467
|
+
const persistedFiles = {};
|
|
37468
|
+
for (const [relativePath, snapshotStat] of input.snapshotFileStats) {
|
|
37469
|
+
const absolutePath = Path.join(input.rootDirectory, relativePath);
|
|
37470
|
+
const contentHash = hashFileContents(absolutePath);
|
|
37471
|
+
if (contentHash === null) return;
|
|
37472
|
+
let currentStat;
|
|
37473
|
+
try {
|
|
37474
|
+
currentStat = NFS.statSync(absolutePath);
|
|
37475
|
+
} catch {
|
|
37476
|
+
return;
|
|
37477
|
+
}
|
|
37478
|
+
if (currentStat.mtimeMs !== snapshotStat.mtimeMs || currentStat.size !== snapshotStat.size) return;
|
|
37479
|
+
persistedFiles[relativePath] = [
|
|
37480
|
+
snapshotStat.mtimeMs,
|
|
37481
|
+
snapshotStat.size,
|
|
37482
|
+
contentHash
|
|
37483
|
+
];
|
|
37484
|
+
}
|
|
37485
|
+
atomicWriteJson(Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
|
|
37486
|
+
version: 2,
|
|
37487
|
+
key: input.cacheKey,
|
|
37488
|
+
files: persistedFiles,
|
|
37489
|
+
diagnostics: input.diagnostics
|
|
37490
|
+
});
|
|
37491
|
+
};
|
|
37304
37492
|
const readSystemFacts = () => ({
|
|
37305
37493
|
availableCores: os.availableParallelism(),
|
|
37306
37494
|
totalMemoryBytes: os.totalmem(),
|
|
@@ -37369,6 +37557,21 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
|
37369
37557
|
releaseSlot();
|
|
37370
37558
|
}
|
|
37371
37559
|
};
|
|
37560
|
+
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
37561
|
+
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
37562
|
+
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
37563
|
+
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
37564
|
+
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
37565
|
+
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
37566
|
+
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
37567
|
+
};
|
|
37568
|
+
const resolveUserCacheScope = () => {
|
|
37569
|
+
try {
|
|
37570
|
+
return String(process.getuid?.() ?? os.userInfo().username);
|
|
37571
|
+
} catch {
|
|
37572
|
+
return "shared";
|
|
37573
|
+
}
|
|
37574
|
+
};
|
|
37372
37575
|
/**
|
|
37373
37576
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37374
37577
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37422,6 +37625,14 @@ process.stdin.on("end", () => {
|
|
|
37422
37625
|
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
37423
37626
|
files: cycle.files,
|
|
37424
37627
|
})),
|
|
37628
|
+
...(result.incrementalCacheStats
|
|
37629
|
+
? {
|
|
37630
|
+
summaryCacheStats: {
|
|
37631
|
+
hits: result.incrementalCacheStats.summaryHits,
|
|
37632
|
+
misses: result.incrementalCacheStats.summaryMisses,
|
|
37633
|
+
},
|
|
37634
|
+
}
|
|
37635
|
+
: {}),
|
|
37425
37636
|
});
|
|
37426
37637
|
|
|
37427
37638
|
const serializeError = (error) =>
|
|
@@ -37445,6 +37656,9 @@ process.stdin.on("end", () => {
|
|
|
37445
37656
|
...(workerInput.ignorePatterns.length > 0
|
|
37446
37657
|
? { ignorePatterns: workerInput.ignorePatterns }
|
|
37447
37658
|
: {}),
|
|
37659
|
+
...(workerInput.incrementalCachePath
|
|
37660
|
+
? { incrementalCachePath: workerInput.incrementalCachePath }
|
|
37661
|
+
: {}),
|
|
37448
37662
|
// We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
|
|
37449
37663
|
// unusedDependencies, circularDependencies). Everything else deslop can compute
|
|
37450
37664
|
// is pure wasted work for us, and it's the bulk of the runtime:
|
|
@@ -37455,12 +37669,18 @@ process.stdin.on("end", () => {
|
|
|
37455
37669
|
// are the single most expensive pass — duplicate-block detection alone was
|
|
37456
37670
|
// ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
|
|
37457
37671
|
// speedup on a large repo.
|
|
37458
|
-
//
|
|
37672
|
+
// - reportRedundancy: the DRY-pattern detectors (duplicate types/constants,
|
|
37673
|
+
// simplifiable functions, identity wrappers, …) — ~120 ms of discarded
|
|
37674
|
+
// output per scan, and skipping them lets the incremental cache drop the
|
|
37675
|
+
// DRY-pattern summary fields (the largest slice of the cache file).
|
|
37676
|
+
// All are provably safe: the consumed graph findings are computed by their own
|
|
37459
37677
|
// detectors, independent of these passes (confirmed byte-identical on
|
|
37460
|
-
// excalidraw + mui-material + sentry
|
|
37461
|
-
//
|
|
37678
|
+
// excalidraw + mui-material + sentry; re-verified on sentry after the
|
|
37679
|
+
// reportRedundancy flip). tsConfigPath stays — the module resolver needs it
|
|
37680
|
+
// for path-alias resolution in the import graph.
|
|
37462
37681
|
semantic: { enabled: false },
|
|
37463
37682
|
reportCodeQuality: false,
|
|
37683
|
+
reportRedundancy: false,
|
|
37464
37684
|
};
|
|
37465
37685
|
const result = await analyze(defineConfig(config));
|
|
37466
37686
|
emit({ ok: true, result: normalizeResult(result) });
|
|
@@ -37544,13 +37764,23 @@ const parseCircularDependencies = (value) => {
|
|
|
37544
37764
|
}
|
|
37545
37765
|
return circularDependencies;
|
|
37546
37766
|
};
|
|
37767
|
+
const parseSummaryCacheStats = (value) => {
|
|
37768
|
+
if (!isRecord(value)) return void 0;
|
|
37769
|
+
if (typeof value.hits !== "number" || typeof value.misses !== "number") return void 0;
|
|
37770
|
+
return {
|
|
37771
|
+
hits: value.hits,
|
|
37772
|
+
misses: value.misses
|
|
37773
|
+
};
|
|
37774
|
+
};
|
|
37547
37775
|
const parseDeadCodeWorkerResult = (value) => {
|
|
37548
37776
|
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
|
|
37777
|
+
const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
|
|
37549
37778
|
return {
|
|
37550
37779
|
unusedFiles: parseUnusedFiles(value.unusedFiles),
|
|
37551
37780
|
unusedExports: parseUnusedExports(value.unusedExports),
|
|
37552
37781
|
unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
|
|
37553
|
-
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
37782
|
+
circularDependencies: parseCircularDependencies(value.circularDependencies),
|
|
37783
|
+
...summaryCacheStats ? { summaryCacheStats } : {}
|
|
37554
37784
|
};
|
|
37555
37785
|
};
|
|
37556
37786
|
const parseDeadCodeWorkerError = (value) => {
|
|
@@ -37664,17 +37894,41 @@ const checkDeadCode = async (options) => {
|
|
|
37664
37894
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37665
37895
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37666
37896
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37897
|
+
const tsConfigPath = resolveTsConfigPath(rootDirectory);
|
|
37898
|
+
const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
|
|
37899
|
+
const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
|
|
37900
|
+
const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
|
|
37901
|
+
rootDirectory,
|
|
37902
|
+
entryPatterns,
|
|
37903
|
+
ignorePatterns,
|
|
37904
|
+
tsConfigPath,
|
|
37905
|
+
deslopJsModuleSpecifier,
|
|
37906
|
+
coreVersion: CORE_PACKAGE_VERSION
|
|
37907
|
+
});
|
|
37908
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) {
|
|
37909
|
+
const cachedDiagnostics = lookupDeadCodeResultCache({
|
|
37910
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
37911
|
+
cacheKey,
|
|
37912
|
+
rootDirectory,
|
|
37913
|
+
currentFileStats: fileStatsSnapshot
|
|
37914
|
+
});
|
|
37915
|
+
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
37916
|
+
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
37917
|
+
}
|
|
37918
|
+
const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
|
|
37667
37919
|
const spawnAndRun = () => {
|
|
37668
37920
|
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37669
37921
|
rootDirectory,
|
|
37670
37922
|
entryPatterns,
|
|
37671
|
-
tsConfigPath
|
|
37923
|
+
tsConfigPath,
|
|
37672
37924
|
ignorePatterns,
|
|
37673
|
-
deslopJsModuleSpecifier
|
|
37674
|
-
parseConcurrency: options.parseConcurrency
|
|
37925
|
+
deslopJsModuleSpecifier,
|
|
37926
|
+
parseConcurrency: options.parseConcurrency,
|
|
37927
|
+
incrementalCachePath
|
|
37675
37928
|
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37676
37929
|
};
|
|
37677
37930
|
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37931
|
+
if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
|
|
37678
37932
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37679
37933
|
const diagnostics = [];
|
|
37680
37934
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37731,6 +37985,13 @@ const checkDeadCode = async (options) => {
|
|
|
37731
37985
|
category: DEAD_CODE_CATEGORY
|
|
37732
37986
|
});
|
|
37733
37987
|
}
|
|
37988
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
|
|
37989
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
37990
|
+
cacheKey,
|
|
37991
|
+
rootDirectory,
|
|
37992
|
+
snapshotFileStats: fileStatsSnapshot,
|
|
37993
|
+
diagnostics
|
|
37994
|
+
});
|
|
37734
37995
|
return diagnostics;
|
|
37735
37996
|
};
|
|
37736
37997
|
const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
|
|
@@ -37833,12 +38094,16 @@ var Config = class Config extends Service()("react-doctor/Config") {
|
|
|
37833
38094
|
*/
|
|
37834
38095
|
var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
37835
38096
|
static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
|
|
38097
|
+
const cacheEnabled = yield* DeadCodeResultCacheEnabled;
|
|
37836
38098
|
return yield* tryPromise({
|
|
37837
38099
|
try: (signal) => checkDeadCode({
|
|
37838
38100
|
rootDirectory: input.rootDirectory,
|
|
37839
38101
|
parseConcurrency: input.parseConcurrency,
|
|
37840
38102
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37841
|
-
abortSignal: signal
|
|
38103
|
+
abortSignal: signal,
|
|
38104
|
+
cacheEnabled,
|
|
38105
|
+
onCacheOutcome: input.onCacheOutcome,
|
|
38106
|
+
onSummaryCacheStats: input.onSummaryCacheStats
|
|
37842
38107
|
}),
|
|
37843
38108
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
37844
38109
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
@@ -38708,7 +38973,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38708
38973
|
}
|
|
38709
38974
|
return enabled;
|
|
38710
38975
|
};
|
|
38711
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38976
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
38712
38977
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38713
38978
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38714
38979
|
const jsPlugins = [];
|
|
@@ -38720,6 +38985,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38720
38985
|
if (!rule) continue;
|
|
38721
38986
|
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38722
38987
|
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38988
|
+
if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
|
|
38723
38989
|
if (rule.scan !== void 0) continue;
|
|
38724
38990
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38725
38991
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38764,33 +39030,145 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38764
39030
|
}
|
|
38765
39031
|
};
|
|
38766
39032
|
};
|
|
38767
|
-
const
|
|
39033
|
+
const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
|
|
39034
|
+
const decodeFileDiagnostics = (raw) => {
|
|
39035
|
+
if (!Array.isArray(raw)) return null;
|
|
38768
39036
|
try {
|
|
38769
|
-
|
|
38770
|
-
|
|
38771
|
-
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
38772
|
-
NFS.renameSync(temporaryPath, filePath);
|
|
39037
|
+
for (const entry of raw) validateDiagnostic$1(entry);
|
|
39038
|
+
return raw;
|
|
38773
39039
|
} catch {
|
|
38774
|
-
return;
|
|
39040
|
+
return null;
|
|
38775
39041
|
}
|
|
38776
39042
|
};
|
|
38777
|
-
const
|
|
38778
|
-
|
|
38779
|
-
|
|
38780
|
-
|
|
38781
|
-
|
|
39043
|
+
const emptyCache$1 = () => ({
|
|
39044
|
+
version: 1,
|
|
39045
|
+
rulesets: {}
|
|
39046
|
+
});
|
|
39047
|
+
const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
|
|
39048
|
+
const entries = /* @__PURE__ */ new Map();
|
|
39049
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
39050
|
+
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
39051
|
+
const bucket = persisted.rulesets[rulesetHash];
|
|
39052
|
+
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39053
|
+
for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
|
|
39054
|
+
const decoded = decodeFileDiagnostics(rawDiagnostics);
|
|
39055
|
+
if (decoded !== null) entries.set(fileKey, decoded);
|
|
38782
39056
|
}
|
|
39057
|
+
return entries;
|
|
39058
|
+
};
|
|
39059
|
+
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
39060
|
+
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
39061
|
+
const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
|
|
39062
|
+
return {
|
|
39063
|
+
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
39064
|
+
store: (fileKey, diagnostics) => {
|
|
39065
|
+
entries.delete(fileKey);
|
|
39066
|
+
entries.set(fileKey, diagnostics);
|
|
39067
|
+
},
|
|
39068
|
+
persist: () => {
|
|
39069
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
39070
|
+
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39071
|
+
const existingBucket = rulesets[rulesetHash];
|
|
39072
|
+
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39073
|
+
const ourFiles = {};
|
|
39074
|
+
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
39075
|
+
const cappedEntries = Object.entries({
|
|
39076
|
+
...existingFiles,
|
|
39077
|
+
...ourFiles
|
|
39078
|
+
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
39079
|
+
rulesets[rulesetHash] = {
|
|
39080
|
+
updatedAtMs: Date.now(),
|
|
39081
|
+
files: Object.fromEntries(cappedEntries)
|
|
39082
|
+
};
|
|
39083
|
+
atomicWriteJson(cacheFilePath, {
|
|
39084
|
+
version: 1,
|
|
39085
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39086
|
+
});
|
|
39087
|
+
}
|
|
39088
|
+
};
|
|
39089
|
+
};
|
|
39090
|
+
const ABSENT_CONTENT_ANSWER = "absent";
|
|
39091
|
+
/**
|
|
39092
|
+
* Answers dependency probes for the sidecar lint cache — used both when
|
|
39093
|
+
* STORING an entry (turn the collector's probe paths into answers) and when
|
|
39094
|
+
* VERIFYING one (re-answer each stored probe against the current tree).
|
|
39095
|
+
* Store and verify must agree exactly, so both go through this one resolver.
|
|
39096
|
+
*
|
|
39097
|
+
* - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
|
|
39098
|
+
* (missing, a directory, permission failure). Source files reuse the
|
|
39099
|
+
* hashes the cache-partition loop already computed
|
|
39100
|
+
* (`contentHashByRelativePath`); everything else (package.json,
|
|
39101
|
+
* tsconfig, ignored files) is hashed on demand.
|
|
39102
|
+
* - `exists` — the existence class a module-resolution candidate probe
|
|
39103
|
+
* sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
|
|
39104
|
+
* `statSync` follows symlinks, matching the resolvers).
|
|
39105
|
+
*
|
|
39106
|
+
* Answers are memoized per scan: the filesystem is frozen for the run, and
|
|
39107
|
+
* hot dependency paths (a barrel imported by hundreds of files, the shared
|
|
39108
|
+
* tsconfig chain) repeat across many files' probe sets.
|
|
39109
|
+
*/
|
|
39110
|
+
const createSidecarProbeAnswerResolver = (input) => {
|
|
39111
|
+
const contentAnswerByPath = /* @__PURE__ */ new Map();
|
|
39112
|
+
const existsAnswerByPath = /* @__PURE__ */ new Map();
|
|
39113
|
+
const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
|
|
39114
|
+
const contentAnswer = (relativePath) => {
|
|
39115
|
+
const memoized = contentAnswerByPath.get(relativePath);
|
|
39116
|
+
if (memoized !== void 0) return memoized;
|
|
39117
|
+
const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
|
|
39118
|
+
contentAnswerByPath.set(relativePath, answer);
|
|
39119
|
+
return answer;
|
|
39120
|
+
};
|
|
39121
|
+
const existsAnswer = (relativePath) => {
|
|
39122
|
+
const memoized = existsAnswerByPath.get(relativePath);
|
|
39123
|
+
if (memoized !== void 0) return memoized;
|
|
39124
|
+
let answer;
|
|
39125
|
+
try {
|
|
39126
|
+
const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
|
|
39127
|
+
answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
|
|
39128
|
+
} catch {
|
|
39129
|
+
answer = "none";
|
|
39130
|
+
}
|
|
39131
|
+
existsAnswerByPath.set(relativePath, answer);
|
|
39132
|
+
return answer;
|
|
39133
|
+
};
|
|
39134
|
+
return {
|
|
39135
|
+
answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
|
|
39136
|
+
toRelativePath
|
|
39137
|
+
};
|
|
38783
39138
|
};
|
|
38784
39139
|
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
38785
|
-
const
|
|
38786
|
-
|
|
39140
|
+
const isProbeKind = (value) => value === "content" || value === "exists";
|
|
39141
|
+
const decodeSidecarFileEntry = (raw) => {
|
|
39142
|
+
if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
|
|
39143
|
+
const probes = [];
|
|
39144
|
+
for (const rawProbe of raw.probes) {
|
|
39145
|
+
if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
|
|
39146
|
+
const [kind, probePath, answer] = rawProbe;
|
|
39147
|
+
if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
|
|
39148
|
+
probes.push({
|
|
39149
|
+
kind,
|
|
39150
|
+
path: probePath,
|
|
39151
|
+
answer
|
|
39152
|
+
});
|
|
39153
|
+
}
|
|
38787
39154
|
try {
|
|
38788
|
-
for (const entry of raw) validateDiagnostic(entry);
|
|
38789
|
-
return raw;
|
|
39155
|
+
for (const entry of raw.diagnostics) validateDiagnostic(entry);
|
|
38790
39156
|
} catch {
|
|
38791
39157
|
return null;
|
|
38792
39158
|
}
|
|
39159
|
+
return {
|
|
39160
|
+
probes,
|
|
39161
|
+
diagnostics: raw.diagnostics
|
|
39162
|
+
};
|
|
38793
39163
|
};
|
|
39164
|
+
const encodeSidecarFileEntry = (entry) => ({
|
|
39165
|
+
probes: entry.probes.map((probe) => [
|
|
39166
|
+
probe.kind,
|
|
39167
|
+
probe.path,
|
|
39168
|
+
probe.answer
|
|
39169
|
+
]),
|
|
39170
|
+
diagnostics: entry.diagnostics
|
|
39171
|
+
});
|
|
38794
39172
|
const emptyCache = () => ({
|
|
38795
39173
|
version: 1,
|
|
38796
39174
|
rulesets: {}
|
|
@@ -38801,20 +39179,20 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
|
38801
39179
|
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38802
39180
|
const bucket = persisted.rulesets[rulesetHash];
|
|
38803
39181
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
38804
|
-
for (const [fileKey,
|
|
38805
|
-
const decoded =
|
|
39182
|
+
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
39183
|
+
const decoded = decodeSidecarFileEntry(rawEntry);
|
|
38806
39184
|
if (decoded !== null) entries.set(fileKey, decoded);
|
|
38807
39185
|
}
|
|
38808
39186
|
return entries;
|
|
38809
39187
|
};
|
|
38810
|
-
const
|
|
38811
|
-
const cacheFilePath = Path.join(cacheDirectory,
|
|
39188
|
+
const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
39189
|
+
const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
|
|
38812
39190
|
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
38813
39191
|
return {
|
|
38814
39192
|
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38815
|
-
store: (fileKey,
|
|
39193
|
+
store: (fileKey, entry) => {
|
|
38816
39194
|
entries.delete(fileKey);
|
|
38817
|
-
entries.set(fileKey,
|
|
39195
|
+
entries.set(fileKey, entry);
|
|
38818
39196
|
},
|
|
38819
39197
|
persist: () => {
|
|
38820
39198
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
@@ -38822,7 +39200,7 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38822
39200
|
const existingBucket = rulesets[rulesetHash];
|
|
38823
39201
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
38824
39202
|
const ourFiles = {};
|
|
38825
|
-
for (const [fileKey,
|
|
39203
|
+
for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
|
|
38826
39204
|
const cappedEntries = Object.entries({
|
|
38827
39205
|
...existingFiles,
|
|
38828
39206
|
...ourFiles
|
|
@@ -39837,13 +40215,6 @@ const validateRuleRegistration = () => {
|
|
|
39837
40215
|
].filter((entry) => entry !== null).join("; ");
|
|
39838
40216
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
39839
40217
|
};
|
|
39840
|
-
const hashFileContents = (filePath) => {
|
|
39841
|
-
try {
|
|
39842
|
-
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39843
|
-
} catch {
|
|
39844
|
-
return null;
|
|
39845
|
-
}
|
|
39846
|
-
};
|
|
39847
40218
|
/**
|
|
39848
40219
|
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39849
40220
|
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
@@ -39908,21 +40279,6 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
|
39908
40279
|
}
|
|
39909
40280
|
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
39910
40281
|
};
|
|
39911
|
-
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39912
|
-
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39913
|
-
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
39914
|
-
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39915
|
-
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39916
|
-
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39917
|
-
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
39918
|
-
};
|
|
39919
|
-
const resolveUserCacheScope = () => {
|
|
39920
|
-
try {
|
|
39921
|
-
return String(process.getuid?.() ?? os.userInfo().username);
|
|
39922
|
-
} catch {
|
|
39923
|
-
return "shared";
|
|
39924
|
-
}
|
|
39925
|
-
};
|
|
39926
40282
|
/**
|
|
39927
40283
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39928
40284
|
* the runner: once for the primary scan, once for the
|
|
@@ -39940,6 +40296,106 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
39940
40296
|
NFS.closeSync(fileHandle);
|
|
39941
40297
|
}
|
|
39942
40298
|
};
|
|
40299
|
+
/**
|
|
40300
|
+
* Attributes diagnostics back to the file that produced them by the
|
|
40301
|
+
* normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
|
|
40302
|
+
* attributed — the path forms don't align, so the caller must skip its cache
|
|
40303
|
+
* store rather than risk caching a wrong empty result for a file that
|
|
40304
|
+
* actually had diagnostics.
|
|
40305
|
+
*/
|
|
40306
|
+
const attributeDiagnosticsToFiles = (diagnostics, files) => {
|
|
40307
|
+
const fileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40308
|
+
for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
|
|
40309
|
+
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40310
|
+
for (const diagnostic of diagnostics) {
|
|
40311
|
+
const file = fileByNormalizedPath.get(diagnostic.filePath);
|
|
40312
|
+
if (file === void 0) return null;
|
|
40313
|
+
const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
|
|
40314
|
+
fileDiagnostics.push(diagnostic);
|
|
40315
|
+
diagnosticsByFile.set(file, fileDiagnostics);
|
|
40316
|
+
}
|
|
40317
|
+
return diagnosticsByFile;
|
|
40318
|
+
};
|
|
40319
|
+
/**
|
|
40320
|
+
* Collects each file's cross-file dependency probes (via the plugin's
|
|
40321
|
+
* collectors) with their current answers. Fail-open per file: an unreadable /
|
|
40322
|
+
* unparseable / collector-failing file maps to `null` (no entry stored — it
|
|
40323
|
+
* re-lints next scan). The filesystem is frozen for the scan, so this can run
|
|
40324
|
+
* CONCURRENTLY with an oxlint child-process await — the caller starts it
|
|
40325
|
+
* unawaited before a lint pass and awaits it at store time, hiding the
|
|
40326
|
+
* in-process parse + resolution cost under the subprocess wait. The loop
|
|
40327
|
+
* yields on the cooperative time budget so the child's stdout handling (and
|
|
40328
|
+
* sibling fibers) keep flowing; each per-file collection is synchronous, so
|
|
40329
|
+
* the module-level probe recorder never sees interleaved use. Never rejects.
|
|
40330
|
+
*/
|
|
40331
|
+
const collectSidecarProbesForFiles = async (input) => {
|
|
40332
|
+
const buildProbes = (file) => {
|
|
40333
|
+
const absoluteFilePath = Path.resolve(input.rootDirectory, file);
|
|
40334
|
+
let trace;
|
|
40335
|
+
try {
|
|
40336
|
+
trace = collectCrossFileDependencyProbes({
|
|
40337
|
+
absoluteFilePath,
|
|
40338
|
+
sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
|
|
40339
|
+
ruleIds: input.boundedSidecarRuleIds
|
|
40340
|
+
});
|
|
40341
|
+
} catch {
|
|
40342
|
+
return null;
|
|
40343
|
+
}
|
|
40344
|
+
if (trace === null) return null;
|
|
40345
|
+
const probes = [];
|
|
40346
|
+
for (const contentPath of trace.contentPaths) {
|
|
40347
|
+
const relativePath = input.probeAnswers.toRelativePath(contentPath);
|
|
40348
|
+
probes.push({
|
|
40349
|
+
kind: "content",
|
|
40350
|
+
path: relativePath,
|
|
40351
|
+
answer: input.probeAnswers.answerFor("content", relativePath)
|
|
40352
|
+
});
|
|
40353
|
+
}
|
|
40354
|
+
for (const existencePath of trace.existencePaths) {
|
|
40355
|
+
const relativePath = input.probeAnswers.toRelativePath(existencePath);
|
|
40356
|
+
probes.push({
|
|
40357
|
+
kind: "exists",
|
|
40358
|
+
path: relativePath,
|
|
40359
|
+
answer: input.probeAnswers.answerFor("exists", relativePath)
|
|
40360
|
+
});
|
|
40361
|
+
}
|
|
40362
|
+
return probes;
|
|
40363
|
+
};
|
|
40364
|
+
const probesByFile = /* @__PURE__ */ new Map();
|
|
40365
|
+
let collectSliceStartedAt = performance.now();
|
|
40366
|
+
for (const file of input.files) {
|
|
40367
|
+
probesByFile.set(file, buildProbes(file));
|
|
40368
|
+
if (performance.now() - collectSliceStartedAt >= 12) {
|
|
40369
|
+
await yieldToEventLoop();
|
|
40370
|
+
collectSliceStartedAt = performance.now();
|
|
40371
|
+
}
|
|
40372
|
+
}
|
|
40373
|
+
return probesByFile;
|
|
40374
|
+
};
|
|
40375
|
+
/**
|
|
40376
|
+
* Stores each freshly-linted file's probe set + cross-file diagnostics so
|
|
40377
|
+
* the next scan can replay instead of re-linting.
|
|
40378
|
+
*/
|
|
40379
|
+
const storeSidecarEntries = (input) => {
|
|
40380
|
+
let didStoreAnyEntry = false;
|
|
40381
|
+
for (const pass of input.storablePasses) {
|
|
40382
|
+
if (!pass.isTrusted) continue;
|
|
40383
|
+
const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
|
|
40384
|
+
if (diagnosticsByFile === null) continue;
|
|
40385
|
+
for (const file of pass.files) {
|
|
40386
|
+
const cacheKey = input.cacheKeyByFile.get(file);
|
|
40387
|
+
if (cacheKey === void 0) continue;
|
|
40388
|
+
const probes = pass.probesByFile.get(file);
|
|
40389
|
+
if (probes === void 0 || probes === null) continue;
|
|
40390
|
+
input.sidecarCache.store(cacheKey, {
|
|
40391
|
+
probes,
|
|
40392
|
+
diagnostics: diagnosticsByFile.get(file) ?? []
|
|
40393
|
+
});
|
|
40394
|
+
didStoreAnyEntry = true;
|
|
40395
|
+
}
|
|
40396
|
+
}
|
|
40397
|
+
if (didStoreAnyEntry) input.sidecarCache.persist();
|
|
40398
|
+
};
|
|
39943
40399
|
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
39944
40400
|
/**
|
|
39945
40401
|
* Detects an oxlint config-load crash caused by the optional
|
|
@@ -39981,7 +40437,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39981
40437
|
* 6. always restore disable directives + clean up the temp dir
|
|
39982
40438
|
*/
|
|
39983
40439
|
const runOxlint = async (options) => {
|
|
39984
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
40440
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
39985
40441
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39986
40442
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39987
40443
|
validateRuleRegistration();
|
|
@@ -39999,7 +40455,8 @@ const runOxlint = async (options) => {
|
|
|
39999
40455
|
severityControls,
|
|
40000
40456
|
userPlugins,
|
|
40001
40457
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
40002
|
-
ruleSelection: overrides.ruleSelection
|
|
40458
|
+
ruleSelection: overrides.ruleSelection,
|
|
40459
|
+
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
40003
40460
|
});
|
|
40004
40461
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
40005
40462
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -40102,15 +40559,18 @@ const runOxlint = async (options) => {
|
|
|
40102
40559
|
});
|
|
40103
40560
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
40104
40561
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40562
|
+
const contentHashByRelativePath = /* @__PURE__ */ new Map();
|
|
40105
40563
|
const missFiles = [];
|
|
40106
40564
|
const replayedDiagnostics = [];
|
|
40107
40565
|
let hashSliceStartedAt = performance.now();
|
|
40108
40566
|
for (const candidateFile of candidateFiles) {
|
|
40109
|
-
const
|
|
40567
|
+
const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
|
|
40568
|
+
const contentHash = hashFileContents(absoluteCandidatePath);
|
|
40110
40569
|
if (contentHash === null) {
|
|
40111
40570
|
missFiles.push(candidateFile);
|
|
40112
40571
|
continue;
|
|
40113
40572
|
}
|
|
40573
|
+
contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
|
|
40114
40574
|
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
40115
40575
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
40116
40576
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
@@ -40122,43 +40582,111 @@ const runOxlint = async (options) => {
|
|
|
40122
40582
|
}
|
|
40123
40583
|
}
|
|
40124
40584
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40585
|
+
const enabledSidecarRuleIds = Object.keys(buildConfig({
|
|
40586
|
+
extendsPaths: [],
|
|
40587
|
+
ruleSelection: "sidecar"
|
|
40588
|
+
}).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
|
|
40589
|
+
const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40590
|
+
const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
|
|
40591
|
+
const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40592
|
+
const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
|
|
40593
|
+
const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
|
|
40594
|
+
config: buildConfig({
|
|
40595
|
+
extendsPaths: [],
|
|
40596
|
+
ruleSelection: "sidecar",
|
|
40597
|
+
sidecarRuleIdFilter: boundedSidecarRuleIdSet
|
|
40598
|
+
}),
|
|
40599
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
40600
|
+
ignorePatterns: combinedPatterns,
|
|
40601
|
+
tsconfigContent
|
|
40602
|
+
})) : null;
|
|
40603
|
+
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
40604
|
+
rootDirectory,
|
|
40605
|
+
contentHashByRelativePath
|
|
40606
|
+
});
|
|
40607
|
+
const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40608
|
+
files: missFiles,
|
|
40609
|
+
rootDirectory,
|
|
40610
|
+
boundedSidecarRuleIds,
|
|
40611
|
+
probeAnswers
|
|
40612
|
+
});
|
|
40125
40613
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40126
40614
|
extendsPaths: [],
|
|
40127
40615
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40128
40616
|
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40129
40617
|
const missFileSet = new Set(missFiles);
|
|
40130
|
-
const
|
|
40618
|
+
const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
|
|
40619
|
+
const sidecarReplayedDiagnostics = [];
|
|
40620
|
+
const sidecarLintFiles = [];
|
|
40621
|
+
if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
|
|
40622
|
+
else {
|
|
40623
|
+
let verifySliceStartedAt = performance.now();
|
|
40624
|
+
for (const hitFile of hitFiles) {
|
|
40625
|
+
const cacheKey = cacheKeyByFile.get(hitFile);
|
|
40626
|
+
const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
|
|
40627
|
+
if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
|
|
40628
|
+
else sidecarLintFiles.push(hitFile);
|
|
40629
|
+
if (performance.now() - verifySliceStartedAt >= 12) {
|
|
40630
|
+
await yieldToEventLoop();
|
|
40631
|
+
verifySliceStartedAt = performance.now();
|
|
40632
|
+
}
|
|
40633
|
+
}
|
|
40634
|
+
}
|
|
40635
|
+
const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40636
|
+
files: sidecarLintFiles,
|
|
40637
|
+
rootDirectory,
|
|
40638
|
+
boundedSidecarRuleIds,
|
|
40639
|
+
probeAnswers
|
|
40640
|
+
});
|
|
40641
|
+
const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
|
|
40642
|
+
if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
|
|
40643
|
+
const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40131
40644
|
extendsPaths: [],
|
|
40132
|
-
ruleSelection: "sidecar"
|
|
40133
|
-
|
|
40645
|
+
ruleSelection: "sidecar",
|
|
40646
|
+
...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
|
|
40647
|
+
}), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
|
|
40648
|
+
const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
|
|
40649
|
+
extendsPaths: [],
|
|
40650
|
+
ruleSelection: "sidecar",
|
|
40651
|
+
sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
|
|
40652
|
+
}), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
|
|
40653
|
+
diagnostics: [],
|
|
40654
|
+
didDropReactHooksJsPlugin: false,
|
|
40655
|
+
hadPartialFailure: false
|
|
40656
|
+
};
|
|
40134
40657
|
const freshCacheableDiagnostics = [];
|
|
40135
40658
|
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40136
40659
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40137
|
-
|
|
40138
|
-
|
|
40139
|
-
|
|
40140
|
-
let isAttributionSound = true;
|
|
40141
|
-
for (const diagnostic of freshCacheableDiagnostics) {
|
|
40142
|
-
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
40143
|
-
if (missFile === void 0) {
|
|
40144
|
-
isAttributionSound = false;
|
|
40145
|
-
break;
|
|
40146
|
-
}
|
|
40147
|
-
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
40148
|
-
fileDiagnostics.push(diagnostic);
|
|
40149
|
-
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
40150
|
-
}
|
|
40151
|
-
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
40660
|
+
if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
|
|
40661
|
+
const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
|
|
40662
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
|
|
40152
40663
|
for (const missFile of missFiles) {
|
|
40153
40664
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40154
40665
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
40155
40666
|
}
|
|
40156
40667
|
cache.persist();
|
|
40157
40668
|
}
|
|
40669
|
+
if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
|
|
40670
|
+
sidecarCache,
|
|
40671
|
+
cacheKeyByFile,
|
|
40672
|
+
storablePasses: [{
|
|
40673
|
+
files: sidecarLintFiles,
|
|
40674
|
+
diagnostics: boundedSidecarResult.diagnostics,
|
|
40675
|
+
isTrusted: !boundedSidecarResult.hadPartialFailure,
|
|
40676
|
+
probesByFile: await sidecarProbesTask
|
|
40677
|
+
}, {
|
|
40678
|
+
files: missFiles,
|
|
40679
|
+
diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
|
|
40680
|
+
isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
|
|
40681
|
+
probesByFile: await missProbesTask
|
|
40682
|
+
}]
|
|
40683
|
+
});
|
|
40158
40684
|
return dedupeDiagnostics([
|
|
40159
40685
|
...replayedDiagnostics,
|
|
40160
40686
|
...fullResult.diagnostics,
|
|
40161
|
-
...
|
|
40687
|
+
...sidecarReplayedDiagnostics,
|
|
40688
|
+
...boundedSidecarResult.diagnostics,
|
|
40689
|
+
...unboundedSidecarResult.diagnostics
|
|
40162
40690
|
]);
|
|
40163
40691
|
}
|
|
40164
40692
|
const baseArgs = makeBaseArgs(configPath);
|
|
@@ -40256,6 +40784,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40256
40784
|
const concurrency = yield* OxlintConcurrency;
|
|
40257
40785
|
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40258
40786
|
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
40787
|
+
const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
|
|
40259
40788
|
const collectedFailures = [];
|
|
40260
40789
|
const diagnostics = yield* tryPromise({
|
|
40261
40790
|
try: (signal) => runOxlint({
|
|
@@ -40274,7 +40803,9 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40274
40803
|
},
|
|
40275
40804
|
onFileProgress: input.onFileProgress,
|
|
40276
40805
|
perFileLintCacheEnabled,
|
|
40806
|
+
sidecarLintCacheEnabled,
|
|
40277
40807
|
onCacheStats: input.onCacheStats,
|
|
40808
|
+
onSidecarStats: input.onSidecarStats,
|
|
40278
40809
|
spawnTimeoutMs,
|
|
40279
40810
|
outputMaxBytes,
|
|
40280
40811
|
concurrency,
|
|
@@ -40696,6 +41227,20 @@ const writeCachedSocketBody = (cacheFile, body) => {
|
|
|
40696
41227
|
}));
|
|
40697
41228
|
} catch {}
|
|
40698
41229
|
};
|
|
41230
|
+
const pruneExpiredSocketCache = (cacheDirectory) => {
|
|
41231
|
+
try {
|
|
41232
|
+
const supplyChainCacheDirectory = Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR);
|
|
41233
|
+
const expiryThresholdMs = Date.now() - SUPPLY_CHAIN_CACHE_TTL_MS;
|
|
41234
|
+
for (const entryName of NFS.readdirSync(supplyChainCacheDirectory)) {
|
|
41235
|
+
const entryPath = Path.join(supplyChainCacheDirectory, entryName);
|
|
41236
|
+
try {
|
|
41237
|
+
if (NFS.statSync(entryPath).mtimeMs < expiryThresholdMs) NFS.rmSync(entryPath);
|
|
41238
|
+
} catch {
|
|
41239
|
+
continue;
|
|
41240
|
+
}
|
|
41241
|
+
}
|
|
41242
|
+
} catch {}
|
|
41243
|
+
};
|
|
40699
41244
|
const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
|
|
40700
41245
|
const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
|
|
40701
41246
|
if (cacheFile !== null) {
|
|
@@ -40823,6 +41368,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40823
41368
|
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
40824
41369
|
if (dependencies.length === 0) return [];
|
|
40825
41370
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41371
|
+
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
40826
41372
|
const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
|
|
40827
41373
|
const diagnostics = [];
|
|
40828
41374
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
@@ -41049,7 +41595,14 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41049
41595
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
41050
41596
|
rootDirectory: scanDirectory,
|
|
41051
41597
|
parseConcurrency: deadCodeParseConcurrency,
|
|
41052
|
-
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
41598
|
+
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
|
|
41599
|
+
onCacheOutcome: (didHitCache) => {
|
|
41600
|
+
deadCodeCacheHit = didHitCache;
|
|
41601
|
+
},
|
|
41602
|
+
onSummaryCacheStats: (stats) => {
|
|
41603
|
+
deadCodeSummaryCacheHits = stats.hits;
|
|
41604
|
+
deadCodeSummaryCacheMisses = stats.misses;
|
|
41605
|
+
}
|
|
41053
41606
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41054
41607
|
yield* set(deadCodeFailure, {
|
|
41055
41608
|
didFail: true,
|
|
@@ -41077,6 +41630,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41077
41630
|
let lastReportedTotalFileCount = 0;
|
|
41078
41631
|
let lintCacheHitFileCount = null;
|
|
41079
41632
|
let lintCacheTotalFileCount = null;
|
|
41633
|
+
let lintSidecarReplayedFileCount = null;
|
|
41634
|
+
let lintSidecarTotalFileCount = null;
|
|
41635
|
+
let deadCodeCacheHit = null;
|
|
41636
|
+
let deadCodeSummaryCacheHits = null;
|
|
41637
|
+
let deadCodeSummaryCacheMisses = null;
|
|
41080
41638
|
const baseLintStream = linterService.run({
|
|
41081
41639
|
rootDirectory: scanDirectory,
|
|
41082
41640
|
project,
|
|
@@ -41096,6 +41654,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41096
41654
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
41097
41655
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
41098
41656
|
},
|
|
41657
|
+
onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
|
|
41658
|
+
lintSidecarReplayedFileCount = sidecarReplayedFileCount;
|
|
41659
|
+
lintSidecarTotalFileCount = sidecarConsideredFileCount;
|
|
41660
|
+
},
|
|
41099
41661
|
deadlineEpochMs: input.deadlineEpochMs
|
|
41100
41662
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41101
41663
|
yield* set(lintFailure, {
|
|
@@ -41206,6 +41768,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41206
41768
|
securityScanFailed,
|
|
41207
41769
|
lintCacheHitFileCount,
|
|
41208
41770
|
lintCacheTotalFileCount,
|
|
41771
|
+
lintSidecarReplayedFileCount,
|
|
41772
|
+
lintSidecarTotalFileCount,
|
|
41773
|
+
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41774
|
+
deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
|
|
41775
|
+
deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
|
|
41209
41776
|
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
41210
41777
|
};
|
|
41211
41778
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
@@ -41846,4 +42413,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41846
42413
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41847
42414
|
|
|
41848
42415
|
//# sourceMappingURL=index.js.map
|
|
41849
|
-
//# debugId=
|
|
42416
|
+
//# debugId=5c52dece-7b5e-5102-9381-7d05fa45734f
|