react-doctor 0.6.2 → 0.6.3-dev.20d81f6
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 +346 -71
- package/dist/index.d.ts +8 -0
- package/dist/index.js +247 -57
- package/dist/lsp.js +248 -58
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -9743,6 +9743,14 @@ interface InspectResult {
|
|
|
9743
9743
|
*/
|
|
9744
9744
|
lintCacheHitFileCount?: number | null;
|
|
9745
9745
|
lintCacheTotalFileCount?: number | null;
|
|
9746
|
+
/**
|
|
9747
|
+
* Dead-code result cache outcome: `true` when the pass replayed a cached
|
|
9748
|
+
* result (the analysis never ran), `false` on a fresh analysis. Absent when
|
|
9749
|
+
* the pass never consulted the cache — dead-code skipped, the cache
|
|
9750
|
+
* disabled, or a whole-repo cache replay where no analysis ran. The CLI
|
|
9751
|
+
* projects it onto the Sentry wide event as `deadCode.cacheHit`.
|
|
9752
|
+
*/
|
|
9753
|
+
deadCodeCacheHit?: boolean | null;
|
|
9746
9754
|
/**
|
|
9747
9755
|
* Present only for a baseline run (`InspectOptions.baseline` set). The
|
|
9748
9756
|
* `diagnostics` above are then the *introduced* findings only; this
|
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]="e96374bd-b4c9-5452-8570-b81c11a2959a")}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";
|
|
@@ -18,6 +18,7 @@ import { fileURLToPath } from "node:url";
|
|
|
18
18
|
import { createJiti } from "jiti";
|
|
19
19
|
import * as Crypto from "node:crypto";
|
|
20
20
|
import crypto, { createHash } from "node:crypto";
|
|
21
|
+
import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
|
|
21
22
|
import { gzipSync } from "node:zlib";
|
|
22
23
|
//#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
|
|
23
24
|
/**
|
|
@@ -32927,6 +32928,8 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32927
32928
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32928
32929
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32929
32930
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32931
|
+
const CORE_PACKAGE_VERSION = "0.6.3";
|
|
32932
|
+
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32930
32933
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
32931
32934
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32932
32935
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
@@ -35479,7 +35482,7 @@ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
|
35479
35482
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
35480
35483
|
};
|
|
35481
35484
|
const resolveLintBatchOrdering = () => {
|
|
35482
|
-
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "
|
|
35485
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
|
|
35483
35486
|
};
|
|
35484
35487
|
/**
|
|
35485
35488
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -35576,17 +35579,19 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
|
|
|
35576
35579
|
return "auto";
|
|
35577
35580
|
} }) {};
|
|
35578
35581
|
/**
|
|
35579
|
-
* How the full-scan lint pass
|
|
35580
|
-
*
|
|
35581
|
-
*
|
|
35582
|
-
*
|
|
35583
|
-
* the
|
|
35584
|
-
*
|
|
35585
|
-
*
|
|
35586
|
-
*
|
|
35587
|
-
* `
|
|
35588
|
-
*
|
|
35589
|
-
*
|
|
35582
|
+
* How the full-scan lint pass plans its file batches. `"cost"` (the default)
|
|
35583
|
+
* builds size-balanced LPT batches (`planLintBatches`): the same mandatory
|
|
35584
|
+
* batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
|
|
35585
|
+
* an even share of files AND bytes, so no 100-file chunk is a straggler while
|
|
35586
|
+
* the remainder-batch worker idles — and the heavy files are SPREAD across
|
|
35587
|
+
* batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
|
|
35588
|
+
* lacked (it packed the heaviest files into one wave-1 straggler batch,
|
|
35589
|
+
* measurably regressing size-skewed repos, which is why it never earned the
|
|
35590
|
+
* default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
|
|
35591
|
+
* rollback hatch to plain greedy 100-file chunking in discovery order. Tests
|
|
35592
|
+
* override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
|
|
35593
|
+
* never reach this — they pass user-scoped `includePaths` that skip discovery
|
|
35594
|
+
* and stay in arrival order; only the full-scan branch reads it.
|
|
35590
35595
|
*/
|
|
35591
35596
|
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
35592
35597
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
@@ -35611,6 +35616,25 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35611
35616
|
if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
|
|
35612
35617
|
return true;
|
|
35613
35618
|
} }) {};
|
|
35619
|
+
/**
|
|
35620
|
+
* Whether the whole-project dead-code result cache
|
|
35621
|
+
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35622
|
+
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
35623
|
+
* unchanged replays the stored diagnostics instead of re-running the
|
|
35624
|
+
* analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
|
|
35625
|
+
*
|
|
35626
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35627
|
+
* - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
|
|
35628
|
+
*
|
|
35629
|
+
* Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
|
|
35630
|
+
*/
|
|
35631
|
+
var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
|
|
35632
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35633
|
+
const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
|
|
35634
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35635
|
+
if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
|
|
35636
|
+
return true;
|
|
35637
|
+
} }) {};
|
|
35614
35638
|
const DIAGNOSTIC_SURFACES = [
|
|
35615
35639
|
"cli",
|
|
35616
35640
|
"prComment",
|
|
@@ -36943,6 +36967,7 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
36943
36967
|
isGeneratedBundleByName: classification.isGeneratedBundleByName
|
|
36944
36968
|
});
|
|
36945
36969
|
}
|
|
36970
|
+
yield null;
|
|
36946
36971
|
}
|
|
36947
36972
|
for (const candidates of [
|
|
36948
36973
|
priorityCandidates,
|
|
@@ -37068,9 +37093,18 @@ const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
|
37068
37093
|
const session = createSecurityScanSession(rootDirectory, options);
|
|
37069
37094
|
if (session === null) return [];
|
|
37070
37095
|
let sliceStartedAt = performance.now();
|
|
37071
|
-
for (const file of collectSecurityScanFiles(rootDirectory))
|
|
37072
|
-
|
|
37073
|
-
|
|
37096
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
37097
|
+
if (file === null) {
|
|
37098
|
+
if (performance.now() - sliceStartedAt >= 12) {
|
|
37099
|
+
await yieldToEventLoop();
|
|
37100
|
+
sliceStartedAt = performance.now();
|
|
37101
|
+
}
|
|
37102
|
+
continue;
|
|
37103
|
+
}
|
|
37104
|
+
for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
|
|
37105
|
+
await yieldToEventLoop();
|
|
37106
|
+
sliceStartedAt = performance.now();
|
|
37107
|
+
}
|
|
37074
37108
|
}
|
|
37075
37109
|
return session.diagnostics;
|
|
37076
37110
|
};
|
|
@@ -37289,6 +37323,80 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37289
37323
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37290
37324
|
};
|
|
37291
37325
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37326
|
+
const atomicWriteJson = (filePath, value) => {
|
|
37327
|
+
try {
|
|
37328
|
+
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
37329
|
+
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
37330
|
+
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
37331
|
+
NFS.renameSync(temporaryPath, filePath);
|
|
37332
|
+
} catch {
|
|
37333
|
+
return;
|
|
37334
|
+
}
|
|
37335
|
+
};
|
|
37336
|
+
const failOpenReadJson = (filePath, fallback) => {
|
|
37337
|
+
try {
|
|
37338
|
+
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
37339
|
+
} catch {
|
|
37340
|
+
return fallback;
|
|
37341
|
+
}
|
|
37342
|
+
};
|
|
37343
|
+
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
37344
|
+
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
37345
|
+
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
37346
|
+
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
37347
|
+
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
37348
|
+
const collectAnalyzedFileFingerprints = (rootDirectory) => {
|
|
37349
|
+
const fingerprints = [];
|
|
37350
|
+
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
37351
|
+
if (!isFingerprintedFile(name)) continue;
|
|
37352
|
+
try {
|
|
37353
|
+
const fileStat = NFS.statSync(absolutePath);
|
|
37354
|
+
const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
|
|
37355
|
+
fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
|
|
37356
|
+
} catch {}
|
|
37357
|
+
}
|
|
37358
|
+
return fingerprints.sort();
|
|
37359
|
+
};
|
|
37360
|
+
const bundledRequire$2 = createRequire(import.meta.url);
|
|
37361
|
+
const resolveDeslopVersion = () => {
|
|
37362
|
+
try {
|
|
37363
|
+
const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
|
|
37364
|
+
return isRecord(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
37365
|
+
} catch {
|
|
37366
|
+
return "unknown";
|
|
37367
|
+
}
|
|
37368
|
+
};
|
|
37369
|
+
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
37370
|
+
schemaVersion: 1,
|
|
37371
|
+
coreVersion: input.coreVersion,
|
|
37372
|
+
deslopVersion: resolveDeslopVersion(),
|
|
37373
|
+
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
37374
|
+
entryPatterns: input.entryPatterns,
|
|
37375
|
+
ignorePatterns: input.ignorePatterns,
|
|
37376
|
+
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
|
|
37377
|
+
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
37378
|
+
})).digest("hex");
|
|
37379
|
+
const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
|
|
37380
|
+
const decodeCachedDiagnostics = (raw) => {
|
|
37381
|
+
try {
|
|
37382
|
+
for (const entry of raw) validateDiagnostic$1(entry);
|
|
37383
|
+
return raw;
|
|
37384
|
+
} catch {
|
|
37385
|
+
return null;
|
|
37386
|
+
}
|
|
37387
|
+
};
|
|
37388
|
+
const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
|
|
37389
|
+
const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
|
|
37390
|
+
if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
|
|
37391
|
+
return decodeCachedDiagnostics(persisted.diagnostics);
|
|
37392
|
+
};
|
|
37393
|
+
const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
|
|
37394
|
+
atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
|
|
37395
|
+
version: 1,
|
|
37396
|
+
key: cacheKey,
|
|
37397
|
+
diagnostics
|
|
37398
|
+
});
|
|
37399
|
+
};
|
|
37292
37400
|
const readSystemFacts = () => ({
|
|
37293
37401
|
availableCores: os.availableParallelism(),
|
|
37294
37402
|
totalMemoryBytes: os.totalmem(),
|
|
@@ -37357,6 +37465,21 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
|
37357
37465
|
releaseSlot();
|
|
37358
37466
|
}
|
|
37359
37467
|
};
|
|
37468
|
+
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
37469
|
+
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
37470
|
+
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
37471
|
+
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
37472
|
+
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
37473
|
+
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
37474
|
+
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
37475
|
+
};
|
|
37476
|
+
const resolveUserCacheScope = () => {
|
|
37477
|
+
try {
|
|
37478
|
+
return String(process.getuid?.() ?? os.userInfo().username);
|
|
37479
|
+
} catch {
|
|
37480
|
+
return "shared";
|
|
37481
|
+
}
|
|
37482
|
+
};
|
|
37360
37483
|
/**
|
|
37361
37484
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37362
37485
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37652,13 +37775,28 @@ const checkDeadCode = async (options) => {
|
|
|
37652
37775
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37653
37776
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37654
37777
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37778
|
+
const tsConfigPath = resolveTsConfigPath(rootDirectory);
|
|
37779
|
+
const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
|
|
37780
|
+
const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
|
|
37781
|
+
rootDirectory,
|
|
37782
|
+
entryPatterns,
|
|
37783
|
+
ignorePatterns,
|
|
37784
|
+
tsConfigPath,
|
|
37785
|
+
deslopJsModuleSpecifier,
|
|
37786
|
+
coreVersion: CORE_PACKAGE_VERSION
|
|
37787
|
+
}) : null;
|
|
37788
|
+
if (cacheKey !== null) {
|
|
37789
|
+
const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
|
|
37790
|
+
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
37791
|
+
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
37792
|
+
}
|
|
37655
37793
|
const spawnAndRun = () => {
|
|
37656
37794
|
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37657
37795
|
rootDirectory,
|
|
37658
37796
|
entryPatterns,
|
|
37659
|
-
tsConfigPath
|
|
37797
|
+
tsConfigPath,
|
|
37660
37798
|
ignorePatterns,
|
|
37661
|
-
deslopJsModuleSpecifier
|
|
37799
|
+
deslopJsModuleSpecifier,
|
|
37662
37800
|
parseConcurrency: options.parseConcurrency
|
|
37663
37801
|
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37664
37802
|
};
|
|
@@ -37719,6 +37857,7 @@ const checkDeadCode = async (options) => {
|
|
|
37719
37857
|
category: DEAD_CODE_CATEGORY
|
|
37720
37858
|
});
|
|
37721
37859
|
}
|
|
37860
|
+
if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
|
|
37722
37861
|
return diagnostics;
|
|
37723
37862
|
};
|
|
37724
37863
|
const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
|
|
@@ -37821,12 +37960,15 @@ var Config = class Config extends Service()("react-doctor/Config") {
|
|
|
37821
37960
|
*/
|
|
37822
37961
|
var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
37823
37962
|
static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
|
|
37963
|
+
const cacheEnabled = yield* DeadCodeResultCacheEnabled;
|
|
37824
37964
|
return yield* tryPromise({
|
|
37825
37965
|
try: (signal) => checkDeadCode({
|
|
37826
37966
|
rootDirectory: input.rootDirectory,
|
|
37827
37967
|
parseConcurrency: input.parseConcurrency,
|
|
37828
37968
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37829
|
-
abortSignal: signal
|
|
37969
|
+
abortSignal: signal,
|
|
37970
|
+
cacheEnabled,
|
|
37971
|
+
onCacheOutcome: input.onCacheOutcome
|
|
37830
37972
|
}),
|
|
37831
37973
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
37832
37974
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
@@ -38752,23 +38894,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38752
38894
|
}
|
|
38753
38895
|
};
|
|
38754
38896
|
};
|
|
38755
|
-
const atomicWriteJson = (filePath, value) => {
|
|
38756
|
-
try {
|
|
38757
|
-
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
38758
|
-
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
38759
|
-
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
38760
|
-
NFS.renameSync(temporaryPath, filePath);
|
|
38761
|
-
} catch {
|
|
38762
|
-
return;
|
|
38763
|
-
}
|
|
38764
|
-
};
|
|
38765
|
-
const failOpenReadJson = (filePath, fallback) => {
|
|
38766
|
-
try {
|
|
38767
|
-
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
38768
|
-
} catch {
|
|
38769
|
-
return fallback;
|
|
38770
|
-
}
|
|
38771
|
-
};
|
|
38772
38897
|
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
38773
38898
|
const decodeFileDiagnostics = (raw) => {
|
|
38774
38899
|
if (!Array.isArray(raw)) return null;
|
|
@@ -39832,22 +39957,70 @@ const hashFileContents = (filePath) => {
|
|
|
39832
39957
|
return null;
|
|
39833
39958
|
}
|
|
39834
39959
|
};
|
|
39835
|
-
|
|
39836
|
-
|
|
39837
|
-
|
|
39838
|
-
|
|
39839
|
-
|
|
39840
|
-
|
|
39841
|
-
|
|
39842
|
-
|
|
39843
|
-
|
|
39844
|
-
|
|
39845
|
-
|
|
39846
|
-
|
|
39847
|
-
|
|
39960
|
+
/**
|
|
39961
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39962
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
39963
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
39964
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
39965
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
39966
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
39967
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
39968
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
39969
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
39970
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
39971
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
39972
|
+
*
|
|
39973
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
39974
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
39975
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
39976
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
39977
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
39978
|
+
*
|
|
39979
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
39980
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
39981
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
39982
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
39983
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
39984
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
39985
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
39986
|
+
* plan only moves wall-clock time.
|
|
39987
|
+
*
|
|
39988
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
39989
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
39990
|
+
*/
|
|
39991
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
39992
|
+
if (files.length === 0) return [];
|
|
39993
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
39994
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
39995
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
39996
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
39997
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
39998
|
+
fileIndexes: [],
|
|
39999
|
+
totalSizeBytes: 0,
|
|
40000
|
+
argsLengthChars: baseArgsLengthChars
|
|
40001
|
+
}));
|
|
40002
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
40003
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
40004
|
+
let targetBatch = null;
|
|
40005
|
+
for (const batch of batches) {
|
|
40006
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
40007
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
40008
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
40009
|
+
}
|
|
40010
|
+
if (targetBatch === null) {
|
|
40011
|
+
targetBatch = {
|
|
40012
|
+
fileIndexes: [],
|
|
40013
|
+
totalSizeBytes: 0,
|
|
40014
|
+
argsLengthChars: baseArgsLengthChars
|
|
40015
|
+
};
|
|
40016
|
+
batches.push(targetBatch);
|
|
40017
|
+
}
|
|
40018
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
40019
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
40020
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
39848
40021
|
}
|
|
40022
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
39849
40023
|
};
|
|
39850
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39851
40024
|
/**
|
|
39852
40025
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39853
40026
|
* the runner: once for the primary scan, once for the
|
|
@@ -39906,7 +40079,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39906
40079
|
* 6. always restore disable directives + clean up the temp dir
|
|
39907
40080
|
*/
|
|
39908
40081
|
const runOxlint = async (options) => {
|
|
39909
|
-
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 = "
|
|
40082
|
+
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;
|
|
39910
40083
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39911
40084
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39912
40085
|
validateRuleRegistration();
|
|
@@ -39958,8 +40131,15 @@ const runOxlint = async (options) => {
|
|
|
39958
40131
|
"json",
|
|
39959
40132
|
...sharedArgs
|
|
39960
40133
|
];
|
|
39961
|
-
|
|
39962
|
-
const
|
|
40134
|
+
await yieldToEventLoop();
|
|
40135
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
40136
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
40137
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
40138
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
40139
|
+
baseArgs: passBaseArgs,
|
|
40140
|
+
files: passFiles,
|
|
40141
|
+
sizeByFile
|
|
40142
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
39963
40143
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39964
40144
|
if (files.length === 0) return {
|
|
39965
40145
|
diagnostics: [],
|
|
@@ -39973,7 +40153,7 @@ const runOxlint = async (options) => {
|
|
|
39973
40153
|
};
|
|
39974
40154
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39975
40155
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39976
|
-
const passFileBatches =
|
|
40156
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
39977
40157
|
const spawnPass = () => spawnLintBatches({
|
|
39978
40158
|
baseArgs: passBaseArgs,
|
|
39979
40159
|
fileBatches: passFileBatches,
|
|
@@ -40022,6 +40202,7 @@ const runOxlint = async (options) => {
|
|
|
40022
40202
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40023
40203
|
const missFiles = [];
|
|
40024
40204
|
const replayedDiagnostics = [];
|
|
40205
|
+
let hashSliceStartedAt = performance.now();
|
|
40025
40206
|
for (const candidateFile of candidateFiles) {
|
|
40026
40207
|
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
40027
40208
|
if (contentHash === null) {
|
|
@@ -40033,6 +40214,10 @@ const runOxlint = async (options) => {
|
|
|
40033
40214
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
40034
40215
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
40035
40216
|
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
40217
|
+
if (performance.now() - hashSliceStartedAt >= 12) {
|
|
40218
|
+
await yieldToEventLoop();
|
|
40219
|
+
hashSliceStartedAt = performance.now();
|
|
40220
|
+
}
|
|
40036
40221
|
}
|
|
40037
40222
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40038
40223
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
@@ -40075,7 +40260,7 @@ const runOxlint = async (options) => {
|
|
|
40075
40260
|
]);
|
|
40076
40261
|
}
|
|
40077
40262
|
const baseArgs = makeBaseArgs(configPath);
|
|
40078
|
-
const fileBatches =
|
|
40263
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
40079
40264
|
const runBatches = () => spawnLintBatches({
|
|
40080
40265
|
baseArgs,
|
|
40081
40266
|
fileBatches,
|
|
@@ -40962,7 +41147,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40962
41147
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40963
41148
|
rootDirectory: scanDirectory,
|
|
40964
41149
|
parseConcurrency: deadCodeParseConcurrency,
|
|
40965
|
-
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
41150
|
+
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
|
|
41151
|
+
onCacheOutcome: (didHitCache) => {
|
|
41152
|
+
deadCodeCacheHit = didHitCache;
|
|
41153
|
+
}
|
|
40966
41154
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
40967
41155
|
yield* set(deadCodeFailure, {
|
|
40968
41156
|
didFail: true,
|
|
@@ -40990,6 +41178,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40990
41178
|
let lastReportedTotalFileCount = 0;
|
|
40991
41179
|
let lintCacheHitFileCount = null;
|
|
40992
41180
|
let lintCacheTotalFileCount = null;
|
|
41181
|
+
let deadCodeCacheHit = null;
|
|
40993
41182
|
const baseLintStream = linterService.run({
|
|
40994
41183
|
rootDirectory: scanDirectory,
|
|
40995
41184
|
project,
|
|
@@ -41119,6 +41308,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41119
41308
|
securityScanFailed,
|
|
41120
41309
|
lintCacheHitFileCount,
|
|
41121
41310
|
lintCacheTotalFileCount,
|
|
41311
|
+
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41122
41312
|
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
41123
41313
|
};
|
|
41124
41314
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
@@ -41759,4 +41949,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41759
41949
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41760
41950
|
|
|
41761
41951
|
//# sourceMappingURL=index.js.map
|
|
41762
|
-
//# debugId=
|
|
41952
|
+
//# debugId=e96374bd-b4c9-5452-8570-b81c11a2959a
|