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/lsp.js
CHANGED
|
@@ -8,13 +8,14 @@ import path from "node:path";
|
|
|
8
8
|
import * as NodeChildProcess from "node:child_process";
|
|
9
9
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import * as ts from "typescript";
|
|
11
|
-
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";
|
|
11
|
+
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";
|
|
12
12
|
import { parseJSON5 } from "confbox";
|
|
13
13
|
import * as NodeUrl from "node:url";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { createJiti } from "jiti";
|
|
16
16
|
import * as Crypto from "node:crypto";
|
|
17
17
|
import crypto, { createHash } from "node:crypto";
|
|
18
|
+
import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
|
|
18
19
|
import { gzipSync } from "node:zlib";
|
|
19
20
|
import { CodeActionKind, CodeActionTriggerKind, DidChangeWatchedFilesNotification, DocumentDiagnosticReportKind, FileChangeType, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/node.js";
|
|
20
21
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
@@ -32986,10 +32987,15 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32986
32987
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32987
32988
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32988
32989
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32990
|
+
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32991
|
+
const CORE_PACKAGE_VERSION = "0.7.0";
|
|
32992
|
+
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32993
|
+
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32989
32994
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
32990
32995
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32991
32996
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
32992
32997
|
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
32998
|
+
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
32993
32999
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
32994
33000
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
32995
33001
|
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
@@ -35646,6 +35652,48 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35646
35652
|
if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
|
|
35647
35653
|
return true;
|
|
35648
35654
|
} }) {};
|
|
35655
|
+
/**
|
|
35656
|
+
* Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
|
|
35657
|
+
* active. Defaults ON — warm rescans replay the cross-file rules' cached
|
|
35658
|
+
* diagnostics for every file whose dependency fingerprint (the probe set the
|
|
35659
|
+
* plugin's collectors recorded) still matches the tree, and re-lint only the
|
|
35660
|
+
* rest. Only reachable when the per-file lint cache itself is on (the
|
|
35661
|
+
* sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
|
|
35662
|
+
* `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
|
|
35663
|
+
*
|
|
35664
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35665
|
+
* - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
|
|
35666
|
+
* per-file cache but run the always-fresh sidecar over every hit
|
|
35667
|
+
* (the pre-cache behavior).
|
|
35668
|
+
*
|
|
35669
|
+
* Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
|
|
35670
|
+
*/
|
|
35671
|
+
var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
|
|
35672
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35673
|
+
const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
|
|
35674
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35675
|
+
if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
|
|
35676
|
+
return true;
|
|
35677
|
+
} }) {};
|
|
35678
|
+
/**
|
|
35679
|
+
* Whether the whole-project dead-code result cache
|
|
35680
|
+
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35681
|
+
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
35682
|
+
* unchanged replays the stored diagnostics instead of re-running the
|
|
35683
|
+
* analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
|
|
35684
|
+
*
|
|
35685
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35686
|
+
* - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
|
|
35687
|
+
*
|
|
35688
|
+
* Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
|
|
35689
|
+
*/
|
|
35690
|
+
var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
|
|
35691
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35692
|
+
const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
|
|
35693
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35694
|
+
if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
|
|
35695
|
+
return true;
|
|
35696
|
+
} }) {};
|
|
35649
35697
|
const DIAGNOSTIC_SURFACES = [
|
|
35650
35698
|
"cli",
|
|
35651
35699
|
"prComment",
|
|
@@ -37286,6 +37334,146 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37286
37334
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37287
37335
|
};
|
|
37288
37336
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37337
|
+
const atomicWriteJson = (filePath, value) => {
|
|
37338
|
+
try {
|
|
37339
|
+
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
37340
|
+
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
37341
|
+
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
37342
|
+
NFS.renameSync(temporaryPath, filePath);
|
|
37343
|
+
} catch {
|
|
37344
|
+
return;
|
|
37345
|
+
}
|
|
37346
|
+
};
|
|
37347
|
+
const failOpenReadJson = (filePath, fallback) => {
|
|
37348
|
+
try {
|
|
37349
|
+
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
37350
|
+
} catch {
|
|
37351
|
+
return fallback;
|
|
37352
|
+
}
|
|
37353
|
+
};
|
|
37354
|
+
const hashFileContents = (filePath) => {
|
|
37355
|
+
try {
|
|
37356
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
37357
|
+
} catch {
|
|
37358
|
+
return null;
|
|
37359
|
+
}
|
|
37360
|
+
};
|
|
37361
|
+
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
37362
|
+
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
37363
|
+
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
37364
|
+
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
37365
|
+
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
37366
|
+
/**
|
|
37367
|
+
* Stat snapshot of every file the analysis reads, keyed by root-relative
|
|
37368
|
+
* `/`-separated path. Taken BEFORE the (long) analysis so a stored result is
|
|
37369
|
+
* verified against the tree it started from.
|
|
37370
|
+
*/
|
|
37371
|
+
const collectAnalyzedFileStats = (rootDirectory) => {
|
|
37372
|
+
const statByRelativePath = /* @__PURE__ */ new Map();
|
|
37373
|
+
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
37374
|
+
if (!isFingerprintedFile(name)) continue;
|
|
37375
|
+
try {
|
|
37376
|
+
const fileStat = NFS.statSync(absolutePath);
|
|
37377
|
+
const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
|
|
37378
|
+
statByRelativePath.set(relativePath, {
|
|
37379
|
+
mtimeMs: fileStat.mtimeMs,
|
|
37380
|
+
size: fileStat.size
|
|
37381
|
+
});
|
|
37382
|
+
} catch {}
|
|
37383
|
+
}
|
|
37384
|
+
return statByRelativePath;
|
|
37385
|
+
};
|
|
37386
|
+
const bundledRequire$2 = createRequire(import.meta.url);
|
|
37387
|
+
const resolveDeslopVersion = () => {
|
|
37388
|
+
try {
|
|
37389
|
+
const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
|
|
37390
|
+
return isRecord(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
37391
|
+
} catch {
|
|
37392
|
+
return "unknown";
|
|
37393
|
+
}
|
|
37394
|
+
};
|
|
37395
|
+
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
37396
|
+
schemaVersion: 2,
|
|
37397
|
+
coreVersion: input.coreVersion,
|
|
37398
|
+
deslopVersion: resolveDeslopVersion(),
|
|
37399
|
+
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
37400
|
+
entryPatterns: input.entryPatterns,
|
|
37401
|
+
ignorePatterns: input.ignorePatterns,
|
|
37402
|
+
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
|
|
37403
|
+
})).digest("hex");
|
|
37404
|
+
const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
|
|
37405
|
+
const decodeCachedDiagnostics = (raw) => {
|
|
37406
|
+
try {
|
|
37407
|
+
for (const entry of raw) validateDiagnostic$2(entry);
|
|
37408
|
+
return raw;
|
|
37409
|
+
} catch {
|
|
37410
|
+
return null;
|
|
37411
|
+
}
|
|
37412
|
+
};
|
|
37413
|
+
const isPersistedFileIdentity = (value) => Array.isArray(value) && value.length === 3 && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "string";
|
|
37414
|
+
const lookupDeadCodeResultCache = (input) => {
|
|
37415
|
+
const cacheFilePath = Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME);
|
|
37416
|
+
const persisted = failOpenReadJson(cacheFilePath, null);
|
|
37417
|
+
if (persisted === null || !isRecord(persisted) || persisted.version !== 2 || persisted.key !== input.cacheKey || !isRecord(persisted.files) || !Array.isArray(persisted.diagnostics)) return null;
|
|
37418
|
+
const storedFileEntries = Object.entries(persisted.files);
|
|
37419
|
+
if (storedFileEntries.length !== input.currentFileStats.size) return null;
|
|
37420
|
+
const repairedFiles = {};
|
|
37421
|
+
let repairedCount = 0;
|
|
37422
|
+
for (const [relativePath, storedIdentity] of storedFileEntries) {
|
|
37423
|
+
if (!isPersistedFileIdentity(storedIdentity)) return null;
|
|
37424
|
+
const currentStat = input.currentFileStats.get(relativePath);
|
|
37425
|
+
if (currentStat === void 0) return null;
|
|
37426
|
+
const [storedMtimeMs, storedSize, storedContentHash] = storedIdentity;
|
|
37427
|
+
if (currentStat.mtimeMs === storedMtimeMs && currentStat.size === storedSize) {
|
|
37428
|
+
repairedFiles[relativePath] = storedIdentity;
|
|
37429
|
+
continue;
|
|
37430
|
+
}
|
|
37431
|
+
if (currentStat.size !== storedSize) return null;
|
|
37432
|
+
const currentContentHash = hashFileContents(Path.join(input.rootDirectory, relativePath));
|
|
37433
|
+
if (currentContentHash === null || currentContentHash !== storedContentHash) return null;
|
|
37434
|
+
repairedFiles[relativePath] = [
|
|
37435
|
+
currentStat.mtimeMs,
|
|
37436
|
+
currentStat.size,
|
|
37437
|
+
storedContentHash
|
|
37438
|
+
];
|
|
37439
|
+
repairedCount += 1;
|
|
37440
|
+
}
|
|
37441
|
+
const diagnostics = decodeCachedDiagnostics(persisted.diagnostics);
|
|
37442
|
+
if (diagnostics === null) return null;
|
|
37443
|
+
if (repairedCount > 0) atomicWriteJson(cacheFilePath, {
|
|
37444
|
+
version: 2,
|
|
37445
|
+
key: input.cacheKey,
|
|
37446
|
+
files: repairedFiles,
|
|
37447
|
+
diagnostics: persisted.diagnostics
|
|
37448
|
+
});
|
|
37449
|
+
return diagnostics;
|
|
37450
|
+
};
|
|
37451
|
+
const storeDeadCodeResultCache = (input) => {
|
|
37452
|
+
const persistedFiles = {};
|
|
37453
|
+
for (const [relativePath, snapshotStat] of input.snapshotFileStats) {
|
|
37454
|
+
const absolutePath = Path.join(input.rootDirectory, relativePath);
|
|
37455
|
+
const contentHash = hashFileContents(absolutePath);
|
|
37456
|
+
if (contentHash === null) return;
|
|
37457
|
+
let currentStat;
|
|
37458
|
+
try {
|
|
37459
|
+
currentStat = NFS.statSync(absolutePath);
|
|
37460
|
+
} catch {
|
|
37461
|
+
return;
|
|
37462
|
+
}
|
|
37463
|
+
if (currentStat.mtimeMs !== snapshotStat.mtimeMs || currentStat.size !== snapshotStat.size) return;
|
|
37464
|
+
persistedFiles[relativePath] = [
|
|
37465
|
+
snapshotStat.mtimeMs,
|
|
37466
|
+
snapshotStat.size,
|
|
37467
|
+
contentHash
|
|
37468
|
+
];
|
|
37469
|
+
}
|
|
37470
|
+
atomicWriteJson(Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
|
|
37471
|
+
version: 2,
|
|
37472
|
+
key: input.cacheKey,
|
|
37473
|
+
files: persistedFiles,
|
|
37474
|
+
diagnostics: input.diagnostics
|
|
37475
|
+
});
|
|
37476
|
+
};
|
|
37289
37477
|
const readSystemFacts = () => ({
|
|
37290
37478
|
availableCores: os.availableParallelism(),
|
|
37291
37479
|
totalMemoryBytes: os.totalmem(),
|
|
@@ -37354,6 +37542,21 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
|
37354
37542
|
releaseSlot();
|
|
37355
37543
|
}
|
|
37356
37544
|
};
|
|
37545
|
+
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
37546
|
+
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
37547
|
+
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
37548
|
+
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
37549
|
+
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
37550
|
+
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
37551
|
+
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
37552
|
+
};
|
|
37553
|
+
const resolveUserCacheScope = () => {
|
|
37554
|
+
try {
|
|
37555
|
+
return String(process.getuid?.() ?? os.userInfo().username);
|
|
37556
|
+
} catch {
|
|
37557
|
+
return "shared";
|
|
37558
|
+
}
|
|
37559
|
+
};
|
|
37357
37560
|
/**
|
|
37358
37561
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37359
37562
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37407,6 +37610,14 @@ process.stdin.on("end", () => {
|
|
|
37407
37610
|
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
37408
37611
|
files: cycle.files,
|
|
37409
37612
|
})),
|
|
37613
|
+
...(result.incrementalCacheStats
|
|
37614
|
+
? {
|
|
37615
|
+
summaryCacheStats: {
|
|
37616
|
+
hits: result.incrementalCacheStats.summaryHits,
|
|
37617
|
+
misses: result.incrementalCacheStats.summaryMisses,
|
|
37618
|
+
},
|
|
37619
|
+
}
|
|
37620
|
+
: {}),
|
|
37410
37621
|
});
|
|
37411
37622
|
|
|
37412
37623
|
const serializeError = (error) =>
|
|
@@ -37430,6 +37641,9 @@ process.stdin.on("end", () => {
|
|
|
37430
37641
|
...(workerInput.ignorePatterns.length > 0
|
|
37431
37642
|
? { ignorePatterns: workerInput.ignorePatterns }
|
|
37432
37643
|
: {}),
|
|
37644
|
+
...(workerInput.incrementalCachePath
|
|
37645
|
+
? { incrementalCachePath: workerInput.incrementalCachePath }
|
|
37646
|
+
: {}),
|
|
37433
37647
|
// We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
|
|
37434
37648
|
// unusedDependencies, circularDependencies). Everything else deslop can compute
|
|
37435
37649
|
// is pure wasted work for us, and it's the bulk of the runtime:
|
|
@@ -37440,12 +37654,18 @@ process.stdin.on("end", () => {
|
|
|
37440
37654
|
// are the single most expensive pass — duplicate-block detection alone was
|
|
37441
37655
|
// ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
|
|
37442
37656
|
// speedup on a large repo.
|
|
37443
|
-
//
|
|
37657
|
+
// - reportRedundancy: the DRY-pattern detectors (duplicate types/constants,
|
|
37658
|
+
// simplifiable functions, identity wrappers, …) — ~120 ms of discarded
|
|
37659
|
+
// output per scan, and skipping them lets the incremental cache drop the
|
|
37660
|
+
// DRY-pattern summary fields (the largest slice of the cache file).
|
|
37661
|
+
// All are provably safe: the consumed graph findings are computed by their own
|
|
37444
37662
|
// detectors, independent of these passes (confirmed byte-identical on
|
|
37445
|
-
// excalidraw + mui-material + sentry
|
|
37446
|
-
//
|
|
37663
|
+
// excalidraw + mui-material + sentry; re-verified on sentry after the
|
|
37664
|
+
// reportRedundancy flip). tsConfigPath stays — the module resolver needs it
|
|
37665
|
+
// for path-alias resolution in the import graph.
|
|
37447
37666
|
semantic: { enabled: false },
|
|
37448
37667
|
reportCodeQuality: false,
|
|
37668
|
+
reportRedundancy: false,
|
|
37449
37669
|
};
|
|
37450
37670
|
const result = await analyze(defineConfig(config));
|
|
37451
37671
|
emit({ ok: true, result: normalizeResult(result) });
|
|
@@ -37529,13 +37749,23 @@ const parseCircularDependencies = (value) => {
|
|
|
37529
37749
|
}
|
|
37530
37750
|
return circularDependencies;
|
|
37531
37751
|
};
|
|
37752
|
+
const parseSummaryCacheStats = (value) => {
|
|
37753
|
+
if (!isRecord(value)) return void 0;
|
|
37754
|
+
if (typeof value.hits !== "number" || typeof value.misses !== "number") return void 0;
|
|
37755
|
+
return {
|
|
37756
|
+
hits: value.hits,
|
|
37757
|
+
misses: value.misses
|
|
37758
|
+
};
|
|
37759
|
+
};
|
|
37532
37760
|
const parseDeadCodeWorkerResult = (value) => {
|
|
37533
37761
|
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
|
|
37762
|
+
const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
|
|
37534
37763
|
return {
|
|
37535
37764
|
unusedFiles: parseUnusedFiles(value.unusedFiles),
|
|
37536
37765
|
unusedExports: parseUnusedExports(value.unusedExports),
|
|
37537
37766
|
unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
|
|
37538
|
-
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
37767
|
+
circularDependencies: parseCircularDependencies(value.circularDependencies),
|
|
37768
|
+
...summaryCacheStats ? { summaryCacheStats } : {}
|
|
37539
37769
|
};
|
|
37540
37770
|
};
|
|
37541
37771
|
const parseDeadCodeWorkerError = (value) => {
|
|
@@ -37649,17 +37879,41 @@ const checkDeadCode = async (options) => {
|
|
|
37649
37879
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37650
37880
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37651
37881
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37882
|
+
const tsConfigPath = resolveTsConfigPath(rootDirectory);
|
|
37883
|
+
const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
|
|
37884
|
+
const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
|
|
37885
|
+
const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
|
|
37886
|
+
rootDirectory,
|
|
37887
|
+
entryPatterns,
|
|
37888
|
+
ignorePatterns,
|
|
37889
|
+
tsConfigPath,
|
|
37890
|
+
deslopJsModuleSpecifier,
|
|
37891
|
+
coreVersion: CORE_PACKAGE_VERSION
|
|
37892
|
+
});
|
|
37893
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) {
|
|
37894
|
+
const cachedDiagnostics = lookupDeadCodeResultCache({
|
|
37895
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
37896
|
+
cacheKey,
|
|
37897
|
+
rootDirectory,
|
|
37898
|
+
currentFileStats: fileStatsSnapshot
|
|
37899
|
+
});
|
|
37900
|
+
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
37901
|
+
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
37902
|
+
}
|
|
37903
|
+
const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
|
|
37652
37904
|
const spawnAndRun = () => {
|
|
37653
37905
|
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37654
37906
|
rootDirectory,
|
|
37655
37907
|
entryPatterns,
|
|
37656
|
-
tsConfigPath
|
|
37908
|
+
tsConfigPath,
|
|
37657
37909
|
ignorePatterns,
|
|
37658
|
-
deslopJsModuleSpecifier
|
|
37659
|
-
parseConcurrency: options.parseConcurrency
|
|
37910
|
+
deslopJsModuleSpecifier,
|
|
37911
|
+
parseConcurrency: options.parseConcurrency,
|
|
37912
|
+
incrementalCachePath
|
|
37660
37913
|
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37661
37914
|
};
|
|
37662
37915
|
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37916
|
+
if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
|
|
37663
37917
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37664
37918
|
const diagnostics = [];
|
|
37665
37919
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37716,6 +37970,13 @@ const checkDeadCode = async (options) => {
|
|
|
37716
37970
|
category: DEAD_CODE_CATEGORY
|
|
37717
37971
|
});
|
|
37718
37972
|
}
|
|
37973
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
|
|
37974
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
37975
|
+
cacheKey,
|
|
37976
|
+
rootDirectory,
|
|
37977
|
+
snapshotFileStats: fileStatsSnapshot,
|
|
37978
|
+
diagnostics
|
|
37979
|
+
});
|
|
37719
37980
|
return diagnostics;
|
|
37720
37981
|
};
|
|
37721
37982
|
const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
|
|
@@ -37818,12 +38079,16 @@ var Config = class Config extends Service()("react-doctor/Config") {
|
|
|
37818
38079
|
*/
|
|
37819
38080
|
var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
37820
38081
|
static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
|
|
38082
|
+
const cacheEnabled = yield* DeadCodeResultCacheEnabled;
|
|
37821
38083
|
return yield* tryPromise({
|
|
37822
38084
|
try: (signal) => checkDeadCode({
|
|
37823
38085
|
rootDirectory: input.rootDirectory,
|
|
37824
38086
|
parseConcurrency: input.parseConcurrency,
|
|
37825
38087
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37826
|
-
abortSignal: signal
|
|
38088
|
+
abortSignal: signal,
|
|
38089
|
+
cacheEnabled,
|
|
38090
|
+
onCacheOutcome: input.onCacheOutcome,
|
|
38091
|
+
onSummaryCacheStats: input.onSummaryCacheStats
|
|
37827
38092
|
}),
|
|
37828
38093
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
37829
38094
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
@@ -38693,7 +38958,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38693
38958
|
}
|
|
38694
38959
|
return enabled;
|
|
38695
38960
|
};
|
|
38696
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38961
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
38697
38962
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38698
38963
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38699
38964
|
const jsPlugins = [];
|
|
@@ -38705,6 +38970,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38705
38970
|
if (!rule) continue;
|
|
38706
38971
|
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38707
38972
|
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38973
|
+
if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
|
|
38708
38974
|
if (rule.scan !== void 0) continue;
|
|
38709
38975
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38710
38976
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38749,33 +39015,145 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38749
39015
|
}
|
|
38750
39016
|
};
|
|
38751
39017
|
};
|
|
38752
|
-
const
|
|
39018
|
+
const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
|
|
39019
|
+
const decodeFileDiagnostics = (raw) => {
|
|
39020
|
+
if (!Array.isArray(raw)) return null;
|
|
38753
39021
|
try {
|
|
38754
|
-
|
|
38755
|
-
|
|
38756
|
-
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
38757
|
-
NFS.renameSync(temporaryPath, filePath);
|
|
39022
|
+
for (const entry of raw) validateDiagnostic$1(entry);
|
|
39023
|
+
return raw;
|
|
38758
39024
|
} catch {
|
|
38759
|
-
return;
|
|
39025
|
+
return null;
|
|
38760
39026
|
}
|
|
38761
39027
|
};
|
|
38762
|
-
const
|
|
38763
|
-
|
|
38764
|
-
|
|
38765
|
-
|
|
38766
|
-
|
|
39028
|
+
const emptyCache$1 = () => ({
|
|
39029
|
+
version: 1,
|
|
39030
|
+
rulesets: {}
|
|
39031
|
+
});
|
|
39032
|
+
const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
|
|
39033
|
+
const entries = /* @__PURE__ */ new Map();
|
|
39034
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
39035
|
+
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
39036
|
+
const bucket = persisted.rulesets[rulesetHash];
|
|
39037
|
+
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39038
|
+
for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
|
|
39039
|
+
const decoded = decodeFileDiagnostics(rawDiagnostics);
|
|
39040
|
+
if (decoded !== null) entries.set(fileKey, decoded);
|
|
38767
39041
|
}
|
|
39042
|
+
return entries;
|
|
39043
|
+
};
|
|
39044
|
+
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
39045
|
+
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
39046
|
+
const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
|
|
39047
|
+
return {
|
|
39048
|
+
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
39049
|
+
store: (fileKey, diagnostics) => {
|
|
39050
|
+
entries.delete(fileKey);
|
|
39051
|
+
entries.set(fileKey, diagnostics);
|
|
39052
|
+
},
|
|
39053
|
+
persist: () => {
|
|
39054
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
39055
|
+
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39056
|
+
const existingBucket = rulesets[rulesetHash];
|
|
39057
|
+
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39058
|
+
const ourFiles = {};
|
|
39059
|
+
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
39060
|
+
const cappedEntries = Object.entries({
|
|
39061
|
+
...existingFiles,
|
|
39062
|
+
...ourFiles
|
|
39063
|
+
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
39064
|
+
rulesets[rulesetHash] = {
|
|
39065
|
+
updatedAtMs: Date.now(),
|
|
39066
|
+
files: Object.fromEntries(cappedEntries)
|
|
39067
|
+
};
|
|
39068
|
+
atomicWriteJson(cacheFilePath, {
|
|
39069
|
+
version: 1,
|
|
39070
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39071
|
+
});
|
|
39072
|
+
}
|
|
39073
|
+
};
|
|
39074
|
+
};
|
|
39075
|
+
const ABSENT_CONTENT_ANSWER = "absent";
|
|
39076
|
+
/**
|
|
39077
|
+
* Answers dependency probes for the sidecar lint cache — used both when
|
|
39078
|
+
* STORING an entry (turn the collector's probe paths into answers) and when
|
|
39079
|
+
* VERIFYING one (re-answer each stored probe against the current tree).
|
|
39080
|
+
* Store and verify must agree exactly, so both go through this one resolver.
|
|
39081
|
+
*
|
|
39082
|
+
* - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
|
|
39083
|
+
* (missing, a directory, permission failure). Source files reuse the
|
|
39084
|
+
* hashes the cache-partition loop already computed
|
|
39085
|
+
* (`contentHashByRelativePath`); everything else (package.json,
|
|
39086
|
+
* tsconfig, ignored files) is hashed on demand.
|
|
39087
|
+
* - `exists` — the existence class a module-resolution candidate probe
|
|
39088
|
+
* sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
|
|
39089
|
+
* `statSync` follows symlinks, matching the resolvers).
|
|
39090
|
+
*
|
|
39091
|
+
* Answers are memoized per scan: the filesystem is frozen for the run, and
|
|
39092
|
+
* hot dependency paths (a barrel imported by hundreds of files, the shared
|
|
39093
|
+
* tsconfig chain) repeat across many files' probe sets.
|
|
39094
|
+
*/
|
|
39095
|
+
const createSidecarProbeAnswerResolver = (input) => {
|
|
39096
|
+
const contentAnswerByPath = /* @__PURE__ */ new Map();
|
|
39097
|
+
const existsAnswerByPath = /* @__PURE__ */ new Map();
|
|
39098
|
+
const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
|
|
39099
|
+
const contentAnswer = (relativePath) => {
|
|
39100
|
+
const memoized = contentAnswerByPath.get(relativePath);
|
|
39101
|
+
if (memoized !== void 0) return memoized;
|
|
39102
|
+
const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
|
|
39103
|
+
contentAnswerByPath.set(relativePath, answer);
|
|
39104
|
+
return answer;
|
|
39105
|
+
};
|
|
39106
|
+
const existsAnswer = (relativePath) => {
|
|
39107
|
+
const memoized = existsAnswerByPath.get(relativePath);
|
|
39108
|
+
if (memoized !== void 0) return memoized;
|
|
39109
|
+
let answer;
|
|
39110
|
+
try {
|
|
39111
|
+
const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
|
|
39112
|
+
answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
|
|
39113
|
+
} catch {
|
|
39114
|
+
answer = "none";
|
|
39115
|
+
}
|
|
39116
|
+
existsAnswerByPath.set(relativePath, answer);
|
|
39117
|
+
return answer;
|
|
39118
|
+
};
|
|
39119
|
+
return {
|
|
39120
|
+
answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
|
|
39121
|
+
toRelativePath
|
|
39122
|
+
};
|
|
38768
39123
|
};
|
|
38769
39124
|
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
38770
|
-
const
|
|
38771
|
-
|
|
39125
|
+
const isProbeKind = (value) => value === "content" || value === "exists";
|
|
39126
|
+
const decodeSidecarFileEntry = (raw) => {
|
|
39127
|
+
if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
|
|
39128
|
+
const probes = [];
|
|
39129
|
+
for (const rawProbe of raw.probes) {
|
|
39130
|
+
if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
|
|
39131
|
+
const [kind, probePath, answer] = rawProbe;
|
|
39132
|
+
if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
|
|
39133
|
+
probes.push({
|
|
39134
|
+
kind,
|
|
39135
|
+
path: probePath,
|
|
39136
|
+
answer
|
|
39137
|
+
});
|
|
39138
|
+
}
|
|
38772
39139
|
try {
|
|
38773
|
-
for (const entry of raw) validateDiagnostic(entry);
|
|
38774
|
-
return raw;
|
|
39140
|
+
for (const entry of raw.diagnostics) validateDiagnostic(entry);
|
|
38775
39141
|
} catch {
|
|
38776
39142
|
return null;
|
|
38777
39143
|
}
|
|
39144
|
+
return {
|
|
39145
|
+
probes,
|
|
39146
|
+
diagnostics: raw.diagnostics
|
|
39147
|
+
};
|
|
38778
39148
|
};
|
|
39149
|
+
const encodeSidecarFileEntry = (entry) => ({
|
|
39150
|
+
probes: entry.probes.map((probe) => [
|
|
39151
|
+
probe.kind,
|
|
39152
|
+
probe.path,
|
|
39153
|
+
probe.answer
|
|
39154
|
+
]),
|
|
39155
|
+
diagnostics: entry.diagnostics
|
|
39156
|
+
});
|
|
38779
39157
|
const emptyCache = () => ({
|
|
38780
39158
|
version: 1,
|
|
38781
39159
|
rulesets: {}
|
|
@@ -38786,20 +39164,20 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
|
38786
39164
|
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38787
39165
|
const bucket = persisted.rulesets[rulesetHash];
|
|
38788
39166
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
38789
|
-
for (const [fileKey,
|
|
38790
|
-
const decoded =
|
|
39167
|
+
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
39168
|
+
const decoded = decodeSidecarFileEntry(rawEntry);
|
|
38791
39169
|
if (decoded !== null) entries.set(fileKey, decoded);
|
|
38792
39170
|
}
|
|
38793
39171
|
return entries;
|
|
38794
39172
|
};
|
|
38795
|
-
const
|
|
38796
|
-
const cacheFilePath = Path.join(cacheDirectory,
|
|
39173
|
+
const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
39174
|
+
const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
|
|
38797
39175
|
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
38798
39176
|
return {
|
|
38799
39177
|
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38800
|
-
store: (fileKey,
|
|
39178
|
+
store: (fileKey, entry) => {
|
|
38801
39179
|
entries.delete(fileKey);
|
|
38802
|
-
entries.set(fileKey,
|
|
39180
|
+
entries.set(fileKey, entry);
|
|
38803
39181
|
},
|
|
38804
39182
|
persist: () => {
|
|
38805
39183
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
@@ -38807,7 +39185,7 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38807
39185
|
const existingBucket = rulesets[rulesetHash];
|
|
38808
39186
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
38809
39187
|
const ourFiles = {};
|
|
38810
|
-
for (const [fileKey,
|
|
39188
|
+
for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
|
|
38811
39189
|
const cappedEntries = Object.entries({
|
|
38812
39190
|
...existingFiles,
|
|
38813
39191
|
...ourFiles
|
|
@@ -39822,13 +40200,6 @@ const validateRuleRegistration = () => {
|
|
|
39822
40200
|
].filter((entry) => entry !== null).join("; ");
|
|
39823
40201
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
39824
40202
|
};
|
|
39825
|
-
const hashFileContents = (filePath) => {
|
|
39826
|
-
try {
|
|
39827
|
-
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39828
|
-
} catch {
|
|
39829
|
-
return null;
|
|
39830
|
-
}
|
|
39831
|
-
};
|
|
39832
40203
|
/**
|
|
39833
40204
|
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39834
40205
|
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
@@ -39893,21 +40264,6 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
|
39893
40264
|
}
|
|
39894
40265
|
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
39895
40266
|
};
|
|
39896
|
-
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39897
|
-
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39898
|
-
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
39899
|
-
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39900
|
-
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39901
|
-
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39902
|
-
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
39903
|
-
};
|
|
39904
|
-
const resolveUserCacheScope = () => {
|
|
39905
|
-
try {
|
|
39906
|
-
return String(process.getuid?.() ?? os.userInfo().username);
|
|
39907
|
-
} catch {
|
|
39908
|
-
return "shared";
|
|
39909
|
-
}
|
|
39910
|
-
};
|
|
39911
40267
|
/**
|
|
39912
40268
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39913
40269
|
* the runner: once for the primary scan, once for the
|
|
@@ -39925,6 +40281,106 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
39925
40281
|
NFS.closeSync(fileHandle);
|
|
39926
40282
|
}
|
|
39927
40283
|
};
|
|
40284
|
+
/**
|
|
40285
|
+
* Attributes diagnostics back to the file that produced them by the
|
|
40286
|
+
* normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
|
|
40287
|
+
* attributed — the path forms don't align, so the caller must skip its cache
|
|
40288
|
+
* store rather than risk caching a wrong empty result for a file that
|
|
40289
|
+
* actually had diagnostics.
|
|
40290
|
+
*/
|
|
40291
|
+
const attributeDiagnosticsToFiles = (diagnostics, files) => {
|
|
40292
|
+
const fileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40293
|
+
for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
|
|
40294
|
+
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40295
|
+
for (const diagnostic of diagnostics) {
|
|
40296
|
+
const file = fileByNormalizedPath.get(diagnostic.filePath);
|
|
40297
|
+
if (file === void 0) return null;
|
|
40298
|
+
const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
|
|
40299
|
+
fileDiagnostics.push(diagnostic);
|
|
40300
|
+
diagnosticsByFile.set(file, fileDiagnostics);
|
|
40301
|
+
}
|
|
40302
|
+
return diagnosticsByFile;
|
|
40303
|
+
};
|
|
40304
|
+
/**
|
|
40305
|
+
* Collects each file's cross-file dependency probes (via the plugin's
|
|
40306
|
+
* collectors) with their current answers. Fail-open per file: an unreadable /
|
|
40307
|
+
* unparseable / collector-failing file maps to `null` (no entry stored — it
|
|
40308
|
+
* re-lints next scan). The filesystem is frozen for the scan, so this can run
|
|
40309
|
+
* CONCURRENTLY with an oxlint child-process await — the caller starts it
|
|
40310
|
+
* unawaited before a lint pass and awaits it at store time, hiding the
|
|
40311
|
+
* in-process parse + resolution cost under the subprocess wait. The loop
|
|
40312
|
+
* yields on the cooperative time budget so the child's stdout handling (and
|
|
40313
|
+
* sibling fibers) keep flowing; each per-file collection is synchronous, so
|
|
40314
|
+
* the module-level probe recorder never sees interleaved use. Never rejects.
|
|
40315
|
+
*/
|
|
40316
|
+
const collectSidecarProbesForFiles = async (input) => {
|
|
40317
|
+
const buildProbes = (file) => {
|
|
40318
|
+
const absoluteFilePath = Path.resolve(input.rootDirectory, file);
|
|
40319
|
+
let trace;
|
|
40320
|
+
try {
|
|
40321
|
+
trace = collectCrossFileDependencyProbes({
|
|
40322
|
+
absoluteFilePath,
|
|
40323
|
+
sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
|
|
40324
|
+
ruleIds: input.boundedSidecarRuleIds
|
|
40325
|
+
});
|
|
40326
|
+
} catch {
|
|
40327
|
+
return null;
|
|
40328
|
+
}
|
|
40329
|
+
if (trace === null) return null;
|
|
40330
|
+
const probes = [];
|
|
40331
|
+
for (const contentPath of trace.contentPaths) {
|
|
40332
|
+
const relativePath = input.probeAnswers.toRelativePath(contentPath);
|
|
40333
|
+
probes.push({
|
|
40334
|
+
kind: "content",
|
|
40335
|
+
path: relativePath,
|
|
40336
|
+
answer: input.probeAnswers.answerFor("content", relativePath)
|
|
40337
|
+
});
|
|
40338
|
+
}
|
|
40339
|
+
for (const existencePath of trace.existencePaths) {
|
|
40340
|
+
const relativePath = input.probeAnswers.toRelativePath(existencePath);
|
|
40341
|
+
probes.push({
|
|
40342
|
+
kind: "exists",
|
|
40343
|
+
path: relativePath,
|
|
40344
|
+
answer: input.probeAnswers.answerFor("exists", relativePath)
|
|
40345
|
+
});
|
|
40346
|
+
}
|
|
40347
|
+
return probes;
|
|
40348
|
+
};
|
|
40349
|
+
const probesByFile = /* @__PURE__ */ new Map();
|
|
40350
|
+
let collectSliceStartedAt = performance.now();
|
|
40351
|
+
for (const file of input.files) {
|
|
40352
|
+
probesByFile.set(file, buildProbes(file));
|
|
40353
|
+
if (performance.now() - collectSliceStartedAt >= 12) {
|
|
40354
|
+
await yieldToEventLoop();
|
|
40355
|
+
collectSliceStartedAt = performance.now();
|
|
40356
|
+
}
|
|
40357
|
+
}
|
|
40358
|
+
return probesByFile;
|
|
40359
|
+
};
|
|
40360
|
+
/**
|
|
40361
|
+
* Stores each freshly-linted file's probe set + cross-file diagnostics so
|
|
40362
|
+
* the next scan can replay instead of re-linting.
|
|
40363
|
+
*/
|
|
40364
|
+
const storeSidecarEntries = (input) => {
|
|
40365
|
+
let didStoreAnyEntry = false;
|
|
40366
|
+
for (const pass of input.storablePasses) {
|
|
40367
|
+
if (!pass.isTrusted) continue;
|
|
40368
|
+
const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
|
|
40369
|
+
if (diagnosticsByFile === null) continue;
|
|
40370
|
+
for (const file of pass.files) {
|
|
40371
|
+
const cacheKey = input.cacheKeyByFile.get(file);
|
|
40372
|
+
if (cacheKey === void 0) continue;
|
|
40373
|
+
const probes = pass.probesByFile.get(file);
|
|
40374
|
+
if (probes === void 0 || probes === null) continue;
|
|
40375
|
+
input.sidecarCache.store(cacheKey, {
|
|
40376
|
+
probes,
|
|
40377
|
+
diagnostics: diagnosticsByFile.get(file) ?? []
|
|
40378
|
+
});
|
|
40379
|
+
didStoreAnyEntry = true;
|
|
40380
|
+
}
|
|
40381
|
+
}
|
|
40382
|
+
if (didStoreAnyEntry) input.sidecarCache.persist();
|
|
40383
|
+
};
|
|
39928
40384
|
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
39929
40385
|
/**
|
|
39930
40386
|
* Detects an oxlint config-load crash caused by the optional
|
|
@@ -39966,7 +40422,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39966
40422
|
* 6. always restore disable directives + clean up the temp dir
|
|
39967
40423
|
*/
|
|
39968
40424
|
const runOxlint = async (options) => {
|
|
39969
|
-
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;
|
|
40425
|
+
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;
|
|
39970
40426
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39971
40427
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39972
40428
|
validateRuleRegistration();
|
|
@@ -39984,7 +40440,8 @@ const runOxlint = async (options) => {
|
|
|
39984
40440
|
severityControls,
|
|
39985
40441
|
userPlugins,
|
|
39986
40442
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
39987
|
-
ruleSelection: overrides.ruleSelection
|
|
40443
|
+
ruleSelection: overrides.ruleSelection,
|
|
40444
|
+
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
39988
40445
|
});
|
|
39989
40446
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
39990
40447
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -40087,15 +40544,18 @@ const runOxlint = async (options) => {
|
|
|
40087
40544
|
});
|
|
40088
40545
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
40089
40546
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40547
|
+
const contentHashByRelativePath = /* @__PURE__ */ new Map();
|
|
40090
40548
|
const missFiles = [];
|
|
40091
40549
|
const replayedDiagnostics = [];
|
|
40092
40550
|
let hashSliceStartedAt = performance.now();
|
|
40093
40551
|
for (const candidateFile of candidateFiles) {
|
|
40094
|
-
const
|
|
40552
|
+
const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
|
|
40553
|
+
const contentHash = hashFileContents(absoluteCandidatePath);
|
|
40095
40554
|
if (contentHash === null) {
|
|
40096
40555
|
missFiles.push(candidateFile);
|
|
40097
40556
|
continue;
|
|
40098
40557
|
}
|
|
40558
|
+
contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
|
|
40099
40559
|
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
40100
40560
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
40101
40561
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
@@ -40107,43 +40567,111 @@ const runOxlint = async (options) => {
|
|
|
40107
40567
|
}
|
|
40108
40568
|
}
|
|
40109
40569
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40570
|
+
const enabledSidecarRuleIds = Object.keys(buildConfig({
|
|
40571
|
+
extendsPaths: [],
|
|
40572
|
+
ruleSelection: "sidecar"
|
|
40573
|
+
}).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
|
|
40574
|
+
const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40575
|
+
const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
|
|
40576
|
+
const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40577
|
+
const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
|
|
40578
|
+
const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
|
|
40579
|
+
config: buildConfig({
|
|
40580
|
+
extendsPaths: [],
|
|
40581
|
+
ruleSelection: "sidecar",
|
|
40582
|
+
sidecarRuleIdFilter: boundedSidecarRuleIdSet
|
|
40583
|
+
}),
|
|
40584
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
40585
|
+
ignorePatterns: combinedPatterns,
|
|
40586
|
+
tsconfigContent
|
|
40587
|
+
})) : null;
|
|
40588
|
+
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
40589
|
+
rootDirectory,
|
|
40590
|
+
contentHashByRelativePath
|
|
40591
|
+
});
|
|
40592
|
+
const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40593
|
+
files: missFiles,
|
|
40594
|
+
rootDirectory,
|
|
40595
|
+
boundedSidecarRuleIds,
|
|
40596
|
+
probeAnswers
|
|
40597
|
+
});
|
|
40110
40598
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40111
40599
|
extendsPaths: [],
|
|
40112
40600
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40113
40601
|
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40114
40602
|
const missFileSet = new Set(missFiles);
|
|
40115
|
-
const
|
|
40603
|
+
const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
|
|
40604
|
+
const sidecarReplayedDiagnostics = [];
|
|
40605
|
+
const sidecarLintFiles = [];
|
|
40606
|
+
if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
|
|
40607
|
+
else {
|
|
40608
|
+
let verifySliceStartedAt = performance.now();
|
|
40609
|
+
for (const hitFile of hitFiles) {
|
|
40610
|
+
const cacheKey = cacheKeyByFile.get(hitFile);
|
|
40611
|
+
const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
|
|
40612
|
+
if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
|
|
40613
|
+
else sidecarLintFiles.push(hitFile);
|
|
40614
|
+
if (performance.now() - verifySliceStartedAt >= 12) {
|
|
40615
|
+
await yieldToEventLoop();
|
|
40616
|
+
verifySliceStartedAt = performance.now();
|
|
40617
|
+
}
|
|
40618
|
+
}
|
|
40619
|
+
}
|
|
40620
|
+
const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40621
|
+
files: sidecarLintFiles,
|
|
40622
|
+
rootDirectory,
|
|
40623
|
+
boundedSidecarRuleIds,
|
|
40624
|
+
probeAnswers
|
|
40625
|
+
});
|
|
40626
|
+
const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
|
|
40627
|
+
if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
|
|
40628
|
+
const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40116
40629
|
extendsPaths: [],
|
|
40117
|
-
ruleSelection: "sidecar"
|
|
40118
|
-
|
|
40630
|
+
ruleSelection: "sidecar",
|
|
40631
|
+
...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
|
|
40632
|
+
}), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
|
|
40633
|
+
const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
|
|
40634
|
+
extendsPaths: [],
|
|
40635
|
+
ruleSelection: "sidecar",
|
|
40636
|
+
sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
|
|
40637
|
+
}), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
|
|
40638
|
+
diagnostics: [],
|
|
40639
|
+
didDropReactHooksJsPlugin: false,
|
|
40640
|
+
hadPartialFailure: false
|
|
40641
|
+
};
|
|
40119
40642
|
const freshCacheableDiagnostics = [];
|
|
40120
40643
|
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40121
40644
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40122
|
-
|
|
40123
|
-
|
|
40124
|
-
|
|
40125
|
-
let isAttributionSound = true;
|
|
40126
|
-
for (const diagnostic of freshCacheableDiagnostics) {
|
|
40127
|
-
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
40128
|
-
if (missFile === void 0) {
|
|
40129
|
-
isAttributionSound = false;
|
|
40130
|
-
break;
|
|
40131
|
-
}
|
|
40132
|
-
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
40133
|
-
fileDiagnostics.push(diagnostic);
|
|
40134
|
-
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
40135
|
-
}
|
|
40136
|
-
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
40645
|
+
if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
|
|
40646
|
+
const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
|
|
40647
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
|
|
40137
40648
|
for (const missFile of missFiles) {
|
|
40138
40649
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40139
40650
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
40140
40651
|
}
|
|
40141
40652
|
cache.persist();
|
|
40142
40653
|
}
|
|
40654
|
+
if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
|
|
40655
|
+
sidecarCache,
|
|
40656
|
+
cacheKeyByFile,
|
|
40657
|
+
storablePasses: [{
|
|
40658
|
+
files: sidecarLintFiles,
|
|
40659
|
+
diagnostics: boundedSidecarResult.diagnostics,
|
|
40660
|
+
isTrusted: !boundedSidecarResult.hadPartialFailure,
|
|
40661
|
+
probesByFile: await sidecarProbesTask
|
|
40662
|
+
}, {
|
|
40663
|
+
files: missFiles,
|
|
40664
|
+
diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
|
|
40665
|
+
isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
|
|
40666
|
+
probesByFile: await missProbesTask
|
|
40667
|
+
}]
|
|
40668
|
+
});
|
|
40143
40669
|
return dedupeDiagnostics([
|
|
40144
40670
|
...replayedDiagnostics,
|
|
40145
40671
|
...fullResult.diagnostics,
|
|
40146
|
-
...
|
|
40672
|
+
...sidecarReplayedDiagnostics,
|
|
40673
|
+
...boundedSidecarResult.diagnostics,
|
|
40674
|
+
...unboundedSidecarResult.diagnostics
|
|
40147
40675
|
]);
|
|
40148
40676
|
}
|
|
40149
40677
|
const baseArgs = makeBaseArgs(configPath);
|
|
@@ -40241,6 +40769,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40241
40769
|
const concurrency = yield* OxlintConcurrency;
|
|
40242
40770
|
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40243
40771
|
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
40772
|
+
const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
|
|
40244
40773
|
const collectedFailures = [];
|
|
40245
40774
|
const diagnostics = yield* tryPromise({
|
|
40246
40775
|
try: (signal) => runOxlint({
|
|
@@ -40259,7 +40788,9 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40259
40788
|
},
|
|
40260
40789
|
onFileProgress: input.onFileProgress,
|
|
40261
40790
|
perFileLintCacheEnabled,
|
|
40791
|
+
sidecarLintCacheEnabled,
|
|
40262
40792
|
onCacheStats: input.onCacheStats,
|
|
40793
|
+
onSidecarStats: input.onSidecarStats,
|
|
40263
40794
|
spawnTimeoutMs,
|
|
40264
40795
|
outputMaxBytes,
|
|
40265
40796
|
concurrency,
|
|
@@ -40681,6 +41212,20 @@ const writeCachedSocketBody = (cacheFile, body) => {
|
|
|
40681
41212
|
}));
|
|
40682
41213
|
} catch {}
|
|
40683
41214
|
};
|
|
41215
|
+
const pruneExpiredSocketCache = (cacheDirectory) => {
|
|
41216
|
+
try {
|
|
41217
|
+
const supplyChainCacheDirectory = Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR);
|
|
41218
|
+
const expiryThresholdMs = Date.now() - SUPPLY_CHAIN_CACHE_TTL_MS;
|
|
41219
|
+
for (const entryName of NFS.readdirSync(supplyChainCacheDirectory)) {
|
|
41220
|
+
const entryPath = Path.join(supplyChainCacheDirectory, entryName);
|
|
41221
|
+
try {
|
|
41222
|
+
if (NFS.statSync(entryPath).mtimeMs < expiryThresholdMs) NFS.rmSync(entryPath);
|
|
41223
|
+
} catch {
|
|
41224
|
+
continue;
|
|
41225
|
+
}
|
|
41226
|
+
}
|
|
41227
|
+
} catch {}
|
|
41228
|
+
};
|
|
40684
41229
|
const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
|
|
40685
41230
|
const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
|
|
40686
41231
|
if (cacheFile !== null) {
|
|
@@ -40808,6 +41353,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40808
41353
|
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
40809
41354
|
if (dependencies.length === 0) return [];
|
|
40810
41355
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41356
|
+
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
40811
41357
|
const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
|
|
40812
41358
|
const diagnostics = [];
|
|
40813
41359
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
@@ -41034,7 +41580,14 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41034
41580
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
41035
41581
|
rootDirectory: scanDirectory,
|
|
41036
41582
|
parseConcurrency: deadCodeParseConcurrency,
|
|
41037
|
-
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
41583
|
+
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
|
|
41584
|
+
onCacheOutcome: (didHitCache) => {
|
|
41585
|
+
deadCodeCacheHit = didHitCache;
|
|
41586
|
+
},
|
|
41587
|
+
onSummaryCacheStats: (stats) => {
|
|
41588
|
+
deadCodeSummaryCacheHits = stats.hits;
|
|
41589
|
+
deadCodeSummaryCacheMisses = stats.misses;
|
|
41590
|
+
}
|
|
41038
41591
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41039
41592
|
yield* set(deadCodeFailure, {
|
|
41040
41593
|
didFail: true,
|
|
@@ -41062,6 +41615,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41062
41615
|
let lastReportedTotalFileCount = 0;
|
|
41063
41616
|
let lintCacheHitFileCount = null;
|
|
41064
41617
|
let lintCacheTotalFileCount = null;
|
|
41618
|
+
let lintSidecarReplayedFileCount = null;
|
|
41619
|
+
let lintSidecarTotalFileCount = null;
|
|
41620
|
+
let deadCodeCacheHit = null;
|
|
41621
|
+
let deadCodeSummaryCacheHits = null;
|
|
41622
|
+
let deadCodeSummaryCacheMisses = null;
|
|
41065
41623
|
const baseLintStream = linterService.run({
|
|
41066
41624
|
rootDirectory: scanDirectory,
|
|
41067
41625
|
project,
|
|
@@ -41081,6 +41639,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41081
41639
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
41082
41640
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
41083
41641
|
},
|
|
41642
|
+
onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
|
|
41643
|
+
lintSidecarReplayedFileCount = sidecarReplayedFileCount;
|
|
41644
|
+
lintSidecarTotalFileCount = sidecarConsideredFileCount;
|
|
41645
|
+
},
|
|
41084
41646
|
deadlineEpochMs: input.deadlineEpochMs
|
|
41085
41647
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41086
41648
|
yield* set(lintFailure, {
|
|
@@ -41191,6 +41753,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41191
41753
|
securityScanFailed,
|
|
41192
41754
|
lintCacheHitFileCount,
|
|
41193
41755
|
lintCacheTotalFileCount,
|
|
41756
|
+
lintSidecarReplayedFileCount,
|
|
41757
|
+
lintSidecarTotalFileCount,
|
|
41758
|
+
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41759
|
+
deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
|
|
41760
|
+
deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
|
|
41194
41761
|
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
41195
41762
|
};
|
|
41196
41763
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
@@ -41503,11 +42070,9 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
41503
42070
|
const parts = [`v=${version}`];
|
|
41504
42071
|
let directory = projectDirectory;
|
|
41505
42072
|
for (;;) {
|
|
41506
|
-
for (const filename of CONFIG_FINGERPRINT_FILENAMES)
|
|
41507
|
-
const
|
|
41508
|
-
parts.push(`${directory}/${filename}=${
|
|
41509
|
-
} catch {
|
|
41510
|
-
continue;
|
|
42073
|
+
for (const filename of CONFIG_FINGERPRINT_FILENAMES) {
|
|
42074
|
+
const contentHash = hashFileContents(path.join(directory, filename));
|
|
42075
|
+
if (contentHash !== null) parts.push(`${directory}/${filename}=${contentHash}`);
|
|
41511
42076
|
}
|
|
41512
42077
|
const parent = path.dirname(directory);
|
|
41513
42078
|
if (parent === directory) break;
|
|
@@ -41520,7 +42085,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
41520
42085
|
/** Display name used in client-facing messages and progress titles. */
|
|
41521
42086
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
41522
42087
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
41523
|
-
const SERVER_VERSION = "0.
|
|
42088
|
+
const SERVER_VERSION = "0.7.0";
|
|
41524
42089
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
41525
42090
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
41526
42091
|
/**
|
|
@@ -43570,5 +44135,5 @@ const startLanguageServer = () => {
|
|
|
43570
44135
|
};
|
|
43571
44136
|
//#endregion
|
|
43572
44137
|
export { startLanguageServer };
|
|
43573
|
-
!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]="
|
|
43574
|
-
//# debugId=
|
|
44138
|
+
!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]="1b40bd68-7ad4-55e0-a6bd-91b6cdcd0ba8")}catch(e){}}();
|
|
44139
|
+
//# debugId=1b40bd68-7ad4-55e0-a6bd-91b6cdcd0ba8
|