react-doctor 0.6.3-dev.ce49250 → 0.6.3-dev.ced746f
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 +842 -346
- package/dist/index.d.ts +8 -0
- package/dist/index.js +487 -63
- package/dist/lsp.js +490 -68
- package/package.json +3 -3
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]="b81c51d5-0ead-51a4-a9f9-ae499fd19a52")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import * as NodeChildProcess from "node:child_process";
|
|
10
10
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import * as ts from "typescript";
|
|
12
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
13
13
|
import * as OS from "node:os";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -32928,6 +32928,7 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32928
32928
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32929
32929
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32930
32930
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32931
|
+
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32931
32932
|
const CORE_PACKAGE_VERSION = "0.6.3";
|
|
32932
32933
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32933
32934
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
@@ -32935,6 +32936,7 @@ const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
|
32935
32936
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32936
32937
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
32937
32938
|
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
32939
|
+
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
32938
32940
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
32939
32941
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
32940
32942
|
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
@@ -35618,6 +35620,29 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35618
35620
|
return true;
|
|
35619
35621
|
} }) {};
|
|
35620
35622
|
/**
|
|
35623
|
+
* Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
|
|
35624
|
+
* active. Defaults ON — warm rescans replay the cross-file rules' cached
|
|
35625
|
+
* diagnostics for every file whose dependency fingerprint (the probe set the
|
|
35626
|
+
* plugin's collectors recorded) still matches the tree, and re-lint only the
|
|
35627
|
+
* rest. Only reachable when the per-file lint cache itself is on (the
|
|
35628
|
+
* sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
|
|
35629
|
+
* `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
|
|
35630
|
+
*
|
|
35631
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35632
|
+
* - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
|
|
35633
|
+
* per-file cache but run the always-fresh sidecar over every hit
|
|
35634
|
+
* (the pre-cache behavior).
|
|
35635
|
+
*
|
|
35636
|
+
* Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
|
|
35637
|
+
*/
|
|
35638
|
+
var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
|
|
35639
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35640
|
+
const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
|
|
35641
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35642
|
+
if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
|
|
35643
|
+
return true;
|
|
35644
|
+
} }) {};
|
|
35645
|
+
/**
|
|
35621
35646
|
* Whether the whole-project dead-code result cache
|
|
35622
35647
|
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35623
35648
|
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
@@ -37341,22 +37366,37 @@ const failOpenReadJson = (filePath, fallback) => {
|
|
|
37341
37366
|
return fallback;
|
|
37342
37367
|
}
|
|
37343
37368
|
};
|
|
37369
|
+
const hashFileContents = (filePath) => {
|
|
37370
|
+
try {
|
|
37371
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
37372
|
+
} catch {
|
|
37373
|
+
return null;
|
|
37374
|
+
}
|
|
37375
|
+
};
|
|
37344
37376
|
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
37345
37377
|
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
37346
37378
|
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
37347
37379
|
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
37348
37380
|
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
37349
|
-
|
|
37350
|
-
|
|
37381
|
+
/**
|
|
37382
|
+
* Stat snapshot of every file the analysis reads, keyed by root-relative
|
|
37383
|
+
* `/`-separated path. Taken BEFORE the (long) analysis so a stored result is
|
|
37384
|
+
* verified against the tree it started from.
|
|
37385
|
+
*/
|
|
37386
|
+
const collectAnalyzedFileStats = (rootDirectory) => {
|
|
37387
|
+
const statByRelativePath = /* @__PURE__ */ new Map();
|
|
37351
37388
|
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
37352
37389
|
if (!isFingerprintedFile(name)) continue;
|
|
37353
37390
|
try {
|
|
37354
37391
|
const fileStat = NFS.statSync(absolutePath);
|
|
37355
37392
|
const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
|
|
37356
|
-
|
|
37393
|
+
statByRelativePath.set(relativePath, {
|
|
37394
|
+
mtimeMs: fileStat.mtimeMs,
|
|
37395
|
+
size: fileStat.size
|
|
37396
|
+
});
|
|
37357
37397
|
} catch {}
|
|
37358
37398
|
}
|
|
37359
|
-
return
|
|
37399
|
+
return statByRelativePath;
|
|
37360
37400
|
};
|
|
37361
37401
|
const bundledRequire$2 = createRequire(import.meta.url);
|
|
37362
37402
|
const resolveDeslopVersion = () => {
|
|
@@ -37368,34 +37408,85 @@ const resolveDeslopVersion = () => {
|
|
|
37368
37408
|
}
|
|
37369
37409
|
};
|
|
37370
37410
|
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
37371
|
-
schemaVersion:
|
|
37411
|
+
schemaVersion: 2,
|
|
37372
37412
|
coreVersion: input.coreVersion,
|
|
37373
37413
|
deslopVersion: resolveDeslopVersion(),
|
|
37374
37414
|
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
37375
37415
|
entryPatterns: input.entryPatterns,
|
|
37376
37416
|
ignorePatterns: input.ignorePatterns,
|
|
37377
|
-
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
|
|
37378
|
-
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
37417
|
+
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
|
|
37379
37418
|
})).digest("hex");
|
|
37380
|
-
const validateDiagnostic$
|
|
37419
|
+
const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
|
|
37381
37420
|
const decodeCachedDiagnostics = (raw) => {
|
|
37382
37421
|
try {
|
|
37383
|
-
for (const entry of raw) validateDiagnostic$
|
|
37422
|
+
for (const entry of raw) validateDiagnostic$2(entry);
|
|
37384
37423
|
return raw;
|
|
37385
37424
|
} catch {
|
|
37386
37425
|
return null;
|
|
37387
37426
|
}
|
|
37388
37427
|
};
|
|
37389
|
-
const
|
|
37390
|
-
|
|
37391
|
-
|
|
37392
|
-
|
|
37428
|
+
const isPersistedFileIdentity = (value) => Array.isArray(value) && value.length === 3 && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "string";
|
|
37429
|
+
const lookupDeadCodeResultCache = (input) => {
|
|
37430
|
+
const cacheFilePath = Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME);
|
|
37431
|
+
const persisted = failOpenReadJson(cacheFilePath, null);
|
|
37432
|
+
if (persisted === null || !isRecord(persisted) || persisted.version !== 2 || persisted.key !== input.cacheKey || !isRecord(persisted.files) || !Array.isArray(persisted.diagnostics)) return null;
|
|
37433
|
+
const storedFileEntries = Object.entries(persisted.files);
|
|
37434
|
+
if (storedFileEntries.length !== input.currentFileStats.size) return null;
|
|
37435
|
+
const repairedFiles = {};
|
|
37436
|
+
let repairedCount = 0;
|
|
37437
|
+
for (const [relativePath, storedIdentity] of storedFileEntries) {
|
|
37438
|
+
if (!isPersistedFileIdentity(storedIdentity)) return null;
|
|
37439
|
+
const currentStat = input.currentFileStats.get(relativePath);
|
|
37440
|
+
if (currentStat === void 0) return null;
|
|
37441
|
+
const [storedMtimeMs, storedSize, storedContentHash] = storedIdentity;
|
|
37442
|
+
if (currentStat.mtimeMs === storedMtimeMs && currentStat.size === storedSize) {
|
|
37443
|
+
repairedFiles[relativePath] = storedIdentity;
|
|
37444
|
+
continue;
|
|
37445
|
+
}
|
|
37446
|
+
if (currentStat.size !== storedSize) return null;
|
|
37447
|
+
const currentContentHash = hashFileContents(Path.join(input.rootDirectory, relativePath));
|
|
37448
|
+
if (currentContentHash === null || currentContentHash !== storedContentHash) return null;
|
|
37449
|
+
repairedFiles[relativePath] = [
|
|
37450
|
+
currentStat.mtimeMs,
|
|
37451
|
+
currentStat.size,
|
|
37452
|
+
storedContentHash
|
|
37453
|
+
];
|
|
37454
|
+
repairedCount += 1;
|
|
37455
|
+
}
|
|
37456
|
+
const diagnostics = decodeCachedDiagnostics(persisted.diagnostics);
|
|
37457
|
+
if (diagnostics === null) return null;
|
|
37458
|
+
if (repairedCount > 0) atomicWriteJson(cacheFilePath, {
|
|
37459
|
+
version: 2,
|
|
37460
|
+
key: input.cacheKey,
|
|
37461
|
+
files: repairedFiles,
|
|
37462
|
+
diagnostics: persisted.diagnostics
|
|
37463
|
+
});
|
|
37464
|
+
return diagnostics;
|
|
37393
37465
|
};
|
|
37394
|
-
const storeDeadCodeResultCache = (
|
|
37395
|
-
|
|
37396
|
-
|
|
37397
|
-
|
|
37398
|
-
|
|
37466
|
+
const storeDeadCodeResultCache = (input) => {
|
|
37467
|
+
const persistedFiles = {};
|
|
37468
|
+
for (const [relativePath, snapshotStat] of input.snapshotFileStats) {
|
|
37469
|
+
const absolutePath = Path.join(input.rootDirectory, relativePath);
|
|
37470
|
+
const contentHash = hashFileContents(absolutePath);
|
|
37471
|
+
if (contentHash === null) return;
|
|
37472
|
+
let currentStat;
|
|
37473
|
+
try {
|
|
37474
|
+
currentStat = NFS.statSync(absolutePath);
|
|
37475
|
+
} catch {
|
|
37476
|
+
return;
|
|
37477
|
+
}
|
|
37478
|
+
if (currentStat.mtimeMs !== snapshotStat.mtimeMs || currentStat.size !== snapshotStat.size) return;
|
|
37479
|
+
persistedFiles[relativePath] = [
|
|
37480
|
+
snapshotStat.mtimeMs,
|
|
37481
|
+
snapshotStat.size,
|
|
37482
|
+
contentHash
|
|
37483
|
+
];
|
|
37484
|
+
}
|
|
37485
|
+
atomicWriteJson(Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
|
|
37486
|
+
version: 2,
|
|
37487
|
+
key: input.cacheKey,
|
|
37488
|
+
files: persistedFiles,
|
|
37489
|
+
diagnostics: input.diagnostics
|
|
37399
37490
|
});
|
|
37400
37491
|
};
|
|
37401
37492
|
const readSystemFacts = () => ({
|
|
@@ -37805,16 +37896,22 @@ const checkDeadCode = async (options) => {
|
|
|
37805
37896
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37806
37897
|
const tsConfigPath = resolveTsConfigPath(rootDirectory);
|
|
37807
37898
|
const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
|
|
37808
|
-
const
|
|
37899
|
+
const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
|
|
37900
|
+
const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
|
|
37809
37901
|
rootDirectory,
|
|
37810
37902
|
entryPatterns,
|
|
37811
37903
|
ignorePatterns,
|
|
37812
37904
|
tsConfigPath,
|
|
37813
37905
|
deslopJsModuleSpecifier,
|
|
37814
37906
|
coreVersion: CORE_PACKAGE_VERSION
|
|
37815
|
-
})
|
|
37816
|
-
if (cacheKey !== null) {
|
|
37817
|
-
const cachedDiagnostics = lookupDeadCodeResultCache(
|
|
37907
|
+
});
|
|
37908
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) {
|
|
37909
|
+
const cachedDiagnostics = lookupDeadCodeResultCache({
|
|
37910
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
37911
|
+
cacheKey,
|
|
37912
|
+
rootDirectory,
|
|
37913
|
+
currentFileStats: fileStatsSnapshot
|
|
37914
|
+
});
|
|
37818
37915
|
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
37819
37916
|
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
37820
37917
|
}
|
|
@@ -37888,7 +37985,13 @@ const checkDeadCode = async (options) => {
|
|
|
37888
37985
|
category: DEAD_CODE_CATEGORY
|
|
37889
37986
|
});
|
|
37890
37987
|
}
|
|
37891
|
-
if (cacheKey !== null) storeDeadCodeResultCache(
|
|
37988
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
|
|
37989
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
37990
|
+
cacheKey,
|
|
37991
|
+
rootDirectory,
|
|
37992
|
+
snapshotFileStats: fileStatsSnapshot,
|
|
37993
|
+
diagnostics
|
|
37994
|
+
});
|
|
37892
37995
|
return diagnostics;
|
|
37893
37996
|
};
|
|
37894
37997
|
const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
|
|
@@ -38870,7 +38973,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38870
38973
|
}
|
|
38871
38974
|
return enabled;
|
|
38872
38975
|
};
|
|
38873
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38976
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
38874
38977
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38875
38978
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38876
38979
|
const jsPlugins = [];
|
|
@@ -38882,6 +38985,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38882
38985
|
if (!rule) continue;
|
|
38883
38986
|
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38884
38987
|
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38988
|
+
if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
|
|
38885
38989
|
if (rule.scan !== void 0) continue;
|
|
38886
38990
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38887
38991
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38926,23 +39030,23 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38926
39030
|
}
|
|
38927
39031
|
};
|
|
38928
39032
|
};
|
|
38929
|
-
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
39033
|
+
const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
|
|
38930
39034
|
const decodeFileDiagnostics = (raw) => {
|
|
38931
39035
|
if (!Array.isArray(raw)) return null;
|
|
38932
39036
|
try {
|
|
38933
|
-
for (const entry of raw) validateDiagnostic(entry);
|
|
39037
|
+
for (const entry of raw) validateDiagnostic$1(entry);
|
|
38934
39038
|
return raw;
|
|
38935
39039
|
} catch {
|
|
38936
39040
|
return null;
|
|
38937
39041
|
}
|
|
38938
39042
|
};
|
|
38939
|
-
const emptyCache = () => ({
|
|
39043
|
+
const emptyCache$1 = () => ({
|
|
38940
39044
|
version: 1,
|
|
38941
39045
|
rulesets: {}
|
|
38942
39046
|
});
|
|
38943
|
-
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39047
|
+
const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
|
|
38944
39048
|
const entries = /* @__PURE__ */ new Map();
|
|
38945
|
-
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39049
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38946
39050
|
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38947
39051
|
const bucket = persisted.rulesets[rulesetHash];
|
|
38948
39052
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
@@ -38954,7 +39058,7 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
|
38954
39058
|
};
|
|
38955
39059
|
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38956
39060
|
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38957
|
-
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
39061
|
+
const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
|
|
38958
39062
|
return {
|
|
38959
39063
|
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38960
39064
|
store: (fileKey, diagnostics) => {
|
|
@@ -38962,7 +39066,7 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38962
39066
|
entries.set(fileKey, diagnostics);
|
|
38963
39067
|
},
|
|
38964
39068
|
persist: () => {
|
|
38965
|
-
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39069
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38966
39070
|
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38967
39071
|
const existingBucket = rulesets[rulesetHash];
|
|
38968
39072
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
@@ -38983,6 +39087,135 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38983
39087
|
}
|
|
38984
39088
|
};
|
|
38985
39089
|
};
|
|
39090
|
+
const ABSENT_CONTENT_ANSWER = "absent";
|
|
39091
|
+
/**
|
|
39092
|
+
* Answers dependency probes for the sidecar lint cache — used both when
|
|
39093
|
+
* STORING an entry (turn the collector's probe paths into answers) and when
|
|
39094
|
+
* VERIFYING one (re-answer each stored probe against the current tree).
|
|
39095
|
+
* Store and verify must agree exactly, so both go through this one resolver.
|
|
39096
|
+
*
|
|
39097
|
+
* - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
|
|
39098
|
+
* (missing, a directory, permission failure). Source files reuse the
|
|
39099
|
+
* hashes the cache-partition loop already computed
|
|
39100
|
+
* (`contentHashByRelativePath`); everything else (package.json,
|
|
39101
|
+
* tsconfig, ignored files) is hashed on demand.
|
|
39102
|
+
* - `exists` — the existence class a module-resolution candidate probe
|
|
39103
|
+
* sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
|
|
39104
|
+
* `statSync` follows symlinks, matching the resolvers).
|
|
39105
|
+
*
|
|
39106
|
+
* Answers are memoized per scan: the filesystem is frozen for the run, and
|
|
39107
|
+
* hot dependency paths (a barrel imported by hundreds of files, the shared
|
|
39108
|
+
* tsconfig chain) repeat across many files' probe sets.
|
|
39109
|
+
*/
|
|
39110
|
+
const createSidecarProbeAnswerResolver = (input) => {
|
|
39111
|
+
const contentAnswerByPath = /* @__PURE__ */ new Map();
|
|
39112
|
+
const existsAnswerByPath = /* @__PURE__ */ new Map();
|
|
39113
|
+
const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
|
|
39114
|
+
const contentAnswer = (relativePath) => {
|
|
39115
|
+
const memoized = contentAnswerByPath.get(relativePath);
|
|
39116
|
+
if (memoized !== void 0) return memoized;
|
|
39117
|
+
const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
|
|
39118
|
+
contentAnswerByPath.set(relativePath, answer);
|
|
39119
|
+
return answer;
|
|
39120
|
+
};
|
|
39121
|
+
const existsAnswer = (relativePath) => {
|
|
39122
|
+
const memoized = existsAnswerByPath.get(relativePath);
|
|
39123
|
+
if (memoized !== void 0) return memoized;
|
|
39124
|
+
let answer;
|
|
39125
|
+
try {
|
|
39126
|
+
const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
|
|
39127
|
+
answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
|
|
39128
|
+
} catch {
|
|
39129
|
+
answer = "none";
|
|
39130
|
+
}
|
|
39131
|
+
existsAnswerByPath.set(relativePath, answer);
|
|
39132
|
+
return answer;
|
|
39133
|
+
};
|
|
39134
|
+
return {
|
|
39135
|
+
answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
|
|
39136
|
+
toRelativePath
|
|
39137
|
+
};
|
|
39138
|
+
};
|
|
39139
|
+
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
39140
|
+
const isProbeKind = (value) => value === "content" || value === "exists";
|
|
39141
|
+
const decodeSidecarFileEntry = (raw) => {
|
|
39142
|
+
if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
|
|
39143
|
+
const probes = [];
|
|
39144
|
+
for (const rawProbe of raw.probes) {
|
|
39145
|
+
if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
|
|
39146
|
+
const [kind, probePath, answer] = rawProbe;
|
|
39147
|
+
if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
|
|
39148
|
+
probes.push({
|
|
39149
|
+
kind,
|
|
39150
|
+
path: probePath,
|
|
39151
|
+
answer
|
|
39152
|
+
});
|
|
39153
|
+
}
|
|
39154
|
+
try {
|
|
39155
|
+
for (const entry of raw.diagnostics) validateDiagnostic(entry);
|
|
39156
|
+
} catch {
|
|
39157
|
+
return null;
|
|
39158
|
+
}
|
|
39159
|
+
return {
|
|
39160
|
+
probes,
|
|
39161
|
+
diagnostics: raw.diagnostics
|
|
39162
|
+
};
|
|
39163
|
+
};
|
|
39164
|
+
const encodeSidecarFileEntry = (entry) => ({
|
|
39165
|
+
probes: entry.probes.map((probe) => [
|
|
39166
|
+
probe.kind,
|
|
39167
|
+
probe.path,
|
|
39168
|
+
probe.answer
|
|
39169
|
+
]),
|
|
39170
|
+
diagnostics: entry.diagnostics
|
|
39171
|
+
});
|
|
39172
|
+
const emptyCache = () => ({
|
|
39173
|
+
version: 1,
|
|
39174
|
+
rulesets: {}
|
|
39175
|
+
});
|
|
39176
|
+
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39177
|
+
const entries = /* @__PURE__ */ new Map();
|
|
39178
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39179
|
+
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
39180
|
+
const bucket = persisted.rulesets[rulesetHash];
|
|
39181
|
+
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39182
|
+
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
39183
|
+
const decoded = decodeSidecarFileEntry(rawEntry);
|
|
39184
|
+
if (decoded !== null) entries.set(fileKey, decoded);
|
|
39185
|
+
}
|
|
39186
|
+
return entries;
|
|
39187
|
+
};
|
|
39188
|
+
const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
39189
|
+
const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
|
|
39190
|
+
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
39191
|
+
return {
|
|
39192
|
+
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
39193
|
+
store: (fileKey, entry) => {
|
|
39194
|
+
entries.delete(fileKey);
|
|
39195
|
+
entries.set(fileKey, entry);
|
|
39196
|
+
},
|
|
39197
|
+
persist: () => {
|
|
39198
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39199
|
+
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39200
|
+
const existingBucket = rulesets[rulesetHash];
|
|
39201
|
+
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39202
|
+
const ourFiles = {};
|
|
39203
|
+
for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
|
|
39204
|
+
const cappedEntries = Object.entries({
|
|
39205
|
+
...existingFiles,
|
|
39206
|
+
...ourFiles
|
|
39207
|
+
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
39208
|
+
rulesets[rulesetHash] = {
|
|
39209
|
+
updatedAtMs: Date.now(),
|
|
39210
|
+
files: Object.fromEntries(cappedEntries)
|
|
39211
|
+
};
|
|
39212
|
+
atomicWriteJson(cacheFilePath, {
|
|
39213
|
+
version: 1,
|
|
39214
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39215
|
+
});
|
|
39216
|
+
}
|
|
39217
|
+
};
|
|
39218
|
+
};
|
|
38986
39219
|
const bundledRequire = createRequire(import.meta.url);
|
|
38987
39220
|
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
38988
39221
|
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
@@ -39982,13 +40215,6 @@ const validateRuleRegistration = () => {
|
|
|
39982
40215
|
].filter((entry) => entry !== null).join("; ");
|
|
39983
40216
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
39984
40217
|
};
|
|
39985
|
-
const hashFileContents = (filePath) => {
|
|
39986
|
-
try {
|
|
39987
|
-
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39988
|
-
} catch {
|
|
39989
|
-
return null;
|
|
39990
|
-
}
|
|
39991
|
-
};
|
|
39992
40218
|
/**
|
|
39993
40219
|
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39994
40220
|
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
@@ -40070,6 +40296,106 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
40070
40296
|
NFS.closeSync(fileHandle);
|
|
40071
40297
|
}
|
|
40072
40298
|
};
|
|
40299
|
+
/**
|
|
40300
|
+
* Attributes diagnostics back to the file that produced them by the
|
|
40301
|
+
* normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
|
|
40302
|
+
* attributed — the path forms don't align, so the caller must skip its cache
|
|
40303
|
+
* store rather than risk caching a wrong empty result for a file that
|
|
40304
|
+
* actually had diagnostics.
|
|
40305
|
+
*/
|
|
40306
|
+
const attributeDiagnosticsToFiles = (diagnostics, files) => {
|
|
40307
|
+
const fileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40308
|
+
for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
|
|
40309
|
+
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40310
|
+
for (const diagnostic of diagnostics) {
|
|
40311
|
+
const file = fileByNormalizedPath.get(diagnostic.filePath);
|
|
40312
|
+
if (file === void 0) return null;
|
|
40313
|
+
const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
|
|
40314
|
+
fileDiagnostics.push(diagnostic);
|
|
40315
|
+
diagnosticsByFile.set(file, fileDiagnostics);
|
|
40316
|
+
}
|
|
40317
|
+
return diagnosticsByFile;
|
|
40318
|
+
};
|
|
40319
|
+
/**
|
|
40320
|
+
* Collects each file's cross-file dependency probes (via the plugin's
|
|
40321
|
+
* collectors) with their current answers. Fail-open per file: an unreadable /
|
|
40322
|
+
* unparseable / collector-failing file maps to `null` (no entry stored — it
|
|
40323
|
+
* re-lints next scan). The filesystem is frozen for the scan, so this can run
|
|
40324
|
+
* CONCURRENTLY with an oxlint child-process await — the caller starts it
|
|
40325
|
+
* unawaited before a lint pass and awaits it at store time, hiding the
|
|
40326
|
+
* in-process parse + resolution cost under the subprocess wait. The loop
|
|
40327
|
+
* yields on the cooperative time budget so the child's stdout handling (and
|
|
40328
|
+
* sibling fibers) keep flowing; each per-file collection is synchronous, so
|
|
40329
|
+
* the module-level probe recorder never sees interleaved use. Never rejects.
|
|
40330
|
+
*/
|
|
40331
|
+
const collectSidecarProbesForFiles = async (input) => {
|
|
40332
|
+
const buildProbes = (file) => {
|
|
40333
|
+
const absoluteFilePath = Path.resolve(input.rootDirectory, file);
|
|
40334
|
+
let trace;
|
|
40335
|
+
try {
|
|
40336
|
+
trace = collectCrossFileDependencyProbes({
|
|
40337
|
+
absoluteFilePath,
|
|
40338
|
+
sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
|
|
40339
|
+
ruleIds: input.boundedSidecarRuleIds
|
|
40340
|
+
});
|
|
40341
|
+
} catch {
|
|
40342
|
+
return null;
|
|
40343
|
+
}
|
|
40344
|
+
if (trace === null) return null;
|
|
40345
|
+
const probes = [];
|
|
40346
|
+
for (const contentPath of trace.contentPaths) {
|
|
40347
|
+
const relativePath = input.probeAnswers.toRelativePath(contentPath);
|
|
40348
|
+
probes.push({
|
|
40349
|
+
kind: "content",
|
|
40350
|
+
path: relativePath,
|
|
40351
|
+
answer: input.probeAnswers.answerFor("content", relativePath)
|
|
40352
|
+
});
|
|
40353
|
+
}
|
|
40354
|
+
for (const existencePath of trace.existencePaths) {
|
|
40355
|
+
const relativePath = input.probeAnswers.toRelativePath(existencePath);
|
|
40356
|
+
probes.push({
|
|
40357
|
+
kind: "exists",
|
|
40358
|
+
path: relativePath,
|
|
40359
|
+
answer: input.probeAnswers.answerFor("exists", relativePath)
|
|
40360
|
+
});
|
|
40361
|
+
}
|
|
40362
|
+
return probes;
|
|
40363
|
+
};
|
|
40364
|
+
const probesByFile = /* @__PURE__ */ new Map();
|
|
40365
|
+
let collectSliceStartedAt = performance.now();
|
|
40366
|
+
for (const file of input.files) {
|
|
40367
|
+
probesByFile.set(file, buildProbes(file));
|
|
40368
|
+
if (performance.now() - collectSliceStartedAt >= 12) {
|
|
40369
|
+
await yieldToEventLoop();
|
|
40370
|
+
collectSliceStartedAt = performance.now();
|
|
40371
|
+
}
|
|
40372
|
+
}
|
|
40373
|
+
return probesByFile;
|
|
40374
|
+
};
|
|
40375
|
+
/**
|
|
40376
|
+
* Stores each freshly-linted file's probe set + cross-file diagnostics so
|
|
40377
|
+
* the next scan can replay instead of re-linting.
|
|
40378
|
+
*/
|
|
40379
|
+
const storeSidecarEntries = (input) => {
|
|
40380
|
+
let didStoreAnyEntry = false;
|
|
40381
|
+
for (const pass of input.storablePasses) {
|
|
40382
|
+
if (!pass.isTrusted) continue;
|
|
40383
|
+
const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
|
|
40384
|
+
if (diagnosticsByFile === null) continue;
|
|
40385
|
+
for (const file of pass.files) {
|
|
40386
|
+
const cacheKey = input.cacheKeyByFile.get(file);
|
|
40387
|
+
if (cacheKey === void 0) continue;
|
|
40388
|
+
const probes = pass.probesByFile.get(file);
|
|
40389
|
+
if (probes === void 0 || probes === null) continue;
|
|
40390
|
+
input.sidecarCache.store(cacheKey, {
|
|
40391
|
+
probes,
|
|
40392
|
+
diagnostics: diagnosticsByFile.get(file) ?? []
|
|
40393
|
+
});
|
|
40394
|
+
didStoreAnyEntry = true;
|
|
40395
|
+
}
|
|
40396
|
+
}
|
|
40397
|
+
if (didStoreAnyEntry) input.sidecarCache.persist();
|
|
40398
|
+
};
|
|
40073
40399
|
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
40074
40400
|
/**
|
|
40075
40401
|
* Detects an oxlint config-load crash caused by the optional
|
|
@@ -40111,7 +40437,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
40111
40437
|
* 6. always restore disable directives + clean up the temp dir
|
|
40112
40438
|
*/
|
|
40113
40439
|
const runOxlint = async (options) => {
|
|
40114
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
40440
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
40115
40441
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40116
40442
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40117
40443
|
validateRuleRegistration();
|
|
@@ -40129,7 +40455,8 @@ const runOxlint = async (options) => {
|
|
|
40129
40455
|
severityControls,
|
|
40130
40456
|
userPlugins,
|
|
40131
40457
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
40132
|
-
ruleSelection: overrides.ruleSelection
|
|
40458
|
+
ruleSelection: overrides.ruleSelection,
|
|
40459
|
+
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
40133
40460
|
});
|
|
40134
40461
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
40135
40462
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -40232,15 +40559,18 @@ const runOxlint = async (options) => {
|
|
|
40232
40559
|
});
|
|
40233
40560
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
40234
40561
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40562
|
+
const contentHashByRelativePath = /* @__PURE__ */ new Map();
|
|
40235
40563
|
const missFiles = [];
|
|
40236
40564
|
const replayedDiagnostics = [];
|
|
40237
40565
|
let hashSliceStartedAt = performance.now();
|
|
40238
40566
|
for (const candidateFile of candidateFiles) {
|
|
40239
|
-
const
|
|
40567
|
+
const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
|
|
40568
|
+
const contentHash = hashFileContents(absoluteCandidatePath);
|
|
40240
40569
|
if (contentHash === null) {
|
|
40241
40570
|
missFiles.push(candidateFile);
|
|
40242
40571
|
continue;
|
|
40243
40572
|
}
|
|
40573
|
+
contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
|
|
40244
40574
|
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
40245
40575
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
40246
40576
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
@@ -40252,43 +40582,111 @@ const runOxlint = async (options) => {
|
|
|
40252
40582
|
}
|
|
40253
40583
|
}
|
|
40254
40584
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40585
|
+
const enabledSidecarRuleIds = Object.keys(buildConfig({
|
|
40586
|
+
extendsPaths: [],
|
|
40587
|
+
ruleSelection: "sidecar"
|
|
40588
|
+
}).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
|
|
40589
|
+
const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40590
|
+
const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
|
|
40591
|
+
const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40592
|
+
const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
|
|
40593
|
+
const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
|
|
40594
|
+
config: buildConfig({
|
|
40595
|
+
extendsPaths: [],
|
|
40596
|
+
ruleSelection: "sidecar",
|
|
40597
|
+
sidecarRuleIdFilter: boundedSidecarRuleIdSet
|
|
40598
|
+
}),
|
|
40599
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
40600
|
+
ignorePatterns: combinedPatterns,
|
|
40601
|
+
tsconfigContent
|
|
40602
|
+
})) : null;
|
|
40603
|
+
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
40604
|
+
rootDirectory,
|
|
40605
|
+
contentHashByRelativePath
|
|
40606
|
+
});
|
|
40607
|
+
const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40608
|
+
files: missFiles,
|
|
40609
|
+
rootDirectory,
|
|
40610
|
+
boundedSidecarRuleIds,
|
|
40611
|
+
probeAnswers
|
|
40612
|
+
});
|
|
40255
40613
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40256
40614
|
extendsPaths: [],
|
|
40257
40615
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40258
40616
|
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40259
40617
|
const missFileSet = new Set(missFiles);
|
|
40260
|
-
const
|
|
40618
|
+
const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
|
|
40619
|
+
const sidecarReplayedDiagnostics = [];
|
|
40620
|
+
const sidecarLintFiles = [];
|
|
40621
|
+
if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
|
|
40622
|
+
else {
|
|
40623
|
+
let verifySliceStartedAt = performance.now();
|
|
40624
|
+
for (const hitFile of hitFiles) {
|
|
40625
|
+
const cacheKey = cacheKeyByFile.get(hitFile);
|
|
40626
|
+
const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
|
|
40627
|
+
if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
|
|
40628
|
+
else sidecarLintFiles.push(hitFile);
|
|
40629
|
+
if (performance.now() - verifySliceStartedAt >= 12) {
|
|
40630
|
+
await yieldToEventLoop();
|
|
40631
|
+
verifySliceStartedAt = performance.now();
|
|
40632
|
+
}
|
|
40633
|
+
}
|
|
40634
|
+
}
|
|
40635
|
+
const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40636
|
+
files: sidecarLintFiles,
|
|
40637
|
+
rootDirectory,
|
|
40638
|
+
boundedSidecarRuleIds,
|
|
40639
|
+
probeAnswers
|
|
40640
|
+
});
|
|
40641
|
+
const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
|
|
40642
|
+
if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
|
|
40643
|
+
const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40261
40644
|
extendsPaths: [],
|
|
40262
|
-
ruleSelection: "sidecar"
|
|
40263
|
-
|
|
40645
|
+
ruleSelection: "sidecar",
|
|
40646
|
+
...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
|
|
40647
|
+
}), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
|
|
40648
|
+
const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
|
|
40649
|
+
extendsPaths: [],
|
|
40650
|
+
ruleSelection: "sidecar",
|
|
40651
|
+
sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
|
|
40652
|
+
}), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
|
|
40653
|
+
diagnostics: [],
|
|
40654
|
+
didDropReactHooksJsPlugin: false,
|
|
40655
|
+
hadPartialFailure: false
|
|
40656
|
+
};
|
|
40264
40657
|
const freshCacheableDiagnostics = [];
|
|
40265
40658
|
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40266
40659
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40267
|
-
|
|
40268
|
-
|
|
40269
|
-
|
|
40270
|
-
let isAttributionSound = true;
|
|
40271
|
-
for (const diagnostic of freshCacheableDiagnostics) {
|
|
40272
|
-
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
40273
|
-
if (missFile === void 0) {
|
|
40274
|
-
isAttributionSound = false;
|
|
40275
|
-
break;
|
|
40276
|
-
}
|
|
40277
|
-
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
40278
|
-
fileDiagnostics.push(diagnostic);
|
|
40279
|
-
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
40280
|
-
}
|
|
40281
|
-
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
40660
|
+
if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
|
|
40661
|
+
const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
|
|
40662
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
|
|
40282
40663
|
for (const missFile of missFiles) {
|
|
40283
40664
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40284
40665
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
40285
40666
|
}
|
|
40286
40667
|
cache.persist();
|
|
40287
40668
|
}
|
|
40669
|
+
if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
|
|
40670
|
+
sidecarCache,
|
|
40671
|
+
cacheKeyByFile,
|
|
40672
|
+
storablePasses: [{
|
|
40673
|
+
files: sidecarLintFiles,
|
|
40674
|
+
diagnostics: boundedSidecarResult.diagnostics,
|
|
40675
|
+
isTrusted: !boundedSidecarResult.hadPartialFailure,
|
|
40676
|
+
probesByFile: await sidecarProbesTask
|
|
40677
|
+
}, {
|
|
40678
|
+
files: missFiles,
|
|
40679
|
+
diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
|
|
40680
|
+
isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
|
|
40681
|
+
probesByFile: await missProbesTask
|
|
40682
|
+
}]
|
|
40683
|
+
});
|
|
40288
40684
|
return dedupeDiagnostics([
|
|
40289
40685
|
...replayedDiagnostics,
|
|
40290
40686
|
...fullResult.diagnostics,
|
|
40291
|
-
...
|
|
40687
|
+
...sidecarReplayedDiagnostics,
|
|
40688
|
+
...boundedSidecarResult.diagnostics,
|
|
40689
|
+
...unboundedSidecarResult.diagnostics
|
|
40292
40690
|
]);
|
|
40293
40691
|
}
|
|
40294
40692
|
const baseArgs = makeBaseArgs(configPath);
|
|
@@ -40386,6 +40784,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40386
40784
|
const concurrency = yield* OxlintConcurrency;
|
|
40387
40785
|
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40388
40786
|
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
40787
|
+
const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
|
|
40389
40788
|
const collectedFailures = [];
|
|
40390
40789
|
const diagnostics = yield* tryPromise({
|
|
40391
40790
|
try: (signal) => runOxlint({
|
|
@@ -40404,7 +40803,9 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40404
40803
|
},
|
|
40405
40804
|
onFileProgress: input.onFileProgress,
|
|
40406
40805
|
perFileLintCacheEnabled,
|
|
40806
|
+
sidecarLintCacheEnabled,
|
|
40407
40807
|
onCacheStats: input.onCacheStats,
|
|
40808
|
+
onSidecarStats: input.onSidecarStats,
|
|
40408
40809
|
spawnTimeoutMs,
|
|
40409
40810
|
outputMaxBytes,
|
|
40410
40811
|
concurrency,
|
|
@@ -40826,6 +41227,20 @@ const writeCachedSocketBody = (cacheFile, body) => {
|
|
|
40826
41227
|
}));
|
|
40827
41228
|
} catch {}
|
|
40828
41229
|
};
|
|
41230
|
+
const pruneExpiredSocketCache = (cacheDirectory) => {
|
|
41231
|
+
try {
|
|
41232
|
+
const supplyChainCacheDirectory = Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR);
|
|
41233
|
+
const expiryThresholdMs = Date.now() - SUPPLY_CHAIN_CACHE_TTL_MS;
|
|
41234
|
+
for (const entryName of NFS.readdirSync(supplyChainCacheDirectory)) {
|
|
41235
|
+
const entryPath = Path.join(supplyChainCacheDirectory, entryName);
|
|
41236
|
+
try {
|
|
41237
|
+
if (NFS.statSync(entryPath).mtimeMs < expiryThresholdMs) NFS.rmSync(entryPath);
|
|
41238
|
+
} catch {
|
|
41239
|
+
continue;
|
|
41240
|
+
}
|
|
41241
|
+
}
|
|
41242
|
+
} catch {}
|
|
41243
|
+
};
|
|
40829
41244
|
const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
|
|
40830
41245
|
const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
|
|
40831
41246
|
if (cacheFile !== null) {
|
|
@@ -40953,6 +41368,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40953
41368
|
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
40954
41369
|
if (dependencies.length === 0) return [];
|
|
40955
41370
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41371
|
+
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
40956
41372
|
const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
|
|
40957
41373
|
const diagnostics = [];
|
|
40958
41374
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
@@ -41214,6 +41630,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41214
41630
|
let lastReportedTotalFileCount = 0;
|
|
41215
41631
|
let lintCacheHitFileCount = null;
|
|
41216
41632
|
let lintCacheTotalFileCount = null;
|
|
41633
|
+
let lintSidecarReplayedFileCount = null;
|
|
41634
|
+
let lintSidecarTotalFileCount = null;
|
|
41217
41635
|
let deadCodeCacheHit = null;
|
|
41218
41636
|
let deadCodeSummaryCacheHits = null;
|
|
41219
41637
|
let deadCodeSummaryCacheMisses = null;
|
|
@@ -41236,6 +41654,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41236
41654
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
41237
41655
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
41238
41656
|
},
|
|
41657
|
+
onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
|
|
41658
|
+
lintSidecarReplayedFileCount = sidecarReplayedFileCount;
|
|
41659
|
+
lintSidecarTotalFileCount = sidecarConsideredFileCount;
|
|
41660
|
+
},
|
|
41239
41661
|
deadlineEpochMs: input.deadlineEpochMs
|
|
41240
41662
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41241
41663
|
yield* set(lintFailure, {
|
|
@@ -41346,6 +41768,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41346
41768
|
securityScanFailed,
|
|
41347
41769
|
lintCacheHitFileCount,
|
|
41348
41770
|
lintCacheTotalFileCount,
|
|
41771
|
+
lintSidecarReplayedFileCount,
|
|
41772
|
+
lintSidecarTotalFileCount,
|
|
41349
41773
|
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41350
41774
|
deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
|
|
41351
41775
|
deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
|
|
@@ -41989,4 +42413,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41989
42413
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41990
42414
|
|
|
41991
42415
|
//# sourceMappingURL=index.js.map
|
|
41992
|
-
//# debugId=
|
|
42416
|
+
//# debugId=b81c51d5-0ead-51a4-a9f9-ae499fd19a52
|