react-doctor 0.6.3-dev.ea9a775 → 0.6.3
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 +92 -678
- package/dist/index.d.ts +0 -26
- package/dist/index.js +77 -557
- package/dist/lsp.js +77 -557
- package/package.json +5 -5
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]="8565c6ea-39f3-51e6-895a-ae9f7c026d0e")}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,
|
|
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";
|
|
13
13
|
import * as OS from "node:os";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -18,7 +18,6 @@ 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";
|
|
22
21
|
import { gzipSync } from "node:zlib";
|
|
23
22
|
//#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
|
|
24
23
|
/**
|
|
@@ -32928,10 +32927,6 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32928
32927
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32929
32928
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32930
32929
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32931
|
-
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32932
|
-
const CORE_PACKAGE_VERSION = "0.6.3";
|
|
32933
|
-
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32934
|
-
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32935
32930
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
32936
32931
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32937
32932
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
@@ -35618,48 +35613,6 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35618
35613
|
if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
|
|
35619
35614
|
return true;
|
|
35620
35615
|
} }) {};
|
|
35621
|
-
/**
|
|
35622
|
-
* Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
|
|
35623
|
-
* active. Defaults ON — warm rescans replay the cross-file rules' cached
|
|
35624
|
-
* diagnostics for every file whose dependency fingerprint (the probe set the
|
|
35625
|
-
* plugin's collectors recorded) still matches the tree, and re-lint only the
|
|
35626
|
-
* rest. Only reachable when the per-file lint cache itself is on (the
|
|
35627
|
-
* sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
|
|
35628
|
-
* `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
|
|
35629
|
-
*
|
|
35630
|
-
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35631
|
-
* - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
|
|
35632
|
-
* per-file cache but run the always-fresh sidecar over every hit
|
|
35633
|
-
* (the pre-cache behavior).
|
|
35634
|
-
*
|
|
35635
|
-
* Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
|
|
35636
|
-
*/
|
|
35637
|
-
var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
|
|
35638
|
-
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35639
|
-
const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
|
|
35640
|
-
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35641
|
-
if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
|
|
35642
|
-
return true;
|
|
35643
|
-
} }) {};
|
|
35644
|
-
/**
|
|
35645
|
-
* Whether the whole-project dead-code result cache
|
|
35646
|
-
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35647
|
-
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
35648
|
-
* unchanged replays the stored diagnostics instead of re-running the
|
|
35649
|
-
* analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
|
|
35650
|
-
*
|
|
35651
|
-
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35652
|
-
* - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
|
|
35653
|
-
*
|
|
35654
|
-
* Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
|
|
35655
|
-
*/
|
|
35656
|
-
var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
|
|
35657
|
-
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35658
|
-
const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
|
|
35659
|
-
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35660
|
-
if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
|
|
35661
|
-
return true;
|
|
35662
|
-
} }) {};
|
|
35663
35616
|
const DIAGNOSTIC_SURFACES = [
|
|
35664
35617
|
"cli",
|
|
35665
35618
|
"prComment",
|
|
@@ -37348,80 +37301,6 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37348
37301
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37349
37302
|
};
|
|
37350
37303
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37351
|
-
const atomicWriteJson = (filePath, value) => {
|
|
37352
|
-
try {
|
|
37353
|
-
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
37354
|
-
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
37355
|
-
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
37356
|
-
NFS.renameSync(temporaryPath, filePath);
|
|
37357
|
-
} catch {
|
|
37358
|
-
return;
|
|
37359
|
-
}
|
|
37360
|
-
};
|
|
37361
|
-
const failOpenReadJson = (filePath, fallback) => {
|
|
37362
|
-
try {
|
|
37363
|
-
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
37364
|
-
} catch {
|
|
37365
|
-
return fallback;
|
|
37366
|
-
}
|
|
37367
|
-
};
|
|
37368
|
-
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
37369
|
-
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
37370
|
-
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
37371
|
-
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
37372
|
-
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
37373
|
-
const collectAnalyzedFileFingerprints = (rootDirectory) => {
|
|
37374
|
-
const fingerprints = [];
|
|
37375
|
-
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
37376
|
-
if (!isFingerprintedFile(name)) continue;
|
|
37377
|
-
try {
|
|
37378
|
-
const fileStat = NFS.statSync(absolutePath);
|
|
37379
|
-
const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
|
|
37380
|
-
fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
|
|
37381
|
-
} catch {}
|
|
37382
|
-
}
|
|
37383
|
-
return fingerprints.sort();
|
|
37384
|
-
};
|
|
37385
|
-
const bundledRequire$2 = createRequire(import.meta.url);
|
|
37386
|
-
const resolveDeslopVersion = () => {
|
|
37387
|
-
try {
|
|
37388
|
-
const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
|
|
37389
|
-
return isRecord(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
37390
|
-
} catch {
|
|
37391
|
-
return "unknown";
|
|
37392
|
-
}
|
|
37393
|
-
};
|
|
37394
|
-
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
37395
|
-
schemaVersion: 1,
|
|
37396
|
-
coreVersion: input.coreVersion,
|
|
37397
|
-
deslopVersion: resolveDeslopVersion(),
|
|
37398
|
-
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
37399
|
-
entryPatterns: input.entryPatterns,
|
|
37400
|
-
ignorePatterns: input.ignorePatterns,
|
|
37401
|
-
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
|
|
37402
|
-
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
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 lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
|
|
37414
|
-
const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
|
|
37415
|
-
if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
|
|
37416
|
-
return decodeCachedDiagnostics(persisted.diagnostics);
|
|
37417
|
-
};
|
|
37418
|
-
const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
|
|
37419
|
-
atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
|
|
37420
|
-
version: 1,
|
|
37421
|
-
key: cacheKey,
|
|
37422
|
-
diagnostics
|
|
37423
|
-
});
|
|
37424
|
-
};
|
|
37425
37304
|
const readSystemFacts = () => ({
|
|
37426
37305
|
availableCores: os.availableParallelism(),
|
|
37427
37306
|
totalMemoryBytes: os.totalmem(),
|
|
@@ -37490,21 +37369,6 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
|
37490
37369
|
releaseSlot();
|
|
37491
37370
|
}
|
|
37492
37371
|
};
|
|
37493
|
-
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
37494
|
-
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
37495
|
-
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
37496
|
-
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
37497
|
-
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
37498
|
-
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
37499
|
-
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
37500
|
-
};
|
|
37501
|
-
const resolveUserCacheScope = () => {
|
|
37502
|
-
try {
|
|
37503
|
-
return String(process.getuid?.() ?? os.userInfo().username);
|
|
37504
|
-
} catch {
|
|
37505
|
-
return "shared";
|
|
37506
|
-
}
|
|
37507
|
-
};
|
|
37508
37372
|
/**
|
|
37509
37373
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37510
37374
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37558,14 +37422,6 @@ process.stdin.on("end", () => {
|
|
|
37558
37422
|
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
37559
37423
|
files: cycle.files,
|
|
37560
37424
|
})),
|
|
37561
|
-
...(result.incrementalCacheStats
|
|
37562
|
-
? {
|
|
37563
|
-
summaryCacheStats: {
|
|
37564
|
-
hits: result.incrementalCacheStats.summaryHits,
|
|
37565
|
-
misses: result.incrementalCacheStats.summaryMisses,
|
|
37566
|
-
},
|
|
37567
|
-
}
|
|
37568
|
-
: {}),
|
|
37569
37425
|
});
|
|
37570
37426
|
|
|
37571
37427
|
const serializeError = (error) =>
|
|
@@ -37589,9 +37445,6 @@ process.stdin.on("end", () => {
|
|
|
37589
37445
|
...(workerInput.ignorePatterns.length > 0
|
|
37590
37446
|
? { ignorePatterns: workerInput.ignorePatterns }
|
|
37591
37447
|
: {}),
|
|
37592
|
-
...(workerInput.incrementalCachePath
|
|
37593
|
-
? { incrementalCachePath: workerInput.incrementalCachePath }
|
|
37594
|
-
: {}),
|
|
37595
37448
|
// We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
|
|
37596
37449
|
// unusedDependencies, circularDependencies). Everything else deslop can compute
|
|
37597
37450
|
// is pure wasted work for us, and it's the bulk of the runtime:
|
|
@@ -37602,18 +37455,12 @@ process.stdin.on("end", () => {
|
|
|
37602
37455
|
// are the single most expensive pass — duplicate-block detection alone was
|
|
37603
37456
|
// ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
|
|
37604
37457
|
// speedup on a large repo.
|
|
37605
|
-
//
|
|
37606
|
-
// simplifiable functions, identity wrappers, …) — ~120 ms of discarded
|
|
37607
|
-
// output per scan, and skipping them lets the incremental cache drop the
|
|
37608
|
-
// DRY-pattern summary fields (the largest slice of the cache file).
|
|
37609
|
-
// All are provably safe: the consumed graph findings are computed by their own
|
|
37458
|
+
// Both are provably safe: the consumed graph findings are computed by their own
|
|
37610
37459
|
// detectors, independent of these passes (confirmed byte-identical on
|
|
37611
|
-
// excalidraw + mui-material + sentry
|
|
37612
|
-
//
|
|
37613
|
-
// for path-alias resolution in the import graph.
|
|
37460
|
+
// excalidraw + mui-material + sentry). tsConfigPath stays — the module resolver
|
|
37461
|
+
// needs it for path-alias resolution in the import graph.
|
|
37614
37462
|
semantic: { enabled: false },
|
|
37615
37463
|
reportCodeQuality: false,
|
|
37616
|
-
reportRedundancy: false,
|
|
37617
37464
|
};
|
|
37618
37465
|
const result = await analyze(defineConfig(config));
|
|
37619
37466
|
emit({ ok: true, result: normalizeResult(result) });
|
|
@@ -37697,23 +37544,13 @@ const parseCircularDependencies = (value) => {
|
|
|
37697
37544
|
}
|
|
37698
37545
|
return circularDependencies;
|
|
37699
37546
|
};
|
|
37700
|
-
const parseSummaryCacheStats = (value) => {
|
|
37701
|
-
if (!isRecord(value)) return void 0;
|
|
37702
|
-
if (typeof value.hits !== "number" || typeof value.misses !== "number") return void 0;
|
|
37703
|
-
return {
|
|
37704
|
-
hits: value.hits,
|
|
37705
|
-
misses: value.misses
|
|
37706
|
-
};
|
|
37707
|
-
};
|
|
37708
37547
|
const parseDeadCodeWorkerResult = (value) => {
|
|
37709
37548
|
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
|
|
37710
|
-
const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
|
|
37711
37549
|
return {
|
|
37712
37550
|
unusedFiles: parseUnusedFiles(value.unusedFiles),
|
|
37713
37551
|
unusedExports: parseUnusedExports(value.unusedExports),
|
|
37714
37552
|
unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
|
|
37715
|
-
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
37716
|
-
...summaryCacheStats ? { summaryCacheStats } : {}
|
|
37553
|
+
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
37717
37554
|
};
|
|
37718
37555
|
};
|
|
37719
37556
|
const parseDeadCodeWorkerError = (value) => {
|
|
@@ -37827,35 +37664,17 @@ const checkDeadCode = async (options) => {
|
|
|
37827
37664
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37828
37665
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37829
37666
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37830
|
-
const tsConfigPath = resolveTsConfigPath(rootDirectory);
|
|
37831
|
-
const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
|
|
37832
|
-
const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
|
|
37833
|
-
rootDirectory,
|
|
37834
|
-
entryPatterns,
|
|
37835
|
-
ignorePatterns,
|
|
37836
|
-
tsConfigPath,
|
|
37837
|
-
deslopJsModuleSpecifier,
|
|
37838
|
-
coreVersion: CORE_PACKAGE_VERSION
|
|
37839
|
-
}) : null;
|
|
37840
|
-
if (cacheKey !== null) {
|
|
37841
|
-
const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
|
|
37842
|
-
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
37843
|
-
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
37844
|
-
}
|
|
37845
|
-
const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
|
|
37846
37667
|
const spawnAndRun = () => {
|
|
37847
37668
|
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37848
37669
|
rootDirectory,
|
|
37849
37670
|
entryPatterns,
|
|
37850
|
-
tsConfigPath,
|
|
37671
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37851
37672
|
ignorePatterns,
|
|
37852
|
-
deslopJsModuleSpecifier,
|
|
37853
|
-
parseConcurrency: options.parseConcurrency
|
|
37854
|
-
incrementalCachePath
|
|
37673
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37674
|
+
parseConcurrency: options.parseConcurrency
|
|
37855
37675
|
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37856
37676
|
};
|
|
37857
37677
|
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37858
|
-
if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
|
|
37859
37678
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37860
37679
|
const diagnostics = [];
|
|
37861
37680
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37912,7 +37731,6 @@ const checkDeadCode = async (options) => {
|
|
|
37912
37731
|
category: DEAD_CODE_CATEGORY
|
|
37913
37732
|
});
|
|
37914
37733
|
}
|
|
37915
|
-
if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
|
|
37916
37734
|
return diagnostics;
|
|
37917
37735
|
};
|
|
37918
37736
|
const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
|
|
@@ -38015,16 +37833,12 @@ var Config = class Config extends Service()("react-doctor/Config") {
|
|
|
38015
37833
|
*/
|
|
38016
37834
|
var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
38017
37835
|
static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
|
|
38018
|
-
const cacheEnabled = yield* DeadCodeResultCacheEnabled;
|
|
38019
37836
|
return yield* tryPromise({
|
|
38020
37837
|
try: (signal) => checkDeadCode({
|
|
38021
37838
|
rootDirectory: input.rootDirectory,
|
|
38022
37839
|
parseConcurrency: input.parseConcurrency,
|
|
38023
37840
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
38024
|
-
abortSignal: signal
|
|
38025
|
-
cacheEnabled,
|
|
38026
|
-
onCacheOutcome: input.onCacheOutcome,
|
|
38027
|
-
onSummaryCacheStats: input.onSummaryCacheStats
|
|
37841
|
+
abortSignal: signal
|
|
38028
37842
|
}),
|
|
38029
37843
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
38030
37844
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
@@ -38894,7 +38708,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38894
38708
|
}
|
|
38895
38709
|
return enabled;
|
|
38896
38710
|
};
|
|
38897
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection
|
|
38711
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38898
38712
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38899
38713
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38900
38714
|
const jsPlugins = [];
|
|
@@ -38906,7 +38720,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38906
38720
|
if (!rule) continue;
|
|
38907
38721
|
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38908
38722
|
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38909
|
-
if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
|
|
38910
38723
|
if (rule.scan !== void 0) continue;
|
|
38911
38724
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38912
38725
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38951,152 +38764,33 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38951
38764
|
}
|
|
38952
38765
|
};
|
|
38953
38766
|
};
|
|
38954
|
-
const
|
|
38955
|
-
const decodeFileDiagnostics = (raw) => {
|
|
38956
|
-
if (!Array.isArray(raw)) return null;
|
|
38767
|
+
const atomicWriteJson = (filePath, value) => {
|
|
38957
38768
|
try {
|
|
38958
|
-
|
|
38959
|
-
|
|
38769
|
+
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
38770
|
+
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
38771
|
+
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
38772
|
+
NFS.renameSync(temporaryPath, filePath);
|
|
38960
38773
|
} catch {
|
|
38961
|
-
return
|
|
38962
|
-
}
|
|
38963
|
-
};
|
|
38964
|
-
const emptyCache$1 = () => ({
|
|
38965
|
-
version: 1,
|
|
38966
|
-
rulesets: {}
|
|
38967
|
-
});
|
|
38968
|
-
const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
|
|
38969
|
-
const entries = /* @__PURE__ */ new Map();
|
|
38970
|
-
const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38971
|
-
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38972
|
-
const bucket = persisted.rulesets[rulesetHash];
|
|
38973
|
-
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
38974
|
-
for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
|
|
38975
|
-
const decoded = decodeFileDiagnostics(rawDiagnostics);
|
|
38976
|
-
if (decoded !== null) entries.set(fileKey, decoded);
|
|
38774
|
+
return;
|
|
38977
38775
|
}
|
|
38978
|
-
return entries;
|
|
38979
38776
|
};
|
|
38980
|
-
const
|
|
38981
|
-
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38982
|
-
const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
|
|
38983
|
-
return {
|
|
38984
|
-
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38985
|
-
store: (fileKey, diagnostics) => {
|
|
38986
|
-
entries.delete(fileKey);
|
|
38987
|
-
entries.set(fileKey, diagnostics);
|
|
38988
|
-
},
|
|
38989
|
-
persist: () => {
|
|
38990
|
-
const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38991
|
-
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38992
|
-
const existingBucket = rulesets[rulesetHash];
|
|
38993
|
-
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
38994
|
-
const ourFiles = {};
|
|
38995
|
-
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
38996
|
-
const cappedEntries = Object.entries({
|
|
38997
|
-
...existingFiles,
|
|
38998
|
-
...ourFiles
|
|
38999
|
-
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
39000
|
-
rulesets[rulesetHash] = {
|
|
39001
|
-
updatedAtMs: Date.now(),
|
|
39002
|
-
files: Object.fromEntries(cappedEntries)
|
|
39003
|
-
};
|
|
39004
|
-
atomicWriteJson(cacheFilePath, {
|
|
39005
|
-
version: 1,
|
|
39006
|
-
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39007
|
-
});
|
|
39008
|
-
}
|
|
39009
|
-
};
|
|
39010
|
-
};
|
|
39011
|
-
const hashFileContents = (filePath) => {
|
|
38777
|
+
const failOpenReadJson = (filePath, fallback) => {
|
|
39012
38778
|
try {
|
|
39013
|
-
return
|
|
38779
|
+
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
39014
38780
|
} catch {
|
|
39015
|
-
return
|
|
38781
|
+
return fallback;
|
|
39016
38782
|
}
|
|
39017
38783
|
};
|
|
39018
|
-
const ABSENT_CONTENT_ANSWER = "absent";
|
|
39019
|
-
/**
|
|
39020
|
-
* Answers dependency probes for the sidecar lint cache — used both when
|
|
39021
|
-
* STORING an entry (turn the collector's probe paths into answers) and when
|
|
39022
|
-
* VERIFYING one (re-answer each stored probe against the current tree).
|
|
39023
|
-
* Store and verify must agree exactly, so both go through this one resolver.
|
|
39024
|
-
*
|
|
39025
|
-
* - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
|
|
39026
|
-
* (missing, a directory, permission failure). Source files reuse the
|
|
39027
|
-
* hashes the cache-partition loop already computed
|
|
39028
|
-
* (`contentHashByRelativePath`); everything else (package.json,
|
|
39029
|
-
* tsconfig, ignored files) is hashed on demand.
|
|
39030
|
-
* - `exists` — the existence class a module-resolution candidate probe
|
|
39031
|
-
* sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
|
|
39032
|
-
* `statSync` follows symlinks, matching the resolvers).
|
|
39033
|
-
*
|
|
39034
|
-
* Answers are memoized per scan: the filesystem is frozen for the run, and
|
|
39035
|
-
* hot dependency paths (a barrel imported by hundreds of files, the shared
|
|
39036
|
-
* tsconfig chain) repeat across many files' probe sets.
|
|
39037
|
-
*/
|
|
39038
|
-
const createSidecarProbeAnswerResolver = (input) => {
|
|
39039
|
-
const contentAnswerByPath = /* @__PURE__ */ new Map();
|
|
39040
|
-
const existsAnswerByPath = /* @__PURE__ */ new Map();
|
|
39041
|
-
const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
|
|
39042
|
-
const contentAnswer = (relativePath) => {
|
|
39043
|
-
const memoized = contentAnswerByPath.get(relativePath);
|
|
39044
|
-
if (memoized !== void 0) return memoized;
|
|
39045
|
-
const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
|
|
39046
|
-
contentAnswerByPath.set(relativePath, answer);
|
|
39047
|
-
return answer;
|
|
39048
|
-
};
|
|
39049
|
-
const existsAnswer = (relativePath) => {
|
|
39050
|
-
const memoized = existsAnswerByPath.get(relativePath);
|
|
39051
|
-
if (memoized !== void 0) return memoized;
|
|
39052
|
-
let answer;
|
|
39053
|
-
try {
|
|
39054
|
-
const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
|
|
39055
|
-
answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
|
|
39056
|
-
} catch {
|
|
39057
|
-
answer = "none";
|
|
39058
|
-
}
|
|
39059
|
-
existsAnswerByPath.set(relativePath, answer);
|
|
39060
|
-
return answer;
|
|
39061
|
-
};
|
|
39062
|
-
return {
|
|
39063
|
-
answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
|
|
39064
|
-
toRelativePath
|
|
39065
|
-
};
|
|
39066
|
-
};
|
|
39067
38784
|
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
39068
|
-
const
|
|
39069
|
-
|
|
39070
|
-
if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
|
|
39071
|
-
const probes = [];
|
|
39072
|
-
for (const rawProbe of raw.probes) {
|
|
39073
|
-
if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
|
|
39074
|
-
const [kind, probePath, answer] = rawProbe;
|
|
39075
|
-
if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
|
|
39076
|
-
probes.push({
|
|
39077
|
-
kind,
|
|
39078
|
-
path: probePath,
|
|
39079
|
-
answer
|
|
39080
|
-
});
|
|
39081
|
-
}
|
|
38785
|
+
const decodeFileDiagnostics = (raw) => {
|
|
38786
|
+
if (!Array.isArray(raw)) return null;
|
|
39082
38787
|
try {
|
|
39083
|
-
for (const entry of raw
|
|
38788
|
+
for (const entry of raw) validateDiagnostic(entry);
|
|
38789
|
+
return raw;
|
|
39084
38790
|
} catch {
|
|
39085
38791
|
return null;
|
|
39086
38792
|
}
|
|
39087
|
-
return {
|
|
39088
|
-
probes,
|
|
39089
|
-
diagnostics: raw.diagnostics
|
|
39090
|
-
};
|
|
39091
38793
|
};
|
|
39092
|
-
const encodeSidecarFileEntry = (entry) => ({
|
|
39093
|
-
probes: entry.probes.map((probe) => [
|
|
39094
|
-
probe.kind,
|
|
39095
|
-
probe.path,
|
|
39096
|
-
probe.answer
|
|
39097
|
-
]),
|
|
39098
|
-
diagnostics: entry.diagnostics
|
|
39099
|
-
});
|
|
39100
38794
|
const emptyCache = () => ({
|
|
39101
38795
|
version: 1,
|
|
39102
38796
|
rulesets: {}
|
|
@@ -39107,20 +38801,20 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
|
39107
38801
|
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
39108
38802
|
const bucket = persisted.rulesets[rulesetHash];
|
|
39109
38803
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39110
|
-
for (const [fileKey,
|
|
39111
|
-
const decoded =
|
|
38804
|
+
for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
|
|
38805
|
+
const decoded = decodeFileDiagnostics(rawDiagnostics);
|
|
39112
38806
|
if (decoded !== null) entries.set(fileKey, decoded);
|
|
39113
38807
|
}
|
|
39114
38808
|
return entries;
|
|
39115
38809
|
};
|
|
39116
|
-
const
|
|
39117
|
-
const cacheFilePath = Path.join(cacheDirectory,
|
|
38810
|
+
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38811
|
+
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
39118
38812
|
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
39119
38813
|
return {
|
|
39120
38814
|
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
39121
|
-
store: (fileKey,
|
|
38815
|
+
store: (fileKey, diagnostics) => {
|
|
39122
38816
|
entries.delete(fileKey);
|
|
39123
|
-
entries.set(fileKey,
|
|
38817
|
+
entries.set(fileKey, diagnostics);
|
|
39124
38818
|
},
|
|
39125
38819
|
persist: () => {
|
|
39126
38820
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
@@ -39128,7 +38822,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
39128
38822
|
const existingBucket = rulesets[rulesetHash];
|
|
39129
38823
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39130
38824
|
const ourFiles = {};
|
|
39131
|
-
for (const [fileKey,
|
|
38825
|
+
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
39132
38826
|
const cappedEntries = Object.entries({
|
|
39133
38827
|
...existingFiles,
|
|
39134
38828
|
...ourFiles
|
|
@@ -40143,6 +39837,13 @@ const validateRuleRegistration = () => {
|
|
|
40143
39837
|
].filter((entry) => entry !== null).join("; ");
|
|
40144
39838
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
40145
39839
|
};
|
|
39840
|
+
const hashFileContents = (filePath) => {
|
|
39841
|
+
try {
|
|
39842
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39843
|
+
} catch {
|
|
39844
|
+
return null;
|
|
39845
|
+
}
|
|
39846
|
+
};
|
|
40146
39847
|
/**
|
|
40147
39848
|
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
40148
39849
|
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
@@ -40207,6 +39908,21 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
|
40207
39908
|
}
|
|
40208
39909
|
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
40209
39910
|
};
|
|
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
|
+
};
|
|
40210
39926
|
/**
|
|
40211
39927
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
40212
39928
|
* the runner: once for the primary scan, once for the
|
|
@@ -40224,106 +39940,6 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
40224
39940
|
NFS.closeSync(fileHandle);
|
|
40225
39941
|
}
|
|
40226
39942
|
};
|
|
40227
|
-
/**
|
|
40228
|
-
* Attributes diagnostics back to the file that produced them by the
|
|
40229
|
-
* normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
|
|
40230
|
-
* attributed — the path forms don't align, so the caller must skip its cache
|
|
40231
|
-
* store rather than risk caching a wrong empty result for a file that
|
|
40232
|
-
* actually had diagnostics.
|
|
40233
|
-
*/
|
|
40234
|
-
const attributeDiagnosticsToFiles = (diagnostics, files) => {
|
|
40235
|
-
const fileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40236
|
-
for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
|
|
40237
|
-
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40238
|
-
for (const diagnostic of diagnostics) {
|
|
40239
|
-
const file = fileByNormalizedPath.get(diagnostic.filePath);
|
|
40240
|
-
if (file === void 0) return null;
|
|
40241
|
-
const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
|
|
40242
|
-
fileDiagnostics.push(diagnostic);
|
|
40243
|
-
diagnosticsByFile.set(file, fileDiagnostics);
|
|
40244
|
-
}
|
|
40245
|
-
return diagnosticsByFile;
|
|
40246
|
-
};
|
|
40247
|
-
/**
|
|
40248
|
-
* Collects each file's cross-file dependency probes (via the plugin's
|
|
40249
|
-
* collectors) with their current answers. Fail-open per file: an unreadable /
|
|
40250
|
-
* unparseable / collector-failing file maps to `null` (no entry stored — it
|
|
40251
|
-
* re-lints next scan). The filesystem is frozen for the scan, so this can run
|
|
40252
|
-
* CONCURRENTLY with an oxlint child-process await — the caller starts it
|
|
40253
|
-
* unawaited before a lint pass and awaits it at store time, hiding the
|
|
40254
|
-
* in-process parse + resolution cost under the subprocess wait. The loop
|
|
40255
|
-
* yields on the cooperative time budget so the child's stdout handling (and
|
|
40256
|
-
* sibling fibers) keep flowing; each per-file collection is synchronous, so
|
|
40257
|
-
* the module-level probe recorder never sees interleaved use. Never rejects.
|
|
40258
|
-
*/
|
|
40259
|
-
const collectSidecarProbesForFiles = async (input) => {
|
|
40260
|
-
const buildProbes = (file) => {
|
|
40261
|
-
const absoluteFilePath = Path.resolve(input.rootDirectory, file);
|
|
40262
|
-
let trace;
|
|
40263
|
-
try {
|
|
40264
|
-
trace = collectCrossFileDependencyProbes({
|
|
40265
|
-
absoluteFilePath,
|
|
40266
|
-
sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
|
|
40267
|
-
ruleIds: input.boundedSidecarRuleIds
|
|
40268
|
-
});
|
|
40269
|
-
} catch {
|
|
40270
|
-
return null;
|
|
40271
|
-
}
|
|
40272
|
-
if (trace === null) return null;
|
|
40273
|
-
const probes = [];
|
|
40274
|
-
for (const contentPath of trace.contentPaths) {
|
|
40275
|
-
const relativePath = input.probeAnswers.toRelativePath(contentPath);
|
|
40276
|
-
probes.push({
|
|
40277
|
-
kind: "content",
|
|
40278
|
-
path: relativePath,
|
|
40279
|
-
answer: input.probeAnswers.answerFor("content", relativePath)
|
|
40280
|
-
});
|
|
40281
|
-
}
|
|
40282
|
-
for (const existencePath of trace.existencePaths) {
|
|
40283
|
-
const relativePath = input.probeAnswers.toRelativePath(existencePath);
|
|
40284
|
-
probes.push({
|
|
40285
|
-
kind: "exists",
|
|
40286
|
-
path: relativePath,
|
|
40287
|
-
answer: input.probeAnswers.answerFor("exists", relativePath)
|
|
40288
|
-
});
|
|
40289
|
-
}
|
|
40290
|
-
return probes;
|
|
40291
|
-
};
|
|
40292
|
-
const probesByFile = /* @__PURE__ */ new Map();
|
|
40293
|
-
let collectSliceStartedAt = performance.now();
|
|
40294
|
-
for (const file of input.files) {
|
|
40295
|
-
probesByFile.set(file, buildProbes(file));
|
|
40296
|
-
if (performance.now() - collectSliceStartedAt >= 12) {
|
|
40297
|
-
await yieldToEventLoop();
|
|
40298
|
-
collectSliceStartedAt = performance.now();
|
|
40299
|
-
}
|
|
40300
|
-
}
|
|
40301
|
-
return probesByFile;
|
|
40302
|
-
};
|
|
40303
|
-
/**
|
|
40304
|
-
* Stores each freshly-linted file's probe set + cross-file diagnostics so
|
|
40305
|
-
* the next scan can replay instead of re-linting.
|
|
40306
|
-
*/
|
|
40307
|
-
const storeSidecarEntries = (input) => {
|
|
40308
|
-
let didStoreAnyEntry = false;
|
|
40309
|
-
for (const pass of input.storablePasses) {
|
|
40310
|
-
if (!pass.isTrusted) continue;
|
|
40311
|
-
const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
|
|
40312
|
-
if (diagnosticsByFile === null) continue;
|
|
40313
|
-
for (const file of pass.files) {
|
|
40314
|
-
const cacheKey = input.cacheKeyByFile.get(file);
|
|
40315
|
-
if (cacheKey === void 0) continue;
|
|
40316
|
-
const probes = pass.probesByFile.get(file);
|
|
40317
|
-
if (probes === void 0 || probes === null) continue;
|
|
40318
|
-
input.sidecarCache.store(cacheKey, {
|
|
40319
|
-
probes,
|
|
40320
|
-
diagnostics: diagnosticsByFile.get(file) ?? []
|
|
40321
|
-
});
|
|
40322
|
-
didStoreAnyEntry = true;
|
|
40323
|
-
}
|
|
40324
|
-
}
|
|
40325
|
-
if (didStoreAnyEntry) input.sidecarCache.persist();
|
|
40326
|
-
};
|
|
40327
39943
|
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
40328
39944
|
/**
|
|
40329
39945
|
* Detects an oxlint config-load crash caused by the optional
|
|
@@ -40365,7 +39981,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
40365
39981
|
* 6. always restore disable directives + clean up the temp dir
|
|
40366
39982
|
*/
|
|
40367
39983
|
const runOxlint = async (options) => {
|
|
40368
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false,
|
|
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;
|
|
40369
39985
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40370
39986
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40371
39987
|
validateRuleRegistration();
|
|
@@ -40383,8 +39999,7 @@ const runOxlint = async (options) => {
|
|
|
40383
39999
|
severityControls,
|
|
40384
40000
|
userPlugins,
|
|
40385
40001
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
40386
|
-
ruleSelection: overrides.ruleSelection
|
|
40387
|
-
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
40002
|
+
ruleSelection: overrides.ruleSelection
|
|
40388
40003
|
});
|
|
40389
40004
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
40390
40005
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -40487,18 +40102,15 @@ const runOxlint = async (options) => {
|
|
|
40487
40102
|
});
|
|
40488
40103
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
40489
40104
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40490
|
-
const contentHashByRelativePath = /* @__PURE__ */ new Map();
|
|
40491
40105
|
const missFiles = [];
|
|
40492
40106
|
const replayedDiagnostics = [];
|
|
40493
40107
|
let hashSliceStartedAt = performance.now();
|
|
40494
40108
|
for (const candidateFile of candidateFiles) {
|
|
40495
|
-
const
|
|
40496
|
-
const contentHash = hashFileContents(absoluteCandidatePath);
|
|
40109
|
+
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
40497
40110
|
if (contentHash === null) {
|
|
40498
40111
|
missFiles.push(candidateFile);
|
|
40499
40112
|
continue;
|
|
40500
40113
|
}
|
|
40501
|
-
contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
|
|
40502
40114
|
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
40503
40115
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
40504
40116
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
@@ -40510,111 +40122,43 @@ const runOxlint = async (options) => {
|
|
|
40510
40122
|
}
|
|
40511
40123
|
}
|
|
40512
40124
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40513
|
-
const enabledSidecarRuleIds = Object.keys(buildConfig({
|
|
40514
|
-
extendsPaths: [],
|
|
40515
|
-
ruleSelection: "sidecar"
|
|
40516
|
-
}).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
|
|
40517
|
-
const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40518
|
-
const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
|
|
40519
|
-
const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40520
|
-
const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
|
|
40521
|
-
const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
|
|
40522
|
-
config: buildConfig({
|
|
40523
|
-
extendsPaths: [],
|
|
40524
|
-
ruleSelection: "sidecar",
|
|
40525
|
-
sidecarRuleIdFilter: boundedSidecarRuleIdSet
|
|
40526
|
-
}),
|
|
40527
|
-
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
40528
|
-
ignorePatterns: combinedPatterns,
|
|
40529
|
-
tsconfigContent
|
|
40530
|
-
})) : null;
|
|
40531
|
-
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
40532
|
-
rootDirectory,
|
|
40533
|
-
contentHashByRelativePath
|
|
40534
|
-
});
|
|
40535
|
-
const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40536
|
-
files: missFiles,
|
|
40537
|
-
rootDirectory,
|
|
40538
|
-
boundedSidecarRuleIds,
|
|
40539
|
-
probeAnswers
|
|
40540
|
-
});
|
|
40541
40125
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40542
40126
|
extendsPaths: [],
|
|
40543
40127
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40544
40128
|
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40545
40129
|
const missFileSet = new Set(missFiles);
|
|
40546
|
-
const
|
|
40547
|
-
const sidecarReplayedDiagnostics = [];
|
|
40548
|
-
const sidecarLintFiles = [];
|
|
40549
|
-
if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
|
|
40550
|
-
else {
|
|
40551
|
-
let verifySliceStartedAt = performance.now();
|
|
40552
|
-
for (const hitFile of hitFiles) {
|
|
40553
|
-
const cacheKey = cacheKeyByFile.get(hitFile);
|
|
40554
|
-
const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
|
|
40555
|
-
if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
|
|
40556
|
-
else sidecarLintFiles.push(hitFile);
|
|
40557
|
-
if (performance.now() - verifySliceStartedAt >= 12) {
|
|
40558
|
-
await yieldToEventLoop();
|
|
40559
|
-
verifySliceStartedAt = performance.now();
|
|
40560
|
-
}
|
|
40561
|
-
}
|
|
40562
|
-
}
|
|
40563
|
-
const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40564
|
-
files: sidecarLintFiles,
|
|
40565
|
-
rootDirectory,
|
|
40566
|
-
boundedSidecarRuleIds,
|
|
40567
|
-
probeAnswers
|
|
40568
|
-
});
|
|
40569
|
-
const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
|
|
40570
|
-
if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
|
|
40571
|
-
const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40130
|
+
const sidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40572
40131
|
extendsPaths: [],
|
|
40573
|
-
ruleSelection: "sidecar"
|
|
40574
|
-
|
|
40575
|
-
}), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
|
|
40576
|
-
const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
|
|
40577
|
-
extendsPaths: [],
|
|
40578
|
-
ruleSelection: "sidecar",
|
|
40579
|
-
sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
|
|
40580
|
-
}), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
|
|
40581
|
-
diagnostics: [],
|
|
40582
|
-
didDropReactHooksJsPlugin: false,
|
|
40583
|
-
hadPartialFailure: false
|
|
40584
|
-
};
|
|
40132
|
+
ruleSelection: "sidecar"
|
|
40133
|
+
}), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
|
|
40585
40134
|
const freshCacheableDiagnostics = [];
|
|
40586
40135
|
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40587
40136
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40588
|
-
|
|
40589
|
-
const
|
|
40590
|
-
|
|
40137
|
+
const missFileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40138
|
+
for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
|
|
40139
|
+
const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
|
|
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) {
|
|
40591
40152
|
for (const missFile of missFiles) {
|
|
40592
40153
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40593
40154
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
40594
40155
|
}
|
|
40595
40156
|
cache.persist();
|
|
40596
40157
|
}
|
|
40597
|
-
if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
|
|
40598
|
-
sidecarCache,
|
|
40599
|
-
cacheKeyByFile,
|
|
40600
|
-
storablePasses: [{
|
|
40601
|
-
files: sidecarLintFiles,
|
|
40602
|
-
diagnostics: boundedSidecarResult.diagnostics,
|
|
40603
|
-
isTrusted: !boundedSidecarResult.hadPartialFailure,
|
|
40604
|
-
probesByFile: await sidecarProbesTask
|
|
40605
|
-
}, {
|
|
40606
|
-
files: missFiles,
|
|
40607
|
-
diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
|
|
40608
|
-
isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
|
|
40609
|
-
probesByFile: await missProbesTask
|
|
40610
|
-
}]
|
|
40611
|
-
});
|
|
40612
40158
|
return dedupeDiagnostics([
|
|
40613
40159
|
...replayedDiagnostics,
|
|
40614
40160
|
...fullResult.diagnostics,
|
|
40615
|
-
...
|
|
40616
|
-
...boundedSidecarResult.diagnostics,
|
|
40617
|
-
...unboundedSidecarResult.diagnostics
|
|
40161
|
+
...sidecarResult.diagnostics
|
|
40618
40162
|
]);
|
|
40619
40163
|
}
|
|
40620
40164
|
const baseArgs = makeBaseArgs(configPath);
|
|
@@ -40712,7 +40256,6 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40712
40256
|
const concurrency = yield* OxlintConcurrency;
|
|
40713
40257
|
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40714
40258
|
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
40715
|
-
const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
|
|
40716
40259
|
const collectedFailures = [];
|
|
40717
40260
|
const diagnostics = yield* tryPromise({
|
|
40718
40261
|
try: (signal) => runOxlint({
|
|
@@ -40731,9 +40274,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40731
40274
|
},
|
|
40732
40275
|
onFileProgress: input.onFileProgress,
|
|
40733
40276
|
perFileLintCacheEnabled,
|
|
40734
|
-
sidecarLintCacheEnabled,
|
|
40735
40277
|
onCacheStats: input.onCacheStats,
|
|
40736
|
-
onSidecarStats: input.onSidecarStats,
|
|
40737
40278
|
spawnTimeoutMs,
|
|
40738
40279
|
outputMaxBytes,
|
|
40739
40280
|
concurrency,
|
|
@@ -41508,14 +41049,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41508
41049
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
41509
41050
|
rootDirectory: scanDirectory,
|
|
41510
41051
|
parseConcurrency: deadCodeParseConcurrency,
|
|
41511
|
-
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
41512
|
-
onCacheOutcome: (didHitCache) => {
|
|
41513
|
-
deadCodeCacheHit = didHitCache;
|
|
41514
|
-
},
|
|
41515
|
-
onSummaryCacheStats: (stats) => {
|
|
41516
|
-
deadCodeSummaryCacheHits = stats.hits;
|
|
41517
|
-
deadCodeSummaryCacheMisses = stats.misses;
|
|
41518
|
-
}
|
|
41052
|
+
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
41519
41053
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41520
41054
|
yield* set(deadCodeFailure, {
|
|
41521
41055
|
didFail: true,
|
|
@@ -41543,11 +41077,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41543
41077
|
let lastReportedTotalFileCount = 0;
|
|
41544
41078
|
let lintCacheHitFileCount = null;
|
|
41545
41079
|
let lintCacheTotalFileCount = null;
|
|
41546
|
-
let lintSidecarReplayedFileCount = null;
|
|
41547
|
-
let lintSidecarTotalFileCount = null;
|
|
41548
|
-
let deadCodeCacheHit = null;
|
|
41549
|
-
let deadCodeSummaryCacheHits = null;
|
|
41550
|
-
let deadCodeSummaryCacheMisses = null;
|
|
41551
41080
|
const baseLintStream = linterService.run({
|
|
41552
41081
|
rootDirectory: scanDirectory,
|
|
41553
41082
|
project,
|
|
@@ -41567,10 +41096,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41567
41096
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
41568
41097
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
41569
41098
|
},
|
|
41570
|
-
onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
|
|
41571
|
-
lintSidecarReplayedFileCount = sidecarReplayedFileCount;
|
|
41572
|
-
lintSidecarTotalFileCount = sidecarConsideredFileCount;
|
|
41573
|
-
},
|
|
41574
41099
|
deadlineEpochMs: input.deadlineEpochMs
|
|
41575
41100
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41576
41101
|
yield* set(lintFailure, {
|
|
@@ -41681,11 +41206,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41681
41206
|
securityScanFailed,
|
|
41682
41207
|
lintCacheHitFileCount,
|
|
41683
41208
|
lintCacheTotalFileCount,
|
|
41684
|
-
lintSidecarReplayedFileCount,
|
|
41685
|
-
lintSidecarTotalFileCount,
|
|
41686
|
-
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41687
|
-
deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
|
|
41688
|
-
deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
|
|
41689
41209
|
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
41690
41210
|
};
|
|
41691
41211
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
@@ -42326,4 +41846,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
42326
41846
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
42327
41847
|
|
|
42328
41848
|
//# sourceMappingURL=index.js.map
|
|
42329
|
-
//# debugId=
|
|
41849
|
+
//# debugId=8565c6ea-39f3-51e6-895a-ae9f7c026d0e
|