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/lsp.js
CHANGED
|
@@ -8,14 +8,13 @@ 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,
|
|
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";
|
|
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";
|
|
19
18
|
import { gzipSync } from "node:zlib";
|
|
20
19
|
import { CodeActionKind, CodeActionTriggerKind, DidChangeWatchedFilesNotification, DocumentDiagnosticReportKind, FileChangeType, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/node.js";
|
|
21
20
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
@@ -32987,10 +32986,6 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32987
32986
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32988
32987
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32989
32988
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32990
|
-
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32991
|
-
const CORE_PACKAGE_VERSION = "0.6.3";
|
|
32992
|
-
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32993
|
-
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32994
32989
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
32995
32990
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32996
32991
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
@@ -35651,48 +35646,6 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35651
35646
|
if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
|
|
35652
35647
|
return true;
|
|
35653
35648
|
} }) {};
|
|
35654
|
-
/**
|
|
35655
|
-
* Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
|
|
35656
|
-
* active. Defaults ON — warm rescans replay the cross-file rules' cached
|
|
35657
|
-
* diagnostics for every file whose dependency fingerprint (the probe set the
|
|
35658
|
-
* plugin's collectors recorded) still matches the tree, and re-lint only the
|
|
35659
|
-
* rest. Only reachable when the per-file lint cache itself is on (the
|
|
35660
|
-
* sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
|
|
35661
|
-
* `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
|
|
35662
|
-
*
|
|
35663
|
-
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35664
|
-
* - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
|
|
35665
|
-
* per-file cache but run the always-fresh sidecar over every hit
|
|
35666
|
-
* (the pre-cache behavior).
|
|
35667
|
-
*
|
|
35668
|
-
* Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
|
|
35669
|
-
*/
|
|
35670
|
-
var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
|
|
35671
|
-
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35672
|
-
const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
|
|
35673
|
-
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35674
|
-
if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
|
|
35675
|
-
return true;
|
|
35676
|
-
} }) {};
|
|
35677
|
-
/**
|
|
35678
|
-
* Whether the whole-project dead-code result cache
|
|
35679
|
-
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35680
|
-
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
35681
|
-
* unchanged replays the stored diagnostics instead of re-running the
|
|
35682
|
-
* analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
|
|
35683
|
-
*
|
|
35684
|
-
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35685
|
-
* - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
|
|
35686
|
-
*
|
|
35687
|
-
* Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
|
|
35688
|
-
*/
|
|
35689
|
-
var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
|
|
35690
|
-
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35691
|
-
const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
|
|
35692
|
-
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35693
|
-
if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
|
|
35694
|
-
return true;
|
|
35695
|
-
} }) {};
|
|
35696
35649
|
const DIAGNOSTIC_SURFACES = [
|
|
35697
35650
|
"cli",
|
|
35698
35651
|
"prComment",
|
|
@@ -37333,80 +37286,6 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37333
37286
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37334
37287
|
};
|
|
37335
37288
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37336
|
-
const atomicWriteJson = (filePath, value) => {
|
|
37337
|
-
try {
|
|
37338
|
-
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
37339
|
-
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
37340
|
-
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
37341
|
-
NFS.renameSync(temporaryPath, filePath);
|
|
37342
|
-
} catch {
|
|
37343
|
-
return;
|
|
37344
|
-
}
|
|
37345
|
-
};
|
|
37346
|
-
const failOpenReadJson = (filePath, fallback) => {
|
|
37347
|
-
try {
|
|
37348
|
-
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
37349
|
-
} catch {
|
|
37350
|
-
return fallback;
|
|
37351
|
-
}
|
|
37352
|
-
};
|
|
37353
|
-
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
37354
|
-
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
37355
|
-
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
37356
|
-
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
37357
|
-
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
37358
|
-
const collectAnalyzedFileFingerprints = (rootDirectory) => {
|
|
37359
|
-
const fingerprints = [];
|
|
37360
|
-
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
37361
|
-
if (!isFingerprintedFile(name)) continue;
|
|
37362
|
-
try {
|
|
37363
|
-
const fileStat = NFS.statSync(absolutePath);
|
|
37364
|
-
const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
|
|
37365
|
-
fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
|
|
37366
|
-
} catch {}
|
|
37367
|
-
}
|
|
37368
|
-
return fingerprints.sort();
|
|
37369
|
-
};
|
|
37370
|
-
const bundledRequire$2 = createRequire(import.meta.url);
|
|
37371
|
-
const resolveDeslopVersion = () => {
|
|
37372
|
-
try {
|
|
37373
|
-
const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
|
|
37374
|
-
return isRecord(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
37375
|
-
} catch {
|
|
37376
|
-
return "unknown";
|
|
37377
|
-
}
|
|
37378
|
-
};
|
|
37379
|
-
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
37380
|
-
schemaVersion: 1,
|
|
37381
|
-
coreVersion: input.coreVersion,
|
|
37382
|
-
deslopVersion: resolveDeslopVersion(),
|
|
37383
|
-
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
37384
|
-
entryPatterns: input.entryPatterns,
|
|
37385
|
-
ignorePatterns: input.ignorePatterns,
|
|
37386
|
-
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
|
|
37387
|
-
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
37388
|
-
})).digest("hex");
|
|
37389
|
-
const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
|
|
37390
|
-
const decodeCachedDiagnostics = (raw) => {
|
|
37391
|
-
try {
|
|
37392
|
-
for (const entry of raw) validateDiagnostic$2(entry);
|
|
37393
|
-
return raw;
|
|
37394
|
-
} catch {
|
|
37395
|
-
return null;
|
|
37396
|
-
}
|
|
37397
|
-
};
|
|
37398
|
-
const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
|
|
37399
|
-
const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
|
|
37400
|
-
if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
|
|
37401
|
-
return decodeCachedDiagnostics(persisted.diagnostics);
|
|
37402
|
-
};
|
|
37403
|
-
const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
|
|
37404
|
-
atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
|
|
37405
|
-
version: 1,
|
|
37406
|
-
key: cacheKey,
|
|
37407
|
-
diagnostics
|
|
37408
|
-
});
|
|
37409
|
-
};
|
|
37410
37289
|
const readSystemFacts = () => ({
|
|
37411
37290
|
availableCores: os.availableParallelism(),
|
|
37412
37291
|
totalMemoryBytes: os.totalmem(),
|
|
@@ -37475,21 +37354,6 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
|
37475
37354
|
releaseSlot();
|
|
37476
37355
|
}
|
|
37477
37356
|
};
|
|
37478
|
-
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
37479
|
-
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
37480
|
-
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
37481
|
-
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
37482
|
-
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
37483
|
-
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
37484
|
-
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
37485
|
-
};
|
|
37486
|
-
const resolveUserCacheScope = () => {
|
|
37487
|
-
try {
|
|
37488
|
-
return String(process.getuid?.() ?? os.userInfo().username);
|
|
37489
|
-
} catch {
|
|
37490
|
-
return "shared";
|
|
37491
|
-
}
|
|
37492
|
-
};
|
|
37493
37357
|
/**
|
|
37494
37358
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37495
37359
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37543,14 +37407,6 @@ process.stdin.on("end", () => {
|
|
|
37543
37407
|
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
37544
37408
|
files: cycle.files,
|
|
37545
37409
|
})),
|
|
37546
|
-
...(result.incrementalCacheStats
|
|
37547
|
-
? {
|
|
37548
|
-
summaryCacheStats: {
|
|
37549
|
-
hits: result.incrementalCacheStats.summaryHits,
|
|
37550
|
-
misses: result.incrementalCacheStats.summaryMisses,
|
|
37551
|
-
},
|
|
37552
|
-
}
|
|
37553
|
-
: {}),
|
|
37554
37410
|
});
|
|
37555
37411
|
|
|
37556
37412
|
const serializeError = (error) =>
|
|
@@ -37574,9 +37430,6 @@ process.stdin.on("end", () => {
|
|
|
37574
37430
|
...(workerInput.ignorePatterns.length > 0
|
|
37575
37431
|
? { ignorePatterns: workerInput.ignorePatterns }
|
|
37576
37432
|
: {}),
|
|
37577
|
-
...(workerInput.incrementalCachePath
|
|
37578
|
-
? { incrementalCachePath: workerInput.incrementalCachePath }
|
|
37579
|
-
: {}),
|
|
37580
37433
|
// We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
|
|
37581
37434
|
// unusedDependencies, circularDependencies). Everything else deslop can compute
|
|
37582
37435
|
// is pure wasted work for us, and it's the bulk of the runtime:
|
|
@@ -37587,18 +37440,12 @@ process.stdin.on("end", () => {
|
|
|
37587
37440
|
// are the single most expensive pass — duplicate-block detection alone was
|
|
37588
37441
|
// ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
|
|
37589
37442
|
// speedup on a large repo.
|
|
37590
|
-
//
|
|
37591
|
-
// simplifiable functions, identity wrappers, …) — ~120 ms of discarded
|
|
37592
|
-
// output per scan, and skipping them lets the incremental cache drop the
|
|
37593
|
-
// DRY-pattern summary fields (the largest slice of the cache file).
|
|
37594
|
-
// All are provably safe: the consumed graph findings are computed by their own
|
|
37443
|
+
// Both are provably safe: the consumed graph findings are computed by their own
|
|
37595
37444
|
// detectors, independent of these passes (confirmed byte-identical on
|
|
37596
|
-
// excalidraw + mui-material + sentry
|
|
37597
|
-
//
|
|
37598
|
-
// for path-alias resolution in the import graph.
|
|
37445
|
+
// excalidraw + mui-material + sentry). tsConfigPath stays — the module resolver
|
|
37446
|
+
// needs it for path-alias resolution in the import graph.
|
|
37599
37447
|
semantic: { enabled: false },
|
|
37600
37448
|
reportCodeQuality: false,
|
|
37601
|
-
reportRedundancy: false,
|
|
37602
37449
|
};
|
|
37603
37450
|
const result = await analyze(defineConfig(config));
|
|
37604
37451
|
emit({ ok: true, result: normalizeResult(result) });
|
|
@@ -37682,23 +37529,13 @@ const parseCircularDependencies = (value) => {
|
|
|
37682
37529
|
}
|
|
37683
37530
|
return circularDependencies;
|
|
37684
37531
|
};
|
|
37685
|
-
const parseSummaryCacheStats = (value) => {
|
|
37686
|
-
if (!isRecord(value)) return void 0;
|
|
37687
|
-
if (typeof value.hits !== "number" || typeof value.misses !== "number") return void 0;
|
|
37688
|
-
return {
|
|
37689
|
-
hits: value.hits,
|
|
37690
|
-
misses: value.misses
|
|
37691
|
-
};
|
|
37692
|
-
};
|
|
37693
37532
|
const parseDeadCodeWorkerResult = (value) => {
|
|
37694
37533
|
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
|
|
37695
|
-
const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
|
|
37696
37534
|
return {
|
|
37697
37535
|
unusedFiles: parseUnusedFiles(value.unusedFiles),
|
|
37698
37536
|
unusedExports: parseUnusedExports(value.unusedExports),
|
|
37699
37537
|
unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
|
|
37700
|
-
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
37701
|
-
...summaryCacheStats ? { summaryCacheStats } : {}
|
|
37538
|
+
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
37702
37539
|
};
|
|
37703
37540
|
};
|
|
37704
37541
|
const parseDeadCodeWorkerError = (value) => {
|
|
@@ -37812,35 +37649,17 @@ const checkDeadCode = async (options) => {
|
|
|
37812
37649
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37813
37650
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37814
37651
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37815
|
-
const tsConfigPath = resolveTsConfigPath(rootDirectory);
|
|
37816
|
-
const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
|
|
37817
|
-
const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
|
|
37818
|
-
rootDirectory,
|
|
37819
|
-
entryPatterns,
|
|
37820
|
-
ignorePatterns,
|
|
37821
|
-
tsConfigPath,
|
|
37822
|
-
deslopJsModuleSpecifier,
|
|
37823
|
-
coreVersion: CORE_PACKAGE_VERSION
|
|
37824
|
-
}) : null;
|
|
37825
|
-
if (cacheKey !== null) {
|
|
37826
|
-
const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
|
|
37827
|
-
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
37828
|
-
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
37829
|
-
}
|
|
37830
|
-
const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
|
|
37831
37652
|
const spawnAndRun = () => {
|
|
37832
37653
|
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37833
37654
|
rootDirectory,
|
|
37834
37655
|
entryPatterns,
|
|
37835
|
-
tsConfigPath,
|
|
37656
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37836
37657
|
ignorePatterns,
|
|
37837
|
-
deslopJsModuleSpecifier,
|
|
37838
|
-
parseConcurrency: options.parseConcurrency
|
|
37839
|
-
incrementalCachePath
|
|
37658
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37659
|
+
parseConcurrency: options.parseConcurrency
|
|
37840
37660
|
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37841
37661
|
};
|
|
37842
37662
|
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37843
|
-
if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
|
|
37844
37663
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37845
37664
|
const diagnostics = [];
|
|
37846
37665
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37897,7 +37716,6 @@ const checkDeadCode = async (options) => {
|
|
|
37897
37716
|
category: DEAD_CODE_CATEGORY
|
|
37898
37717
|
});
|
|
37899
37718
|
}
|
|
37900
|
-
if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
|
|
37901
37719
|
return diagnostics;
|
|
37902
37720
|
};
|
|
37903
37721
|
const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
|
|
@@ -38000,16 +37818,12 @@ var Config = class Config extends Service()("react-doctor/Config") {
|
|
|
38000
37818
|
*/
|
|
38001
37819
|
var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
38002
37820
|
static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
|
|
38003
|
-
const cacheEnabled = yield* DeadCodeResultCacheEnabled;
|
|
38004
37821
|
return yield* tryPromise({
|
|
38005
37822
|
try: (signal) => checkDeadCode({
|
|
38006
37823
|
rootDirectory: input.rootDirectory,
|
|
38007
37824
|
parseConcurrency: input.parseConcurrency,
|
|
38008
37825
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
38009
|
-
abortSignal: signal
|
|
38010
|
-
cacheEnabled,
|
|
38011
|
-
onCacheOutcome: input.onCacheOutcome,
|
|
38012
|
-
onSummaryCacheStats: input.onSummaryCacheStats
|
|
37826
|
+
abortSignal: signal
|
|
38013
37827
|
}),
|
|
38014
37828
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
38015
37829
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
@@ -38879,7 +38693,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38879
38693
|
}
|
|
38880
38694
|
return enabled;
|
|
38881
38695
|
};
|
|
38882
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection
|
|
38696
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38883
38697
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38884
38698
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38885
38699
|
const jsPlugins = [];
|
|
@@ -38891,7 +38705,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38891
38705
|
if (!rule) continue;
|
|
38892
38706
|
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38893
38707
|
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38894
|
-
if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
|
|
38895
38708
|
if (rule.scan !== void 0) continue;
|
|
38896
38709
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38897
38710
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38936,152 +38749,33 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38936
38749
|
}
|
|
38937
38750
|
};
|
|
38938
38751
|
};
|
|
38939
|
-
const
|
|
38940
|
-
const decodeFileDiagnostics = (raw) => {
|
|
38941
|
-
if (!Array.isArray(raw)) return null;
|
|
38752
|
+
const atomicWriteJson = (filePath, value) => {
|
|
38942
38753
|
try {
|
|
38943
|
-
|
|
38944
|
-
|
|
38754
|
+
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
38755
|
+
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
38756
|
+
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
38757
|
+
NFS.renameSync(temporaryPath, filePath);
|
|
38945
38758
|
} catch {
|
|
38946
|
-
return
|
|
38947
|
-
}
|
|
38948
|
-
};
|
|
38949
|
-
const emptyCache$1 = () => ({
|
|
38950
|
-
version: 1,
|
|
38951
|
-
rulesets: {}
|
|
38952
|
-
});
|
|
38953
|
-
const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
|
|
38954
|
-
const entries = /* @__PURE__ */ new Map();
|
|
38955
|
-
const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38956
|
-
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38957
|
-
const bucket = persisted.rulesets[rulesetHash];
|
|
38958
|
-
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
38959
|
-
for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
|
|
38960
|
-
const decoded = decodeFileDiagnostics(rawDiagnostics);
|
|
38961
|
-
if (decoded !== null) entries.set(fileKey, decoded);
|
|
38759
|
+
return;
|
|
38962
38760
|
}
|
|
38963
|
-
return entries;
|
|
38964
38761
|
};
|
|
38965
|
-
const
|
|
38966
|
-
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38967
|
-
const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
|
|
38968
|
-
return {
|
|
38969
|
-
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38970
|
-
store: (fileKey, diagnostics) => {
|
|
38971
|
-
entries.delete(fileKey);
|
|
38972
|
-
entries.set(fileKey, diagnostics);
|
|
38973
|
-
},
|
|
38974
|
-
persist: () => {
|
|
38975
|
-
const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38976
|
-
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38977
|
-
const existingBucket = rulesets[rulesetHash];
|
|
38978
|
-
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
38979
|
-
const ourFiles = {};
|
|
38980
|
-
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
38981
|
-
const cappedEntries = Object.entries({
|
|
38982
|
-
...existingFiles,
|
|
38983
|
-
...ourFiles
|
|
38984
|
-
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
38985
|
-
rulesets[rulesetHash] = {
|
|
38986
|
-
updatedAtMs: Date.now(),
|
|
38987
|
-
files: Object.fromEntries(cappedEntries)
|
|
38988
|
-
};
|
|
38989
|
-
atomicWriteJson(cacheFilePath, {
|
|
38990
|
-
version: 1,
|
|
38991
|
-
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
38992
|
-
});
|
|
38993
|
-
}
|
|
38994
|
-
};
|
|
38995
|
-
};
|
|
38996
|
-
const hashFileContents = (filePath) => {
|
|
38762
|
+
const failOpenReadJson = (filePath, fallback) => {
|
|
38997
38763
|
try {
|
|
38998
|
-
return
|
|
38764
|
+
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
38999
38765
|
} catch {
|
|
39000
|
-
return
|
|
38766
|
+
return fallback;
|
|
39001
38767
|
}
|
|
39002
38768
|
};
|
|
39003
|
-
const ABSENT_CONTENT_ANSWER = "absent";
|
|
39004
|
-
/**
|
|
39005
|
-
* Answers dependency probes for the sidecar lint cache — used both when
|
|
39006
|
-
* STORING an entry (turn the collector's probe paths into answers) and when
|
|
39007
|
-
* VERIFYING one (re-answer each stored probe against the current tree).
|
|
39008
|
-
* Store and verify must agree exactly, so both go through this one resolver.
|
|
39009
|
-
*
|
|
39010
|
-
* - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
|
|
39011
|
-
* (missing, a directory, permission failure). Source files reuse the
|
|
39012
|
-
* hashes the cache-partition loop already computed
|
|
39013
|
-
* (`contentHashByRelativePath`); everything else (package.json,
|
|
39014
|
-
* tsconfig, ignored files) is hashed on demand.
|
|
39015
|
-
* - `exists` — the existence class a module-resolution candidate probe
|
|
39016
|
-
* sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
|
|
39017
|
-
* `statSync` follows symlinks, matching the resolvers).
|
|
39018
|
-
*
|
|
39019
|
-
* Answers are memoized per scan: the filesystem is frozen for the run, and
|
|
39020
|
-
* hot dependency paths (a barrel imported by hundreds of files, the shared
|
|
39021
|
-
* tsconfig chain) repeat across many files' probe sets.
|
|
39022
|
-
*/
|
|
39023
|
-
const createSidecarProbeAnswerResolver = (input) => {
|
|
39024
|
-
const contentAnswerByPath = /* @__PURE__ */ new Map();
|
|
39025
|
-
const existsAnswerByPath = /* @__PURE__ */ new Map();
|
|
39026
|
-
const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
|
|
39027
|
-
const contentAnswer = (relativePath) => {
|
|
39028
|
-
const memoized = contentAnswerByPath.get(relativePath);
|
|
39029
|
-
if (memoized !== void 0) return memoized;
|
|
39030
|
-
const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
|
|
39031
|
-
contentAnswerByPath.set(relativePath, answer);
|
|
39032
|
-
return answer;
|
|
39033
|
-
};
|
|
39034
|
-
const existsAnswer = (relativePath) => {
|
|
39035
|
-
const memoized = existsAnswerByPath.get(relativePath);
|
|
39036
|
-
if (memoized !== void 0) return memoized;
|
|
39037
|
-
let answer;
|
|
39038
|
-
try {
|
|
39039
|
-
const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
|
|
39040
|
-
answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
|
|
39041
|
-
} catch {
|
|
39042
|
-
answer = "none";
|
|
39043
|
-
}
|
|
39044
|
-
existsAnswerByPath.set(relativePath, answer);
|
|
39045
|
-
return answer;
|
|
39046
|
-
};
|
|
39047
|
-
return {
|
|
39048
|
-
answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
|
|
39049
|
-
toRelativePath
|
|
39050
|
-
};
|
|
39051
|
-
};
|
|
39052
38769
|
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
39053
|
-
const
|
|
39054
|
-
|
|
39055
|
-
if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
|
|
39056
|
-
const probes = [];
|
|
39057
|
-
for (const rawProbe of raw.probes) {
|
|
39058
|
-
if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
|
|
39059
|
-
const [kind, probePath, answer] = rawProbe;
|
|
39060
|
-
if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
|
|
39061
|
-
probes.push({
|
|
39062
|
-
kind,
|
|
39063
|
-
path: probePath,
|
|
39064
|
-
answer
|
|
39065
|
-
});
|
|
39066
|
-
}
|
|
38770
|
+
const decodeFileDiagnostics = (raw) => {
|
|
38771
|
+
if (!Array.isArray(raw)) return null;
|
|
39067
38772
|
try {
|
|
39068
|
-
for (const entry of raw
|
|
38773
|
+
for (const entry of raw) validateDiagnostic(entry);
|
|
38774
|
+
return raw;
|
|
39069
38775
|
} catch {
|
|
39070
38776
|
return null;
|
|
39071
38777
|
}
|
|
39072
|
-
return {
|
|
39073
|
-
probes,
|
|
39074
|
-
diagnostics: raw.diagnostics
|
|
39075
|
-
};
|
|
39076
38778
|
};
|
|
39077
|
-
const encodeSidecarFileEntry = (entry) => ({
|
|
39078
|
-
probes: entry.probes.map((probe) => [
|
|
39079
|
-
probe.kind,
|
|
39080
|
-
probe.path,
|
|
39081
|
-
probe.answer
|
|
39082
|
-
]),
|
|
39083
|
-
diagnostics: entry.diagnostics
|
|
39084
|
-
});
|
|
39085
38779
|
const emptyCache = () => ({
|
|
39086
38780
|
version: 1,
|
|
39087
38781
|
rulesets: {}
|
|
@@ -39092,20 +38786,20 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
|
39092
38786
|
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
39093
38787
|
const bucket = persisted.rulesets[rulesetHash];
|
|
39094
38788
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39095
|
-
for (const [fileKey,
|
|
39096
|
-
const decoded =
|
|
38789
|
+
for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
|
|
38790
|
+
const decoded = decodeFileDiagnostics(rawDiagnostics);
|
|
39097
38791
|
if (decoded !== null) entries.set(fileKey, decoded);
|
|
39098
38792
|
}
|
|
39099
38793
|
return entries;
|
|
39100
38794
|
};
|
|
39101
|
-
const
|
|
39102
|
-
const cacheFilePath = Path.join(cacheDirectory,
|
|
38795
|
+
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38796
|
+
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
39103
38797
|
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
39104
38798
|
return {
|
|
39105
38799
|
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
39106
|
-
store: (fileKey,
|
|
38800
|
+
store: (fileKey, diagnostics) => {
|
|
39107
38801
|
entries.delete(fileKey);
|
|
39108
|
-
entries.set(fileKey,
|
|
38802
|
+
entries.set(fileKey, diagnostics);
|
|
39109
38803
|
},
|
|
39110
38804
|
persist: () => {
|
|
39111
38805
|
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
@@ -39113,7 +38807,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
39113
38807
|
const existingBucket = rulesets[rulesetHash];
|
|
39114
38808
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39115
38809
|
const ourFiles = {};
|
|
39116
|
-
for (const [fileKey,
|
|
38810
|
+
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
39117
38811
|
const cappedEntries = Object.entries({
|
|
39118
38812
|
...existingFiles,
|
|
39119
38813
|
...ourFiles
|
|
@@ -40128,6 +39822,13 @@ const validateRuleRegistration = () => {
|
|
|
40128
39822
|
].filter((entry) => entry !== null).join("; ");
|
|
40129
39823
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
40130
39824
|
};
|
|
39825
|
+
const hashFileContents = (filePath) => {
|
|
39826
|
+
try {
|
|
39827
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39828
|
+
} catch {
|
|
39829
|
+
return null;
|
|
39830
|
+
}
|
|
39831
|
+
};
|
|
40131
39832
|
/**
|
|
40132
39833
|
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
40133
39834
|
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
@@ -40192,6 +39893,21 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
|
40192
39893
|
}
|
|
40193
39894
|
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
40194
39895
|
};
|
|
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
|
+
};
|
|
40195
39911
|
/**
|
|
40196
39912
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
40197
39913
|
* the runner: once for the primary scan, once for the
|
|
@@ -40209,106 +39925,6 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
40209
39925
|
NFS.closeSync(fileHandle);
|
|
40210
39926
|
}
|
|
40211
39927
|
};
|
|
40212
|
-
/**
|
|
40213
|
-
* Attributes diagnostics back to the file that produced them by the
|
|
40214
|
-
* normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
|
|
40215
|
-
* attributed — the path forms don't align, so the caller must skip its cache
|
|
40216
|
-
* store rather than risk caching a wrong empty result for a file that
|
|
40217
|
-
* actually had diagnostics.
|
|
40218
|
-
*/
|
|
40219
|
-
const attributeDiagnosticsToFiles = (diagnostics, files) => {
|
|
40220
|
-
const fileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40221
|
-
for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
|
|
40222
|
-
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40223
|
-
for (const diagnostic of diagnostics) {
|
|
40224
|
-
const file = fileByNormalizedPath.get(diagnostic.filePath);
|
|
40225
|
-
if (file === void 0) return null;
|
|
40226
|
-
const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
|
|
40227
|
-
fileDiagnostics.push(diagnostic);
|
|
40228
|
-
diagnosticsByFile.set(file, fileDiagnostics);
|
|
40229
|
-
}
|
|
40230
|
-
return diagnosticsByFile;
|
|
40231
|
-
};
|
|
40232
|
-
/**
|
|
40233
|
-
* Collects each file's cross-file dependency probes (via the plugin's
|
|
40234
|
-
* collectors) with their current answers. Fail-open per file: an unreadable /
|
|
40235
|
-
* unparseable / collector-failing file maps to `null` (no entry stored — it
|
|
40236
|
-
* re-lints next scan). The filesystem is frozen for the scan, so this can run
|
|
40237
|
-
* CONCURRENTLY with an oxlint child-process await — the caller starts it
|
|
40238
|
-
* unawaited before a lint pass and awaits it at store time, hiding the
|
|
40239
|
-
* in-process parse + resolution cost under the subprocess wait. The loop
|
|
40240
|
-
* yields on the cooperative time budget so the child's stdout handling (and
|
|
40241
|
-
* sibling fibers) keep flowing; each per-file collection is synchronous, so
|
|
40242
|
-
* the module-level probe recorder never sees interleaved use. Never rejects.
|
|
40243
|
-
*/
|
|
40244
|
-
const collectSidecarProbesForFiles = async (input) => {
|
|
40245
|
-
const buildProbes = (file) => {
|
|
40246
|
-
const absoluteFilePath = Path.resolve(input.rootDirectory, file);
|
|
40247
|
-
let trace;
|
|
40248
|
-
try {
|
|
40249
|
-
trace = collectCrossFileDependencyProbes({
|
|
40250
|
-
absoluteFilePath,
|
|
40251
|
-
sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
|
|
40252
|
-
ruleIds: input.boundedSidecarRuleIds
|
|
40253
|
-
});
|
|
40254
|
-
} catch {
|
|
40255
|
-
return null;
|
|
40256
|
-
}
|
|
40257
|
-
if (trace === null) return null;
|
|
40258
|
-
const probes = [];
|
|
40259
|
-
for (const contentPath of trace.contentPaths) {
|
|
40260
|
-
const relativePath = input.probeAnswers.toRelativePath(contentPath);
|
|
40261
|
-
probes.push({
|
|
40262
|
-
kind: "content",
|
|
40263
|
-
path: relativePath,
|
|
40264
|
-
answer: input.probeAnswers.answerFor("content", relativePath)
|
|
40265
|
-
});
|
|
40266
|
-
}
|
|
40267
|
-
for (const existencePath of trace.existencePaths) {
|
|
40268
|
-
const relativePath = input.probeAnswers.toRelativePath(existencePath);
|
|
40269
|
-
probes.push({
|
|
40270
|
-
kind: "exists",
|
|
40271
|
-
path: relativePath,
|
|
40272
|
-
answer: input.probeAnswers.answerFor("exists", relativePath)
|
|
40273
|
-
});
|
|
40274
|
-
}
|
|
40275
|
-
return probes;
|
|
40276
|
-
};
|
|
40277
|
-
const probesByFile = /* @__PURE__ */ new Map();
|
|
40278
|
-
let collectSliceStartedAt = performance.now();
|
|
40279
|
-
for (const file of input.files) {
|
|
40280
|
-
probesByFile.set(file, buildProbes(file));
|
|
40281
|
-
if (performance.now() - collectSliceStartedAt >= 12) {
|
|
40282
|
-
await yieldToEventLoop();
|
|
40283
|
-
collectSliceStartedAt = performance.now();
|
|
40284
|
-
}
|
|
40285
|
-
}
|
|
40286
|
-
return probesByFile;
|
|
40287
|
-
};
|
|
40288
|
-
/**
|
|
40289
|
-
* Stores each freshly-linted file's probe set + cross-file diagnostics so
|
|
40290
|
-
* the next scan can replay instead of re-linting.
|
|
40291
|
-
*/
|
|
40292
|
-
const storeSidecarEntries = (input) => {
|
|
40293
|
-
let didStoreAnyEntry = false;
|
|
40294
|
-
for (const pass of input.storablePasses) {
|
|
40295
|
-
if (!pass.isTrusted) continue;
|
|
40296
|
-
const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
|
|
40297
|
-
if (diagnosticsByFile === null) continue;
|
|
40298
|
-
for (const file of pass.files) {
|
|
40299
|
-
const cacheKey = input.cacheKeyByFile.get(file);
|
|
40300
|
-
if (cacheKey === void 0) continue;
|
|
40301
|
-
const probes = pass.probesByFile.get(file);
|
|
40302
|
-
if (probes === void 0 || probes === null) continue;
|
|
40303
|
-
input.sidecarCache.store(cacheKey, {
|
|
40304
|
-
probes,
|
|
40305
|
-
diagnostics: diagnosticsByFile.get(file) ?? []
|
|
40306
|
-
});
|
|
40307
|
-
didStoreAnyEntry = true;
|
|
40308
|
-
}
|
|
40309
|
-
}
|
|
40310
|
-
if (didStoreAnyEntry) input.sidecarCache.persist();
|
|
40311
|
-
};
|
|
40312
39928
|
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
40313
39929
|
/**
|
|
40314
39930
|
* Detects an oxlint config-load crash caused by the optional
|
|
@@ -40350,7 +39966,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
40350
39966
|
* 6. always restore disable directives + clean up the temp dir
|
|
40351
39967
|
*/
|
|
40352
39968
|
const runOxlint = async (options) => {
|
|
40353
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false,
|
|
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;
|
|
40354
39970
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40355
39971
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40356
39972
|
validateRuleRegistration();
|
|
@@ -40368,8 +39984,7 @@ const runOxlint = async (options) => {
|
|
|
40368
39984
|
severityControls,
|
|
40369
39985
|
userPlugins,
|
|
40370
39986
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
40371
|
-
ruleSelection: overrides.ruleSelection
|
|
40372
|
-
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
39987
|
+
ruleSelection: overrides.ruleSelection
|
|
40373
39988
|
});
|
|
40374
39989
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
40375
39990
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -40472,18 +40087,15 @@ const runOxlint = async (options) => {
|
|
|
40472
40087
|
});
|
|
40473
40088
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
40474
40089
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40475
|
-
const contentHashByRelativePath = /* @__PURE__ */ new Map();
|
|
40476
40090
|
const missFiles = [];
|
|
40477
40091
|
const replayedDiagnostics = [];
|
|
40478
40092
|
let hashSliceStartedAt = performance.now();
|
|
40479
40093
|
for (const candidateFile of candidateFiles) {
|
|
40480
|
-
const
|
|
40481
|
-
const contentHash = hashFileContents(absoluteCandidatePath);
|
|
40094
|
+
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
40482
40095
|
if (contentHash === null) {
|
|
40483
40096
|
missFiles.push(candidateFile);
|
|
40484
40097
|
continue;
|
|
40485
40098
|
}
|
|
40486
|
-
contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
|
|
40487
40099
|
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
40488
40100
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
40489
40101
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
@@ -40495,111 +40107,43 @@ const runOxlint = async (options) => {
|
|
|
40495
40107
|
}
|
|
40496
40108
|
}
|
|
40497
40109
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40498
|
-
const enabledSidecarRuleIds = Object.keys(buildConfig({
|
|
40499
|
-
extendsPaths: [],
|
|
40500
|
-
ruleSelection: "sidecar"
|
|
40501
|
-
}).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
|
|
40502
|
-
const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40503
|
-
const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
|
|
40504
|
-
const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40505
|
-
const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
|
|
40506
|
-
const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
|
|
40507
|
-
config: buildConfig({
|
|
40508
|
-
extendsPaths: [],
|
|
40509
|
-
ruleSelection: "sidecar",
|
|
40510
|
-
sidecarRuleIdFilter: boundedSidecarRuleIdSet
|
|
40511
|
-
}),
|
|
40512
|
-
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
40513
|
-
ignorePatterns: combinedPatterns,
|
|
40514
|
-
tsconfigContent
|
|
40515
|
-
})) : null;
|
|
40516
|
-
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
40517
|
-
rootDirectory,
|
|
40518
|
-
contentHashByRelativePath
|
|
40519
|
-
});
|
|
40520
|
-
const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40521
|
-
files: missFiles,
|
|
40522
|
-
rootDirectory,
|
|
40523
|
-
boundedSidecarRuleIds,
|
|
40524
|
-
probeAnswers
|
|
40525
|
-
});
|
|
40526
40110
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40527
40111
|
extendsPaths: [],
|
|
40528
40112
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40529
40113
|
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40530
40114
|
const missFileSet = new Set(missFiles);
|
|
40531
|
-
const
|
|
40532
|
-
const sidecarReplayedDiagnostics = [];
|
|
40533
|
-
const sidecarLintFiles = [];
|
|
40534
|
-
if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
|
|
40535
|
-
else {
|
|
40536
|
-
let verifySliceStartedAt = performance.now();
|
|
40537
|
-
for (const hitFile of hitFiles) {
|
|
40538
|
-
const cacheKey = cacheKeyByFile.get(hitFile);
|
|
40539
|
-
const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
|
|
40540
|
-
if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
|
|
40541
|
-
else sidecarLintFiles.push(hitFile);
|
|
40542
|
-
if (performance.now() - verifySliceStartedAt >= 12) {
|
|
40543
|
-
await yieldToEventLoop();
|
|
40544
|
-
verifySliceStartedAt = performance.now();
|
|
40545
|
-
}
|
|
40546
|
-
}
|
|
40547
|
-
}
|
|
40548
|
-
const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40549
|
-
files: sidecarLintFiles,
|
|
40550
|
-
rootDirectory,
|
|
40551
|
-
boundedSidecarRuleIds,
|
|
40552
|
-
probeAnswers
|
|
40553
|
-
});
|
|
40554
|
-
const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
|
|
40555
|
-
if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
|
|
40556
|
-
const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40115
|
+
const sidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40557
40116
|
extendsPaths: [],
|
|
40558
|
-
ruleSelection: "sidecar"
|
|
40559
|
-
|
|
40560
|
-
}), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
|
|
40561
|
-
const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
|
|
40562
|
-
extendsPaths: [],
|
|
40563
|
-
ruleSelection: "sidecar",
|
|
40564
|
-
sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
|
|
40565
|
-
}), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
|
|
40566
|
-
diagnostics: [],
|
|
40567
|
-
didDropReactHooksJsPlugin: false,
|
|
40568
|
-
hadPartialFailure: false
|
|
40569
|
-
};
|
|
40117
|
+
ruleSelection: "sidecar"
|
|
40118
|
+
}), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
|
|
40570
40119
|
const freshCacheableDiagnostics = [];
|
|
40571
40120
|
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40572
40121
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40573
|
-
|
|
40574
|
-
const
|
|
40575
|
-
|
|
40122
|
+
const missFileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40123
|
+
for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
|
|
40124
|
+
const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
|
|
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) {
|
|
40576
40137
|
for (const missFile of missFiles) {
|
|
40577
40138
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40578
40139
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
40579
40140
|
}
|
|
40580
40141
|
cache.persist();
|
|
40581
40142
|
}
|
|
40582
|
-
if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
|
|
40583
|
-
sidecarCache,
|
|
40584
|
-
cacheKeyByFile,
|
|
40585
|
-
storablePasses: [{
|
|
40586
|
-
files: sidecarLintFiles,
|
|
40587
|
-
diagnostics: boundedSidecarResult.diagnostics,
|
|
40588
|
-
isTrusted: !boundedSidecarResult.hadPartialFailure,
|
|
40589
|
-
probesByFile: await sidecarProbesTask
|
|
40590
|
-
}, {
|
|
40591
|
-
files: missFiles,
|
|
40592
|
-
diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
|
|
40593
|
-
isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
|
|
40594
|
-
probesByFile: await missProbesTask
|
|
40595
|
-
}]
|
|
40596
|
-
});
|
|
40597
40143
|
return dedupeDiagnostics([
|
|
40598
40144
|
...replayedDiagnostics,
|
|
40599
40145
|
...fullResult.diagnostics,
|
|
40600
|
-
...
|
|
40601
|
-
...boundedSidecarResult.diagnostics,
|
|
40602
|
-
...unboundedSidecarResult.diagnostics
|
|
40146
|
+
...sidecarResult.diagnostics
|
|
40603
40147
|
]);
|
|
40604
40148
|
}
|
|
40605
40149
|
const baseArgs = makeBaseArgs(configPath);
|
|
@@ -40697,7 +40241,6 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40697
40241
|
const concurrency = yield* OxlintConcurrency;
|
|
40698
40242
|
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40699
40243
|
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
40700
|
-
const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
|
|
40701
40244
|
const collectedFailures = [];
|
|
40702
40245
|
const diagnostics = yield* tryPromise({
|
|
40703
40246
|
try: (signal) => runOxlint({
|
|
@@ -40716,9 +40259,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40716
40259
|
},
|
|
40717
40260
|
onFileProgress: input.onFileProgress,
|
|
40718
40261
|
perFileLintCacheEnabled,
|
|
40719
|
-
sidecarLintCacheEnabled,
|
|
40720
40262
|
onCacheStats: input.onCacheStats,
|
|
40721
|
-
onSidecarStats: input.onSidecarStats,
|
|
40722
40263
|
spawnTimeoutMs,
|
|
40723
40264
|
outputMaxBytes,
|
|
40724
40265
|
concurrency,
|
|
@@ -41493,14 +41034,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41493
41034
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
41494
41035
|
rootDirectory: scanDirectory,
|
|
41495
41036
|
parseConcurrency: deadCodeParseConcurrency,
|
|
41496
|
-
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
41497
|
-
onCacheOutcome: (didHitCache) => {
|
|
41498
|
-
deadCodeCacheHit = didHitCache;
|
|
41499
|
-
},
|
|
41500
|
-
onSummaryCacheStats: (stats) => {
|
|
41501
|
-
deadCodeSummaryCacheHits = stats.hits;
|
|
41502
|
-
deadCodeSummaryCacheMisses = stats.misses;
|
|
41503
|
-
}
|
|
41037
|
+
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
41504
41038
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41505
41039
|
yield* set(deadCodeFailure, {
|
|
41506
41040
|
didFail: true,
|
|
@@ -41528,11 +41062,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41528
41062
|
let lastReportedTotalFileCount = 0;
|
|
41529
41063
|
let lintCacheHitFileCount = null;
|
|
41530
41064
|
let lintCacheTotalFileCount = null;
|
|
41531
|
-
let lintSidecarReplayedFileCount = null;
|
|
41532
|
-
let lintSidecarTotalFileCount = null;
|
|
41533
|
-
let deadCodeCacheHit = null;
|
|
41534
|
-
let deadCodeSummaryCacheHits = null;
|
|
41535
|
-
let deadCodeSummaryCacheMisses = null;
|
|
41536
41065
|
const baseLintStream = linterService.run({
|
|
41537
41066
|
rootDirectory: scanDirectory,
|
|
41538
41067
|
project,
|
|
@@ -41552,10 +41081,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41552
41081
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
41553
41082
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
41554
41083
|
},
|
|
41555
|
-
onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
|
|
41556
|
-
lintSidecarReplayedFileCount = sidecarReplayedFileCount;
|
|
41557
|
-
lintSidecarTotalFileCount = sidecarConsideredFileCount;
|
|
41558
|
-
},
|
|
41559
41084
|
deadlineEpochMs: input.deadlineEpochMs
|
|
41560
41085
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41561
41086
|
yield* set(lintFailure, {
|
|
@@ -41666,11 +41191,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41666
41191
|
securityScanFailed,
|
|
41667
41192
|
lintCacheHitFileCount,
|
|
41668
41193
|
lintCacheTotalFileCount,
|
|
41669
|
-
lintSidecarReplayedFileCount,
|
|
41670
|
-
lintSidecarTotalFileCount,
|
|
41671
|
-
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41672
|
-
deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
|
|
41673
|
-
deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
|
|
41674
41194
|
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
41675
41195
|
};
|
|
41676
41196
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
@@ -44050,5 +43570,5 @@ const startLanguageServer = () => {
|
|
|
44050
43570
|
};
|
|
44051
43571
|
//#endregion
|
|
44052
43572
|
export { startLanguageServer };
|
|
44053
|
-
!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]="
|
|
44054
|
-
//# debugId=
|
|
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]="94a57ec3-d5a9-52ea-a743-e91d996065b8")}catch(e){}}();
|
|
43574
|
+
//# debugId=94a57ec3-d5a9-52ea-a743-e91d996065b8
|