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/lsp.js
CHANGED
|
@@ -8,7 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
import * as NodeChildProcess from "node:child_process";
|
|
9
9
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import * as ts from "typescript";
|
|
11
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
11
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
12
|
import { parseJSON5 } from "confbox";
|
|
13
13
|
import * as NodeUrl from "node:url";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
@@ -32987,6 +32987,7 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32987
32987
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32988
32988
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32989
32989
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32990
|
+
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32990
32991
|
const CORE_PACKAGE_VERSION = "0.6.3";
|
|
32991
32992
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32992
32993
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
@@ -32994,6 +32995,7 @@ const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
|
32994
32995
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32995
32996
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
32996
32997
|
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
32998
|
+
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
32997
32999
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
32998
33000
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
32999
33001
|
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
@@ -35651,6 +35653,29 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35651
35653
|
return true;
|
|
35652
35654
|
} }) {};
|
|
35653
35655
|
/**
|
|
35656
|
+
* Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
|
|
35657
|
+
* active. Defaults ON — warm rescans replay the cross-file rules' cached
|
|
35658
|
+
* diagnostics for every file whose dependency fingerprint (the probe set the
|
|
35659
|
+
* plugin's collectors recorded) still matches the tree, and re-lint only the
|
|
35660
|
+
* rest. Only reachable when the per-file lint cache itself is on (the
|
|
35661
|
+
* sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
|
|
35662
|
+
* `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
|
|
35663
|
+
*
|
|
35664
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35665
|
+
* - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
|
|
35666
|
+
* per-file cache but run the always-fresh sidecar over every hit
|
|
35667
|
+
* (the pre-cache behavior).
|
|
35668
|
+
*
|
|
35669
|
+
* Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
|
|
35670
|
+
*/
|
|
35671
|
+
var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
|
|
35672
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35673
|
+
const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
|
|
35674
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35675
|
+
if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
|
|
35676
|
+
return true;
|
|
35677
|
+
} }) {};
|
|
35678
|
+
/**
|
|
35654
35679
|
* Whether the whole-project dead-code result cache
|
|
35655
35680
|
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35656
35681
|
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
@@ -37326,22 +37351,37 @@ const failOpenReadJson = (filePath, fallback) => {
|
|
|
37326
37351
|
return fallback;
|
|
37327
37352
|
}
|
|
37328
37353
|
};
|
|
37354
|
+
const hashFileContents = (filePath) => {
|
|
37355
|
+
try {
|
|
37356
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
37357
|
+
} catch {
|
|
37358
|
+
return null;
|
|
37359
|
+
}
|
|
37360
|
+
};
|
|
37329
37361
|
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
37330
37362
|
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
37331
37363
|
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
37332
37364
|
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
37333
37365
|
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
37334
|
-
|
|
37335
|
-
|
|
37366
|
+
/**
|
|
37367
|
+
* Stat snapshot of every file the analysis reads, keyed by root-relative
|
|
37368
|
+
* `/`-separated path. Taken BEFORE the (long) analysis so a stored result is
|
|
37369
|
+
* verified against the tree it started from.
|
|
37370
|
+
*/
|
|
37371
|
+
const collectAnalyzedFileStats = (rootDirectory) => {
|
|
37372
|
+
const statByRelativePath = /* @__PURE__ */ new Map();
|
|
37336
37373
|
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
37337
37374
|
if (!isFingerprintedFile(name)) continue;
|
|
37338
37375
|
try {
|
|
37339
37376
|
const fileStat = NFS.statSync(absolutePath);
|
|
37340
37377
|
const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
|
|
37341
|
-
|
|
37378
|
+
statByRelativePath.set(relativePath, {
|
|
37379
|
+
mtimeMs: fileStat.mtimeMs,
|
|
37380
|
+
size: fileStat.size
|
|
37381
|
+
});
|
|
37342
37382
|
} catch {}
|
|
37343
37383
|
}
|
|
37344
|
-
return
|
|
37384
|
+
return statByRelativePath;
|
|
37345
37385
|
};
|
|
37346
37386
|
const bundledRequire$2 = createRequire(import.meta.url);
|
|
37347
37387
|
const resolveDeslopVersion = () => {
|
|
@@ -37353,34 +37393,85 @@ const resolveDeslopVersion = () => {
|
|
|
37353
37393
|
}
|
|
37354
37394
|
};
|
|
37355
37395
|
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
37356
|
-
schemaVersion:
|
|
37396
|
+
schemaVersion: 2,
|
|
37357
37397
|
coreVersion: input.coreVersion,
|
|
37358
37398
|
deslopVersion: resolveDeslopVersion(),
|
|
37359
37399
|
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
37360
37400
|
entryPatterns: input.entryPatterns,
|
|
37361
37401
|
ignorePatterns: input.ignorePatterns,
|
|
37362
|
-
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
|
|
37363
|
-
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
37402
|
+
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
|
|
37364
37403
|
})).digest("hex");
|
|
37365
|
-
const validateDiagnostic$
|
|
37404
|
+
const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
|
|
37366
37405
|
const decodeCachedDiagnostics = (raw) => {
|
|
37367
37406
|
try {
|
|
37368
|
-
for (const entry of raw) validateDiagnostic$
|
|
37407
|
+
for (const entry of raw) validateDiagnostic$2(entry);
|
|
37369
37408
|
return raw;
|
|
37370
37409
|
} catch {
|
|
37371
37410
|
return null;
|
|
37372
37411
|
}
|
|
37373
37412
|
};
|
|
37374
|
-
const
|
|
37375
|
-
|
|
37376
|
-
|
|
37377
|
-
|
|
37413
|
+
const isPersistedFileIdentity = (value) => Array.isArray(value) && value.length === 3 && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "string";
|
|
37414
|
+
const lookupDeadCodeResultCache = (input) => {
|
|
37415
|
+
const cacheFilePath = Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME);
|
|
37416
|
+
const persisted = failOpenReadJson(cacheFilePath, null);
|
|
37417
|
+
if (persisted === null || !isRecord(persisted) || persisted.version !== 2 || persisted.key !== input.cacheKey || !isRecord(persisted.files) || !Array.isArray(persisted.diagnostics)) return null;
|
|
37418
|
+
const storedFileEntries = Object.entries(persisted.files);
|
|
37419
|
+
if (storedFileEntries.length !== input.currentFileStats.size) return null;
|
|
37420
|
+
const repairedFiles = {};
|
|
37421
|
+
let repairedCount = 0;
|
|
37422
|
+
for (const [relativePath, storedIdentity] of storedFileEntries) {
|
|
37423
|
+
if (!isPersistedFileIdentity(storedIdentity)) return null;
|
|
37424
|
+
const currentStat = input.currentFileStats.get(relativePath);
|
|
37425
|
+
if (currentStat === void 0) return null;
|
|
37426
|
+
const [storedMtimeMs, storedSize, storedContentHash] = storedIdentity;
|
|
37427
|
+
if (currentStat.mtimeMs === storedMtimeMs && currentStat.size === storedSize) {
|
|
37428
|
+
repairedFiles[relativePath] = storedIdentity;
|
|
37429
|
+
continue;
|
|
37430
|
+
}
|
|
37431
|
+
if (currentStat.size !== storedSize) return null;
|
|
37432
|
+
const currentContentHash = hashFileContents(Path.join(input.rootDirectory, relativePath));
|
|
37433
|
+
if (currentContentHash === null || currentContentHash !== storedContentHash) return null;
|
|
37434
|
+
repairedFiles[relativePath] = [
|
|
37435
|
+
currentStat.mtimeMs,
|
|
37436
|
+
currentStat.size,
|
|
37437
|
+
storedContentHash
|
|
37438
|
+
];
|
|
37439
|
+
repairedCount += 1;
|
|
37440
|
+
}
|
|
37441
|
+
const diagnostics = decodeCachedDiagnostics(persisted.diagnostics);
|
|
37442
|
+
if (diagnostics === null) return null;
|
|
37443
|
+
if (repairedCount > 0) atomicWriteJson(cacheFilePath, {
|
|
37444
|
+
version: 2,
|
|
37445
|
+
key: input.cacheKey,
|
|
37446
|
+
files: repairedFiles,
|
|
37447
|
+
diagnostics: persisted.diagnostics
|
|
37448
|
+
});
|
|
37449
|
+
return diagnostics;
|
|
37378
37450
|
};
|
|
37379
|
-
const storeDeadCodeResultCache = (
|
|
37380
|
-
|
|
37381
|
-
|
|
37382
|
-
|
|
37383
|
-
|
|
37451
|
+
const storeDeadCodeResultCache = (input) => {
|
|
37452
|
+
const persistedFiles = {};
|
|
37453
|
+
for (const [relativePath, snapshotStat] of input.snapshotFileStats) {
|
|
37454
|
+
const absolutePath = Path.join(input.rootDirectory, relativePath);
|
|
37455
|
+
const contentHash = hashFileContents(absolutePath);
|
|
37456
|
+
if (contentHash === null) return;
|
|
37457
|
+
let currentStat;
|
|
37458
|
+
try {
|
|
37459
|
+
currentStat = NFS.statSync(absolutePath);
|
|
37460
|
+
} catch {
|
|
37461
|
+
return;
|
|
37462
|
+
}
|
|
37463
|
+
if (currentStat.mtimeMs !== snapshotStat.mtimeMs || currentStat.size !== snapshotStat.size) return;
|
|
37464
|
+
persistedFiles[relativePath] = [
|
|
37465
|
+
snapshotStat.mtimeMs,
|
|
37466
|
+
snapshotStat.size,
|
|
37467
|
+
contentHash
|
|
37468
|
+
];
|
|
37469
|
+
}
|
|
37470
|
+
atomicWriteJson(Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
|
|
37471
|
+
version: 2,
|
|
37472
|
+
key: input.cacheKey,
|
|
37473
|
+
files: persistedFiles,
|
|
37474
|
+
diagnostics: input.diagnostics
|
|
37384
37475
|
});
|
|
37385
37476
|
};
|
|
37386
37477
|
const readSystemFacts = () => ({
|
|
@@ -37790,16 +37881,22 @@ const checkDeadCode = async (options) => {
|
|
|
37790
37881
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37791
37882
|
const tsConfigPath = resolveTsConfigPath(rootDirectory);
|
|
37792
37883
|
const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
|
|
37793
|
-
const
|
|
37884
|
+
const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
|
|
37885
|
+
const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
|
|
37794
37886
|
rootDirectory,
|
|
37795
37887
|
entryPatterns,
|
|
37796
37888
|
ignorePatterns,
|
|
37797
37889
|
tsConfigPath,
|
|
37798
37890
|
deslopJsModuleSpecifier,
|
|
37799
37891
|
coreVersion: CORE_PACKAGE_VERSION
|
|
37800
|
-
})
|
|
37801
|
-
if (cacheKey !== null) {
|
|
37802
|
-
const cachedDiagnostics = lookupDeadCodeResultCache(
|
|
37892
|
+
});
|
|
37893
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) {
|
|
37894
|
+
const cachedDiagnostics = lookupDeadCodeResultCache({
|
|
37895
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
37896
|
+
cacheKey,
|
|
37897
|
+
rootDirectory,
|
|
37898
|
+
currentFileStats: fileStatsSnapshot
|
|
37899
|
+
});
|
|
37803
37900
|
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
37804
37901
|
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
37805
37902
|
}
|
|
@@ -37873,7 +37970,13 @@ const checkDeadCode = async (options) => {
|
|
|
37873
37970
|
category: DEAD_CODE_CATEGORY
|
|
37874
37971
|
});
|
|
37875
37972
|
}
|
|
37876
|
-
if (cacheKey !== null) storeDeadCodeResultCache(
|
|
37973
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
|
|
37974
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
37975
|
+
cacheKey,
|
|
37976
|
+
rootDirectory,
|
|
37977
|
+
snapshotFileStats: fileStatsSnapshot,
|
|
37978
|
+
diagnostics
|
|
37979
|
+
});
|
|
37877
37980
|
return diagnostics;
|
|
37878
37981
|
};
|
|
37879
37982
|
const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
|
|
@@ -38855,7 +38958,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38855
38958
|
}
|
|
38856
38959
|
return enabled;
|
|
38857
38960
|
};
|
|
38858
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38961
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
38859
38962
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38860
38963
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38861
38964
|
const jsPlugins = [];
|
|
@@ -38867,6 +38970,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38867
38970
|
if (!rule) continue;
|
|
38868
38971
|
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38869
38972
|
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38973
|
+
if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
|
|
38870
38974
|
if (rule.scan !== void 0) continue;
|
|
38871
38975
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38872
38976
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38911,23 +39015,23 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38911
39015
|
}
|
|
38912
39016
|
};
|
|
38913
39017
|
};
|
|
38914
|
-
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
39018
|
+
const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
|
|
38915
39019
|
const decodeFileDiagnostics = (raw) => {
|
|
38916
39020
|
if (!Array.isArray(raw)) return null;
|
|
38917
39021
|
try {
|
|
38918
|
-
for (const entry of raw) validateDiagnostic(entry);
|
|
39022
|
+
for (const entry of raw) validateDiagnostic$1(entry);
|
|
38919
39023
|
return raw;
|
|
38920
39024
|
} catch {
|
|
38921
39025
|
return null;
|
|
38922
39026
|
}
|
|
38923
39027
|
};
|
|
38924
|
-
const emptyCache = () => ({
|
|
39028
|
+
const emptyCache$1 = () => ({
|
|
38925
39029
|
version: 1,
|
|
38926
39030
|
rulesets: {}
|
|
38927
39031
|
});
|
|
38928
|
-
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39032
|
+
const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
|
|
38929
39033
|
const entries = /* @__PURE__ */ new Map();
|
|
38930
|
-
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39034
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38931
39035
|
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38932
39036
|
const bucket = persisted.rulesets[rulesetHash];
|
|
38933
39037
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
@@ -38939,7 +39043,7 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
|
38939
39043
|
};
|
|
38940
39044
|
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38941
39045
|
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38942
|
-
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
39046
|
+
const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
|
|
38943
39047
|
return {
|
|
38944
39048
|
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38945
39049
|
store: (fileKey, diagnostics) => {
|
|
@@ -38947,7 +39051,7 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38947
39051
|
entries.set(fileKey, diagnostics);
|
|
38948
39052
|
},
|
|
38949
39053
|
persist: () => {
|
|
38950
|
-
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39054
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38951
39055
|
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38952
39056
|
const existingBucket = rulesets[rulesetHash];
|
|
38953
39057
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
@@ -38968,6 +39072,135 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38968
39072
|
}
|
|
38969
39073
|
};
|
|
38970
39074
|
};
|
|
39075
|
+
const ABSENT_CONTENT_ANSWER = "absent";
|
|
39076
|
+
/**
|
|
39077
|
+
* Answers dependency probes for the sidecar lint cache — used both when
|
|
39078
|
+
* STORING an entry (turn the collector's probe paths into answers) and when
|
|
39079
|
+
* VERIFYING one (re-answer each stored probe against the current tree).
|
|
39080
|
+
* Store and verify must agree exactly, so both go through this one resolver.
|
|
39081
|
+
*
|
|
39082
|
+
* - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
|
|
39083
|
+
* (missing, a directory, permission failure). Source files reuse the
|
|
39084
|
+
* hashes the cache-partition loop already computed
|
|
39085
|
+
* (`contentHashByRelativePath`); everything else (package.json,
|
|
39086
|
+
* tsconfig, ignored files) is hashed on demand.
|
|
39087
|
+
* - `exists` — the existence class a module-resolution candidate probe
|
|
39088
|
+
* sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
|
|
39089
|
+
* `statSync` follows symlinks, matching the resolvers).
|
|
39090
|
+
*
|
|
39091
|
+
* Answers are memoized per scan: the filesystem is frozen for the run, and
|
|
39092
|
+
* hot dependency paths (a barrel imported by hundreds of files, the shared
|
|
39093
|
+
* tsconfig chain) repeat across many files' probe sets.
|
|
39094
|
+
*/
|
|
39095
|
+
const createSidecarProbeAnswerResolver = (input) => {
|
|
39096
|
+
const contentAnswerByPath = /* @__PURE__ */ new Map();
|
|
39097
|
+
const existsAnswerByPath = /* @__PURE__ */ new Map();
|
|
39098
|
+
const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
|
|
39099
|
+
const contentAnswer = (relativePath) => {
|
|
39100
|
+
const memoized = contentAnswerByPath.get(relativePath);
|
|
39101
|
+
if (memoized !== void 0) return memoized;
|
|
39102
|
+
const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
|
|
39103
|
+
contentAnswerByPath.set(relativePath, answer);
|
|
39104
|
+
return answer;
|
|
39105
|
+
};
|
|
39106
|
+
const existsAnswer = (relativePath) => {
|
|
39107
|
+
const memoized = existsAnswerByPath.get(relativePath);
|
|
39108
|
+
if (memoized !== void 0) return memoized;
|
|
39109
|
+
let answer;
|
|
39110
|
+
try {
|
|
39111
|
+
const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
|
|
39112
|
+
answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
|
|
39113
|
+
} catch {
|
|
39114
|
+
answer = "none";
|
|
39115
|
+
}
|
|
39116
|
+
existsAnswerByPath.set(relativePath, answer);
|
|
39117
|
+
return answer;
|
|
39118
|
+
};
|
|
39119
|
+
return {
|
|
39120
|
+
answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
|
|
39121
|
+
toRelativePath
|
|
39122
|
+
};
|
|
39123
|
+
};
|
|
39124
|
+
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
39125
|
+
const isProbeKind = (value) => value === "content" || value === "exists";
|
|
39126
|
+
const decodeSidecarFileEntry = (raw) => {
|
|
39127
|
+
if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
|
|
39128
|
+
const probes = [];
|
|
39129
|
+
for (const rawProbe of raw.probes) {
|
|
39130
|
+
if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
|
|
39131
|
+
const [kind, probePath, answer] = rawProbe;
|
|
39132
|
+
if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
|
|
39133
|
+
probes.push({
|
|
39134
|
+
kind,
|
|
39135
|
+
path: probePath,
|
|
39136
|
+
answer
|
|
39137
|
+
});
|
|
39138
|
+
}
|
|
39139
|
+
try {
|
|
39140
|
+
for (const entry of raw.diagnostics) validateDiagnostic(entry);
|
|
39141
|
+
} catch {
|
|
39142
|
+
return null;
|
|
39143
|
+
}
|
|
39144
|
+
return {
|
|
39145
|
+
probes,
|
|
39146
|
+
diagnostics: raw.diagnostics
|
|
39147
|
+
};
|
|
39148
|
+
};
|
|
39149
|
+
const encodeSidecarFileEntry = (entry) => ({
|
|
39150
|
+
probes: entry.probes.map((probe) => [
|
|
39151
|
+
probe.kind,
|
|
39152
|
+
probe.path,
|
|
39153
|
+
probe.answer
|
|
39154
|
+
]),
|
|
39155
|
+
diagnostics: entry.diagnostics
|
|
39156
|
+
});
|
|
39157
|
+
const emptyCache = () => ({
|
|
39158
|
+
version: 1,
|
|
39159
|
+
rulesets: {}
|
|
39160
|
+
});
|
|
39161
|
+
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39162
|
+
const entries = /* @__PURE__ */ new Map();
|
|
39163
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39164
|
+
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
39165
|
+
const bucket = persisted.rulesets[rulesetHash];
|
|
39166
|
+
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39167
|
+
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
39168
|
+
const decoded = decodeSidecarFileEntry(rawEntry);
|
|
39169
|
+
if (decoded !== null) entries.set(fileKey, decoded);
|
|
39170
|
+
}
|
|
39171
|
+
return entries;
|
|
39172
|
+
};
|
|
39173
|
+
const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
39174
|
+
const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
|
|
39175
|
+
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
39176
|
+
return {
|
|
39177
|
+
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
39178
|
+
store: (fileKey, entry) => {
|
|
39179
|
+
entries.delete(fileKey);
|
|
39180
|
+
entries.set(fileKey, entry);
|
|
39181
|
+
},
|
|
39182
|
+
persist: () => {
|
|
39183
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39184
|
+
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39185
|
+
const existingBucket = rulesets[rulesetHash];
|
|
39186
|
+
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39187
|
+
const ourFiles = {};
|
|
39188
|
+
for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
|
|
39189
|
+
const cappedEntries = Object.entries({
|
|
39190
|
+
...existingFiles,
|
|
39191
|
+
...ourFiles
|
|
39192
|
+
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
39193
|
+
rulesets[rulesetHash] = {
|
|
39194
|
+
updatedAtMs: Date.now(),
|
|
39195
|
+
files: Object.fromEntries(cappedEntries)
|
|
39196
|
+
};
|
|
39197
|
+
atomicWriteJson(cacheFilePath, {
|
|
39198
|
+
version: 1,
|
|
39199
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39200
|
+
});
|
|
39201
|
+
}
|
|
39202
|
+
};
|
|
39203
|
+
};
|
|
38971
39204
|
const bundledRequire = createRequire(import.meta.url);
|
|
38972
39205
|
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
38973
39206
|
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
@@ -39967,13 +40200,6 @@ const validateRuleRegistration = () => {
|
|
|
39967
40200
|
].filter((entry) => entry !== null).join("; ");
|
|
39968
40201
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
39969
40202
|
};
|
|
39970
|
-
const hashFileContents = (filePath) => {
|
|
39971
|
-
try {
|
|
39972
|
-
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39973
|
-
} catch {
|
|
39974
|
-
return null;
|
|
39975
|
-
}
|
|
39976
|
-
};
|
|
39977
40203
|
/**
|
|
39978
40204
|
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39979
40205
|
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
@@ -40055,6 +40281,106 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
40055
40281
|
NFS.closeSync(fileHandle);
|
|
40056
40282
|
}
|
|
40057
40283
|
};
|
|
40284
|
+
/**
|
|
40285
|
+
* Attributes diagnostics back to the file that produced them by the
|
|
40286
|
+
* normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
|
|
40287
|
+
* attributed — the path forms don't align, so the caller must skip its cache
|
|
40288
|
+
* store rather than risk caching a wrong empty result for a file that
|
|
40289
|
+
* actually had diagnostics.
|
|
40290
|
+
*/
|
|
40291
|
+
const attributeDiagnosticsToFiles = (diagnostics, files) => {
|
|
40292
|
+
const fileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40293
|
+
for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
|
|
40294
|
+
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40295
|
+
for (const diagnostic of diagnostics) {
|
|
40296
|
+
const file = fileByNormalizedPath.get(diagnostic.filePath);
|
|
40297
|
+
if (file === void 0) return null;
|
|
40298
|
+
const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
|
|
40299
|
+
fileDiagnostics.push(diagnostic);
|
|
40300
|
+
diagnosticsByFile.set(file, fileDiagnostics);
|
|
40301
|
+
}
|
|
40302
|
+
return diagnosticsByFile;
|
|
40303
|
+
};
|
|
40304
|
+
/**
|
|
40305
|
+
* Collects each file's cross-file dependency probes (via the plugin's
|
|
40306
|
+
* collectors) with their current answers. Fail-open per file: an unreadable /
|
|
40307
|
+
* unparseable / collector-failing file maps to `null` (no entry stored — it
|
|
40308
|
+
* re-lints next scan). The filesystem is frozen for the scan, so this can run
|
|
40309
|
+
* CONCURRENTLY with an oxlint child-process await — the caller starts it
|
|
40310
|
+
* unawaited before a lint pass and awaits it at store time, hiding the
|
|
40311
|
+
* in-process parse + resolution cost under the subprocess wait. The loop
|
|
40312
|
+
* yields on the cooperative time budget so the child's stdout handling (and
|
|
40313
|
+
* sibling fibers) keep flowing; each per-file collection is synchronous, so
|
|
40314
|
+
* the module-level probe recorder never sees interleaved use. Never rejects.
|
|
40315
|
+
*/
|
|
40316
|
+
const collectSidecarProbesForFiles = async (input) => {
|
|
40317
|
+
const buildProbes = (file) => {
|
|
40318
|
+
const absoluteFilePath = Path.resolve(input.rootDirectory, file);
|
|
40319
|
+
let trace;
|
|
40320
|
+
try {
|
|
40321
|
+
trace = collectCrossFileDependencyProbes({
|
|
40322
|
+
absoluteFilePath,
|
|
40323
|
+
sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
|
|
40324
|
+
ruleIds: input.boundedSidecarRuleIds
|
|
40325
|
+
});
|
|
40326
|
+
} catch {
|
|
40327
|
+
return null;
|
|
40328
|
+
}
|
|
40329
|
+
if (trace === null) return null;
|
|
40330
|
+
const probes = [];
|
|
40331
|
+
for (const contentPath of trace.contentPaths) {
|
|
40332
|
+
const relativePath = input.probeAnswers.toRelativePath(contentPath);
|
|
40333
|
+
probes.push({
|
|
40334
|
+
kind: "content",
|
|
40335
|
+
path: relativePath,
|
|
40336
|
+
answer: input.probeAnswers.answerFor("content", relativePath)
|
|
40337
|
+
});
|
|
40338
|
+
}
|
|
40339
|
+
for (const existencePath of trace.existencePaths) {
|
|
40340
|
+
const relativePath = input.probeAnswers.toRelativePath(existencePath);
|
|
40341
|
+
probes.push({
|
|
40342
|
+
kind: "exists",
|
|
40343
|
+
path: relativePath,
|
|
40344
|
+
answer: input.probeAnswers.answerFor("exists", relativePath)
|
|
40345
|
+
});
|
|
40346
|
+
}
|
|
40347
|
+
return probes;
|
|
40348
|
+
};
|
|
40349
|
+
const probesByFile = /* @__PURE__ */ new Map();
|
|
40350
|
+
let collectSliceStartedAt = performance.now();
|
|
40351
|
+
for (const file of input.files) {
|
|
40352
|
+
probesByFile.set(file, buildProbes(file));
|
|
40353
|
+
if (performance.now() - collectSliceStartedAt >= 12) {
|
|
40354
|
+
await yieldToEventLoop();
|
|
40355
|
+
collectSliceStartedAt = performance.now();
|
|
40356
|
+
}
|
|
40357
|
+
}
|
|
40358
|
+
return probesByFile;
|
|
40359
|
+
};
|
|
40360
|
+
/**
|
|
40361
|
+
* Stores each freshly-linted file's probe set + cross-file diagnostics so
|
|
40362
|
+
* the next scan can replay instead of re-linting.
|
|
40363
|
+
*/
|
|
40364
|
+
const storeSidecarEntries = (input) => {
|
|
40365
|
+
let didStoreAnyEntry = false;
|
|
40366
|
+
for (const pass of input.storablePasses) {
|
|
40367
|
+
if (!pass.isTrusted) continue;
|
|
40368
|
+
const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
|
|
40369
|
+
if (diagnosticsByFile === null) continue;
|
|
40370
|
+
for (const file of pass.files) {
|
|
40371
|
+
const cacheKey = input.cacheKeyByFile.get(file);
|
|
40372
|
+
if (cacheKey === void 0) continue;
|
|
40373
|
+
const probes = pass.probesByFile.get(file);
|
|
40374
|
+
if (probes === void 0 || probes === null) continue;
|
|
40375
|
+
input.sidecarCache.store(cacheKey, {
|
|
40376
|
+
probes,
|
|
40377
|
+
diagnostics: diagnosticsByFile.get(file) ?? []
|
|
40378
|
+
});
|
|
40379
|
+
didStoreAnyEntry = true;
|
|
40380
|
+
}
|
|
40381
|
+
}
|
|
40382
|
+
if (didStoreAnyEntry) input.sidecarCache.persist();
|
|
40383
|
+
};
|
|
40058
40384
|
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
40059
40385
|
/**
|
|
40060
40386
|
* Detects an oxlint config-load crash caused by the optional
|
|
@@ -40096,7 +40422,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
40096
40422
|
* 6. always restore disable directives + clean up the temp dir
|
|
40097
40423
|
*/
|
|
40098
40424
|
const runOxlint = async (options) => {
|
|
40099
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
40425
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
40100
40426
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40101
40427
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40102
40428
|
validateRuleRegistration();
|
|
@@ -40114,7 +40440,8 @@ const runOxlint = async (options) => {
|
|
|
40114
40440
|
severityControls,
|
|
40115
40441
|
userPlugins,
|
|
40116
40442
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
40117
|
-
ruleSelection: overrides.ruleSelection
|
|
40443
|
+
ruleSelection: overrides.ruleSelection,
|
|
40444
|
+
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
40118
40445
|
});
|
|
40119
40446
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
40120
40447
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -40217,15 +40544,18 @@ const runOxlint = async (options) => {
|
|
|
40217
40544
|
});
|
|
40218
40545
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
40219
40546
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40547
|
+
const contentHashByRelativePath = /* @__PURE__ */ new Map();
|
|
40220
40548
|
const missFiles = [];
|
|
40221
40549
|
const replayedDiagnostics = [];
|
|
40222
40550
|
let hashSliceStartedAt = performance.now();
|
|
40223
40551
|
for (const candidateFile of candidateFiles) {
|
|
40224
|
-
const
|
|
40552
|
+
const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
|
|
40553
|
+
const contentHash = hashFileContents(absoluteCandidatePath);
|
|
40225
40554
|
if (contentHash === null) {
|
|
40226
40555
|
missFiles.push(candidateFile);
|
|
40227
40556
|
continue;
|
|
40228
40557
|
}
|
|
40558
|
+
contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
|
|
40229
40559
|
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
40230
40560
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
40231
40561
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
@@ -40237,43 +40567,111 @@ const runOxlint = async (options) => {
|
|
|
40237
40567
|
}
|
|
40238
40568
|
}
|
|
40239
40569
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40570
|
+
const enabledSidecarRuleIds = Object.keys(buildConfig({
|
|
40571
|
+
extendsPaths: [],
|
|
40572
|
+
ruleSelection: "sidecar"
|
|
40573
|
+
}).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
|
|
40574
|
+
const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40575
|
+
const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
|
|
40576
|
+
const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40577
|
+
const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
|
|
40578
|
+
const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
|
|
40579
|
+
config: buildConfig({
|
|
40580
|
+
extendsPaths: [],
|
|
40581
|
+
ruleSelection: "sidecar",
|
|
40582
|
+
sidecarRuleIdFilter: boundedSidecarRuleIdSet
|
|
40583
|
+
}),
|
|
40584
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
40585
|
+
ignorePatterns: combinedPatterns,
|
|
40586
|
+
tsconfigContent
|
|
40587
|
+
})) : null;
|
|
40588
|
+
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
40589
|
+
rootDirectory,
|
|
40590
|
+
contentHashByRelativePath
|
|
40591
|
+
});
|
|
40592
|
+
const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40593
|
+
files: missFiles,
|
|
40594
|
+
rootDirectory,
|
|
40595
|
+
boundedSidecarRuleIds,
|
|
40596
|
+
probeAnswers
|
|
40597
|
+
});
|
|
40240
40598
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40241
40599
|
extendsPaths: [],
|
|
40242
40600
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40243
40601
|
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40244
40602
|
const missFileSet = new Set(missFiles);
|
|
40245
|
-
const
|
|
40603
|
+
const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
|
|
40604
|
+
const sidecarReplayedDiagnostics = [];
|
|
40605
|
+
const sidecarLintFiles = [];
|
|
40606
|
+
if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
|
|
40607
|
+
else {
|
|
40608
|
+
let verifySliceStartedAt = performance.now();
|
|
40609
|
+
for (const hitFile of hitFiles) {
|
|
40610
|
+
const cacheKey = cacheKeyByFile.get(hitFile);
|
|
40611
|
+
const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
|
|
40612
|
+
if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
|
|
40613
|
+
else sidecarLintFiles.push(hitFile);
|
|
40614
|
+
if (performance.now() - verifySliceStartedAt >= 12) {
|
|
40615
|
+
await yieldToEventLoop();
|
|
40616
|
+
verifySliceStartedAt = performance.now();
|
|
40617
|
+
}
|
|
40618
|
+
}
|
|
40619
|
+
}
|
|
40620
|
+
const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40621
|
+
files: sidecarLintFiles,
|
|
40622
|
+
rootDirectory,
|
|
40623
|
+
boundedSidecarRuleIds,
|
|
40624
|
+
probeAnswers
|
|
40625
|
+
});
|
|
40626
|
+
const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
|
|
40627
|
+
if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
|
|
40628
|
+
const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40246
40629
|
extendsPaths: [],
|
|
40247
|
-
ruleSelection: "sidecar"
|
|
40248
|
-
|
|
40630
|
+
ruleSelection: "sidecar",
|
|
40631
|
+
...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
|
|
40632
|
+
}), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
|
|
40633
|
+
const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
|
|
40634
|
+
extendsPaths: [],
|
|
40635
|
+
ruleSelection: "sidecar",
|
|
40636
|
+
sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
|
|
40637
|
+
}), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
|
|
40638
|
+
diagnostics: [],
|
|
40639
|
+
didDropReactHooksJsPlugin: false,
|
|
40640
|
+
hadPartialFailure: false
|
|
40641
|
+
};
|
|
40249
40642
|
const freshCacheableDiagnostics = [];
|
|
40250
40643
|
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40251
40644
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40252
|
-
|
|
40253
|
-
|
|
40254
|
-
|
|
40255
|
-
let isAttributionSound = true;
|
|
40256
|
-
for (const diagnostic of freshCacheableDiagnostics) {
|
|
40257
|
-
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
40258
|
-
if (missFile === void 0) {
|
|
40259
|
-
isAttributionSound = false;
|
|
40260
|
-
break;
|
|
40261
|
-
}
|
|
40262
|
-
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
40263
|
-
fileDiagnostics.push(diagnostic);
|
|
40264
|
-
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
40265
|
-
}
|
|
40266
|
-
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
40645
|
+
if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
|
|
40646
|
+
const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
|
|
40647
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
|
|
40267
40648
|
for (const missFile of missFiles) {
|
|
40268
40649
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40269
40650
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
40270
40651
|
}
|
|
40271
40652
|
cache.persist();
|
|
40272
40653
|
}
|
|
40654
|
+
if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
|
|
40655
|
+
sidecarCache,
|
|
40656
|
+
cacheKeyByFile,
|
|
40657
|
+
storablePasses: [{
|
|
40658
|
+
files: sidecarLintFiles,
|
|
40659
|
+
diagnostics: boundedSidecarResult.diagnostics,
|
|
40660
|
+
isTrusted: !boundedSidecarResult.hadPartialFailure,
|
|
40661
|
+
probesByFile: await sidecarProbesTask
|
|
40662
|
+
}, {
|
|
40663
|
+
files: missFiles,
|
|
40664
|
+
diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
|
|
40665
|
+
isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
|
|
40666
|
+
probesByFile: await missProbesTask
|
|
40667
|
+
}]
|
|
40668
|
+
});
|
|
40273
40669
|
return dedupeDiagnostics([
|
|
40274
40670
|
...replayedDiagnostics,
|
|
40275
40671
|
...fullResult.diagnostics,
|
|
40276
|
-
...
|
|
40672
|
+
...sidecarReplayedDiagnostics,
|
|
40673
|
+
...boundedSidecarResult.diagnostics,
|
|
40674
|
+
...unboundedSidecarResult.diagnostics
|
|
40277
40675
|
]);
|
|
40278
40676
|
}
|
|
40279
40677
|
const baseArgs = makeBaseArgs(configPath);
|
|
@@ -40371,6 +40769,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40371
40769
|
const concurrency = yield* OxlintConcurrency;
|
|
40372
40770
|
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40373
40771
|
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
40772
|
+
const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
|
|
40374
40773
|
const collectedFailures = [];
|
|
40375
40774
|
const diagnostics = yield* tryPromise({
|
|
40376
40775
|
try: (signal) => runOxlint({
|
|
@@ -40389,7 +40788,9 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40389
40788
|
},
|
|
40390
40789
|
onFileProgress: input.onFileProgress,
|
|
40391
40790
|
perFileLintCacheEnabled,
|
|
40791
|
+
sidecarLintCacheEnabled,
|
|
40392
40792
|
onCacheStats: input.onCacheStats,
|
|
40793
|
+
onSidecarStats: input.onSidecarStats,
|
|
40393
40794
|
spawnTimeoutMs,
|
|
40394
40795
|
outputMaxBytes,
|
|
40395
40796
|
concurrency,
|
|
@@ -40811,6 +41212,20 @@ const writeCachedSocketBody = (cacheFile, body) => {
|
|
|
40811
41212
|
}));
|
|
40812
41213
|
} catch {}
|
|
40813
41214
|
};
|
|
41215
|
+
const pruneExpiredSocketCache = (cacheDirectory) => {
|
|
41216
|
+
try {
|
|
41217
|
+
const supplyChainCacheDirectory = Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR);
|
|
41218
|
+
const expiryThresholdMs = Date.now() - SUPPLY_CHAIN_CACHE_TTL_MS;
|
|
41219
|
+
for (const entryName of NFS.readdirSync(supplyChainCacheDirectory)) {
|
|
41220
|
+
const entryPath = Path.join(supplyChainCacheDirectory, entryName);
|
|
41221
|
+
try {
|
|
41222
|
+
if (NFS.statSync(entryPath).mtimeMs < expiryThresholdMs) NFS.rmSync(entryPath);
|
|
41223
|
+
} catch {
|
|
41224
|
+
continue;
|
|
41225
|
+
}
|
|
41226
|
+
}
|
|
41227
|
+
} catch {}
|
|
41228
|
+
};
|
|
40814
41229
|
const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
|
|
40815
41230
|
const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
|
|
40816
41231
|
if (cacheFile !== null) {
|
|
@@ -40938,6 +41353,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40938
41353
|
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
40939
41354
|
if (dependencies.length === 0) return [];
|
|
40940
41355
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41356
|
+
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
40941
41357
|
const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
|
|
40942
41358
|
const diagnostics = [];
|
|
40943
41359
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
@@ -41199,6 +41615,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41199
41615
|
let lastReportedTotalFileCount = 0;
|
|
41200
41616
|
let lintCacheHitFileCount = null;
|
|
41201
41617
|
let lintCacheTotalFileCount = null;
|
|
41618
|
+
let lintSidecarReplayedFileCount = null;
|
|
41619
|
+
let lintSidecarTotalFileCount = null;
|
|
41202
41620
|
let deadCodeCacheHit = null;
|
|
41203
41621
|
let deadCodeSummaryCacheHits = null;
|
|
41204
41622
|
let deadCodeSummaryCacheMisses = null;
|
|
@@ -41221,6 +41639,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41221
41639
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
41222
41640
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
41223
41641
|
},
|
|
41642
|
+
onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
|
|
41643
|
+
lintSidecarReplayedFileCount = sidecarReplayedFileCount;
|
|
41644
|
+
lintSidecarTotalFileCount = sidecarConsideredFileCount;
|
|
41645
|
+
},
|
|
41224
41646
|
deadlineEpochMs: input.deadlineEpochMs
|
|
41225
41647
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41226
41648
|
yield* set(lintFailure, {
|
|
@@ -41331,6 +41753,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41331
41753
|
securityScanFailed,
|
|
41332
41754
|
lintCacheHitFileCount,
|
|
41333
41755
|
lintCacheTotalFileCount,
|
|
41756
|
+
lintSidecarReplayedFileCount,
|
|
41757
|
+
lintSidecarTotalFileCount,
|
|
41334
41758
|
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41335
41759
|
deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
|
|
41336
41760
|
deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
|
|
@@ -41646,11 +42070,9 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
41646
42070
|
const parts = [`v=${version}`];
|
|
41647
42071
|
let directory = projectDirectory;
|
|
41648
42072
|
for (;;) {
|
|
41649
|
-
for (const filename of CONFIG_FINGERPRINT_FILENAMES)
|
|
41650
|
-
const
|
|
41651
|
-
parts.push(`${directory}/${filename}=${
|
|
41652
|
-
} catch {
|
|
41653
|
-
continue;
|
|
42073
|
+
for (const filename of CONFIG_FINGERPRINT_FILENAMES) {
|
|
42074
|
+
const contentHash = hashFileContents(path.join(directory, filename));
|
|
42075
|
+
if (contentHash !== null) parts.push(`${directory}/${filename}=${contentHash}`);
|
|
41654
42076
|
}
|
|
41655
42077
|
const parent = path.dirname(directory);
|
|
41656
42078
|
if (parent === directory) break;
|
|
@@ -43713,5 +44135,5 @@ const startLanguageServer = () => {
|
|
|
43713
44135
|
};
|
|
43714
44136
|
//#endregion
|
|
43715
44137
|
export { startLanguageServer };
|
|
43716
|
-
!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]="
|
|
43717
|
-
//# debugId=
|
|
44138
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="35802bcf-f0e9-56fb-8cd9-6aee4dd99007")}catch(e){}}();
|
|
44139
|
+
//# debugId=35802bcf-f0e9-56fb-8cd9-6aee4dd99007
|