react-doctor 0.6.3-dev.8c004f0 → 0.6.3-dev.bdf8074
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 +803 -360
- package/dist/index.d.ts +18 -0
- package/dist/index.js +433 -81
- package/dist/lsp.js +433 -81
- package/package.json +2 -2
package/dist/lsp.js
CHANGED
|
@@ -8,13 +8,14 @@ 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";
|
|
15
15
|
import { createJiti } from "jiti";
|
|
16
16
|
import * as Crypto from "node:crypto";
|
|
17
17
|
import crypto, { createHash } from "node:crypto";
|
|
18
|
+
import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
|
|
18
19
|
import { gzipSync } from "node:zlib";
|
|
19
20
|
import { CodeActionKind, CodeActionTriggerKind, DidChangeWatchedFilesNotification, DocumentDiagnosticReportKind, FileChangeType, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/node.js";
|
|
20
21
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
@@ -32986,7 +32987,10 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
|
32986
32987
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
32987
32988
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32988
32989
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32990
|
+
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32991
|
+
const CORE_PACKAGE_VERSION = "0.6.3";
|
|
32989
32992
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32993
|
+
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32990
32994
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
32991
32995
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
32992
32996
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
@@ -35648,6 +35652,29 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
|
|
|
35648
35652
|
return true;
|
|
35649
35653
|
} }) {};
|
|
35650
35654
|
/**
|
|
35655
|
+
* Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
|
|
35656
|
+
* active. Defaults ON — warm rescans replay the cross-file rules' cached
|
|
35657
|
+
* diagnostics for every file whose dependency fingerprint (the probe set the
|
|
35658
|
+
* plugin's collectors recorded) still matches the tree, and re-lint only the
|
|
35659
|
+
* rest. Only reachable when the per-file lint cache itself is on (the
|
|
35660
|
+
* sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
|
|
35661
|
+
* `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
|
|
35662
|
+
*
|
|
35663
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
|
|
35664
|
+
* - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
|
|
35665
|
+
* per-file cache but run the always-fresh sidecar over every hit
|
|
35666
|
+
* (the pre-cache behavior).
|
|
35667
|
+
*
|
|
35668
|
+
* Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
|
|
35669
|
+
*/
|
|
35670
|
+
var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
|
|
35671
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35672
|
+
const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
|
|
35673
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35674
|
+
if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
|
|
35675
|
+
return true;
|
|
35676
|
+
} }) {};
|
|
35677
|
+
/**
|
|
35651
35678
|
* Whether the whole-project dead-code result cache
|
|
35652
35679
|
* (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
|
|
35653
35680
|
* whose inputs (source tree, manifests, configs, analyzer version) are
|
|
@@ -37323,38 +37350,9 @@ const failOpenReadJson = (filePath, fallback) => {
|
|
|
37323
37350
|
return fallback;
|
|
37324
37351
|
}
|
|
37325
37352
|
};
|
|
37326
|
-
const ANALYZED_FILE_EXTENSIONS = new Set(
|
|
37327
|
-
|
|
37328
|
-
|
|
37329
|
-
".js",
|
|
37330
|
-
".jsx",
|
|
37331
|
-
".mts",
|
|
37332
|
-
".mjs",
|
|
37333
|
-
".cts",
|
|
37334
|
-
".cjs",
|
|
37335
|
-
".mdx",
|
|
37336
|
-
".astro",
|
|
37337
|
-
".graphql",
|
|
37338
|
-
".gql",
|
|
37339
|
-
".css",
|
|
37340
|
-
".scss",
|
|
37341
|
-
".vue",
|
|
37342
|
-
".svelte"
|
|
37343
|
-
]);
|
|
37344
|
-
const ANALYZED_MANIFEST_NAMES = new Set([
|
|
37345
|
-
"package.json",
|
|
37346
|
-
"knip.json",
|
|
37347
|
-
"app.json",
|
|
37348
|
-
"pnpm-workspace.yaml",
|
|
37349
|
-
"lerna.json",
|
|
37350
|
-
"pnpm-lock.yaml",
|
|
37351
|
-
"package-lock.json",
|
|
37352
|
-
"yarn.lock",
|
|
37353
|
-
"bun.lock",
|
|
37354
|
-
"bun.lockb",
|
|
37355
|
-
"deno.lock",
|
|
37356
|
-
".gitignore"
|
|
37357
|
-
]);
|
|
37353
|
+
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
37354
|
+
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
37355
|
+
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
37358
37356
|
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
37359
37357
|
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
37360
37358
|
const collectAnalyzedFileFingerprints = (rootDirectory) => {
|
|
@@ -37380,6 +37378,7 @@ const resolveDeslopVersion = () => {
|
|
|
37380
37378
|
};
|
|
37381
37379
|
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
37382
37380
|
schemaVersion: 1,
|
|
37381
|
+
coreVersion: input.coreVersion,
|
|
37383
37382
|
deslopVersion: resolveDeslopVersion(),
|
|
37384
37383
|
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
37385
37384
|
entryPatterns: input.entryPatterns,
|
|
@@ -37387,10 +37386,10 @@ const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON
|
|
|
37387
37386
|
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
|
|
37388
37387
|
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
37389
37388
|
})).digest("hex");
|
|
37390
|
-
const validateDiagnostic$
|
|
37389
|
+
const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
|
|
37391
37390
|
const decodeCachedDiagnostics = (raw) => {
|
|
37392
37391
|
try {
|
|
37393
|
-
for (const entry of raw) validateDiagnostic$
|
|
37392
|
+
for (const entry of raw) validateDiagnostic$2(entry);
|
|
37394
37393
|
return raw;
|
|
37395
37394
|
} catch {
|
|
37396
37395
|
return null;
|
|
@@ -37544,6 +37543,14 @@ process.stdin.on("end", () => {
|
|
|
37544
37543
|
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
37545
37544
|
files: cycle.files,
|
|
37546
37545
|
})),
|
|
37546
|
+
...(result.incrementalCacheStats
|
|
37547
|
+
? {
|
|
37548
|
+
summaryCacheStats: {
|
|
37549
|
+
hits: result.incrementalCacheStats.summaryHits,
|
|
37550
|
+
misses: result.incrementalCacheStats.summaryMisses,
|
|
37551
|
+
},
|
|
37552
|
+
}
|
|
37553
|
+
: {}),
|
|
37547
37554
|
});
|
|
37548
37555
|
|
|
37549
37556
|
const serializeError = (error) =>
|
|
@@ -37567,6 +37574,9 @@ process.stdin.on("end", () => {
|
|
|
37567
37574
|
...(workerInput.ignorePatterns.length > 0
|
|
37568
37575
|
? { ignorePatterns: workerInput.ignorePatterns }
|
|
37569
37576
|
: {}),
|
|
37577
|
+
...(workerInput.incrementalCachePath
|
|
37578
|
+
? { incrementalCachePath: workerInput.incrementalCachePath }
|
|
37579
|
+
: {}),
|
|
37570
37580
|
// We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
|
|
37571
37581
|
// unusedDependencies, circularDependencies). Everything else deslop can compute
|
|
37572
37582
|
// is pure wasted work for us, and it's the bulk of the runtime:
|
|
@@ -37577,12 +37587,18 @@ process.stdin.on("end", () => {
|
|
|
37577
37587
|
// are the single most expensive pass — duplicate-block detection alone was
|
|
37578
37588
|
// ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
|
|
37579
37589
|
// speedup on a large repo.
|
|
37580
|
-
//
|
|
37590
|
+
// - reportRedundancy: the DRY-pattern detectors (duplicate types/constants,
|
|
37591
|
+
// simplifiable functions, identity wrappers, …) — ~120 ms of discarded
|
|
37592
|
+
// output per scan, and skipping them lets the incremental cache drop the
|
|
37593
|
+
// DRY-pattern summary fields (the largest slice of the cache file).
|
|
37594
|
+
// All are provably safe: the consumed graph findings are computed by their own
|
|
37581
37595
|
// detectors, independent of these passes (confirmed byte-identical on
|
|
37582
|
-
// excalidraw + mui-material + sentry
|
|
37583
|
-
//
|
|
37596
|
+
// excalidraw + mui-material + sentry; re-verified on sentry after the
|
|
37597
|
+
// reportRedundancy flip). tsConfigPath stays — the module resolver needs it
|
|
37598
|
+
// for path-alias resolution in the import graph.
|
|
37584
37599
|
semantic: { enabled: false },
|
|
37585
37600
|
reportCodeQuality: false,
|
|
37601
|
+
reportRedundancy: false,
|
|
37586
37602
|
};
|
|
37587
37603
|
const result = await analyze(defineConfig(config));
|
|
37588
37604
|
emit({ ok: true, result: normalizeResult(result) });
|
|
@@ -37666,13 +37682,23 @@ const parseCircularDependencies = (value) => {
|
|
|
37666
37682
|
}
|
|
37667
37683
|
return circularDependencies;
|
|
37668
37684
|
};
|
|
37685
|
+
const parseSummaryCacheStats = (value) => {
|
|
37686
|
+
if (!isRecord(value)) return void 0;
|
|
37687
|
+
if (typeof value.hits !== "number" || typeof value.misses !== "number") return void 0;
|
|
37688
|
+
return {
|
|
37689
|
+
hits: value.hits,
|
|
37690
|
+
misses: value.misses
|
|
37691
|
+
};
|
|
37692
|
+
};
|
|
37669
37693
|
const parseDeadCodeWorkerResult = (value) => {
|
|
37670
37694
|
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
|
|
37695
|
+
const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
|
|
37671
37696
|
return {
|
|
37672
37697
|
unusedFiles: parseUnusedFiles(value.unusedFiles),
|
|
37673
37698
|
unusedExports: parseUnusedExports(value.unusedExports),
|
|
37674
37699
|
unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
|
|
37675
|
-
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
37700
|
+
circularDependencies: parseCircularDependencies(value.circularDependencies),
|
|
37701
|
+
...summaryCacheStats ? { summaryCacheStats } : {}
|
|
37676
37702
|
};
|
|
37677
37703
|
};
|
|
37678
37704
|
const parseDeadCodeWorkerError = (value) => {
|
|
@@ -37793,13 +37819,15 @@ const checkDeadCode = async (options) => {
|
|
|
37793
37819
|
entryPatterns,
|
|
37794
37820
|
ignorePatterns,
|
|
37795
37821
|
tsConfigPath,
|
|
37796
|
-
deslopJsModuleSpecifier
|
|
37822
|
+
deslopJsModuleSpecifier,
|
|
37823
|
+
coreVersion: CORE_PACKAGE_VERSION
|
|
37797
37824
|
}) : null;
|
|
37798
37825
|
if (cacheKey !== null) {
|
|
37799
37826
|
const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
|
|
37800
37827
|
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
37801
37828
|
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
37802
37829
|
}
|
|
37830
|
+
const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
|
|
37803
37831
|
const spawnAndRun = () => {
|
|
37804
37832
|
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37805
37833
|
rootDirectory,
|
|
@@ -37807,10 +37835,12 @@ const checkDeadCode = async (options) => {
|
|
|
37807
37835
|
tsConfigPath,
|
|
37808
37836
|
ignorePatterns,
|
|
37809
37837
|
deslopJsModuleSpecifier,
|
|
37810
|
-
parseConcurrency: options.parseConcurrency
|
|
37838
|
+
parseConcurrency: options.parseConcurrency,
|
|
37839
|
+
incrementalCachePath
|
|
37811
37840
|
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37812
37841
|
};
|
|
37813
37842
|
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37843
|
+
if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
|
|
37814
37844
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37815
37845
|
const diagnostics = [];
|
|
37816
37846
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37978,7 +38008,8 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37978
38008
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37979
38009
|
abortSignal: signal,
|
|
37980
38010
|
cacheEnabled,
|
|
37981
|
-
onCacheOutcome: input.onCacheOutcome
|
|
38011
|
+
onCacheOutcome: input.onCacheOutcome,
|
|
38012
|
+
onSummaryCacheStats: input.onSummaryCacheStats
|
|
37982
38013
|
}),
|
|
37983
38014
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
37984
38015
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
@@ -38848,7 +38879,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38848
38879
|
}
|
|
38849
38880
|
return enabled;
|
|
38850
38881
|
};
|
|
38851
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38882
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
38852
38883
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38853
38884
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38854
38885
|
const jsPlugins = [];
|
|
@@ -38860,6 +38891,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38860
38891
|
if (!rule) continue;
|
|
38861
38892
|
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38862
38893
|
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38894
|
+
if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
|
|
38863
38895
|
if (rule.scan !== void 0) continue;
|
|
38864
38896
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38865
38897
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38904,23 +38936,23 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38904
38936
|
}
|
|
38905
38937
|
};
|
|
38906
38938
|
};
|
|
38907
|
-
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
38939
|
+
const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
|
|
38908
38940
|
const decodeFileDiagnostics = (raw) => {
|
|
38909
38941
|
if (!Array.isArray(raw)) return null;
|
|
38910
38942
|
try {
|
|
38911
|
-
for (const entry of raw) validateDiagnostic(entry);
|
|
38943
|
+
for (const entry of raw) validateDiagnostic$1(entry);
|
|
38912
38944
|
return raw;
|
|
38913
38945
|
} catch {
|
|
38914
38946
|
return null;
|
|
38915
38947
|
}
|
|
38916
38948
|
};
|
|
38917
|
-
const emptyCache = () => ({
|
|
38949
|
+
const emptyCache$1 = () => ({
|
|
38918
38950
|
version: 1,
|
|
38919
38951
|
rulesets: {}
|
|
38920
38952
|
});
|
|
38921
|
-
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
38953
|
+
const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
|
|
38922
38954
|
const entries = /* @__PURE__ */ new Map();
|
|
38923
|
-
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38955
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38924
38956
|
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38925
38957
|
const bucket = persisted.rulesets[rulesetHash];
|
|
38926
38958
|
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
@@ -38932,7 +38964,7 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
|
38932
38964
|
};
|
|
38933
38965
|
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38934
38966
|
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38935
|
-
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
38967
|
+
const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
|
|
38936
38968
|
return {
|
|
38937
38969
|
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38938
38970
|
store: (fileKey, diagnostics) => {
|
|
@@ -38940,7 +38972,7 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38940
38972
|
entries.set(fileKey, diagnostics);
|
|
38941
38973
|
},
|
|
38942
38974
|
persist: () => {
|
|
38943
|
-
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38975
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
|
|
38944
38976
|
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38945
38977
|
const existingBucket = rulesets[rulesetHash];
|
|
38946
38978
|
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
@@ -38961,6 +38993,142 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38961
38993
|
}
|
|
38962
38994
|
};
|
|
38963
38995
|
};
|
|
38996
|
+
const hashFileContents = (filePath) => {
|
|
38997
|
+
try {
|
|
38998
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
38999
|
+
} catch {
|
|
39000
|
+
return null;
|
|
39001
|
+
}
|
|
39002
|
+
};
|
|
39003
|
+
const ABSENT_CONTENT_ANSWER = "absent";
|
|
39004
|
+
/**
|
|
39005
|
+
* Answers dependency probes for the sidecar lint cache — used both when
|
|
39006
|
+
* STORING an entry (turn the collector's probe paths into answers) and when
|
|
39007
|
+
* VERIFYING one (re-answer each stored probe against the current tree).
|
|
39008
|
+
* Store and verify must agree exactly, so both go through this one resolver.
|
|
39009
|
+
*
|
|
39010
|
+
* - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
|
|
39011
|
+
* (missing, a directory, permission failure). Source files reuse the
|
|
39012
|
+
* hashes the cache-partition loop already computed
|
|
39013
|
+
* (`contentHashByRelativePath`); everything else (package.json,
|
|
39014
|
+
* tsconfig, ignored files) is hashed on demand.
|
|
39015
|
+
* - `exists` — the existence class a module-resolution candidate probe
|
|
39016
|
+
* sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
|
|
39017
|
+
* `statSync` follows symlinks, matching the resolvers).
|
|
39018
|
+
*
|
|
39019
|
+
* Answers are memoized per scan: the filesystem is frozen for the run, and
|
|
39020
|
+
* hot dependency paths (a barrel imported by hundreds of files, the shared
|
|
39021
|
+
* tsconfig chain) repeat across many files' probe sets.
|
|
39022
|
+
*/
|
|
39023
|
+
const createSidecarProbeAnswerResolver = (input) => {
|
|
39024
|
+
const contentAnswerByPath = /* @__PURE__ */ new Map();
|
|
39025
|
+
const existsAnswerByPath = /* @__PURE__ */ new Map();
|
|
39026
|
+
const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
|
|
39027
|
+
const contentAnswer = (relativePath) => {
|
|
39028
|
+
const memoized = contentAnswerByPath.get(relativePath);
|
|
39029
|
+
if (memoized !== void 0) return memoized;
|
|
39030
|
+
const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
|
|
39031
|
+
contentAnswerByPath.set(relativePath, answer);
|
|
39032
|
+
return answer;
|
|
39033
|
+
};
|
|
39034
|
+
const existsAnswer = (relativePath) => {
|
|
39035
|
+
const memoized = existsAnswerByPath.get(relativePath);
|
|
39036
|
+
if (memoized !== void 0) return memoized;
|
|
39037
|
+
let answer;
|
|
39038
|
+
try {
|
|
39039
|
+
const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
|
|
39040
|
+
answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
|
|
39041
|
+
} catch {
|
|
39042
|
+
answer = "none";
|
|
39043
|
+
}
|
|
39044
|
+
existsAnswerByPath.set(relativePath, answer);
|
|
39045
|
+
return answer;
|
|
39046
|
+
};
|
|
39047
|
+
return {
|
|
39048
|
+
answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
|
|
39049
|
+
toRelativePath
|
|
39050
|
+
};
|
|
39051
|
+
};
|
|
39052
|
+
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
39053
|
+
const isProbeKind = (value) => value === "content" || value === "exists";
|
|
39054
|
+
const decodeSidecarFileEntry = (raw) => {
|
|
39055
|
+
if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
|
|
39056
|
+
const probes = [];
|
|
39057
|
+
for (const rawProbe of raw.probes) {
|
|
39058
|
+
if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
|
|
39059
|
+
const [kind, probePath, answer] = rawProbe;
|
|
39060
|
+
if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
|
|
39061
|
+
probes.push({
|
|
39062
|
+
kind,
|
|
39063
|
+
path: probePath,
|
|
39064
|
+
answer
|
|
39065
|
+
});
|
|
39066
|
+
}
|
|
39067
|
+
try {
|
|
39068
|
+
for (const entry of raw.diagnostics) validateDiagnostic(entry);
|
|
39069
|
+
} catch {
|
|
39070
|
+
return null;
|
|
39071
|
+
}
|
|
39072
|
+
return {
|
|
39073
|
+
probes,
|
|
39074
|
+
diagnostics: raw.diagnostics
|
|
39075
|
+
};
|
|
39076
|
+
};
|
|
39077
|
+
const encodeSidecarFileEntry = (entry) => ({
|
|
39078
|
+
probes: entry.probes.map((probe) => [
|
|
39079
|
+
probe.kind,
|
|
39080
|
+
probe.path,
|
|
39081
|
+
probe.answer
|
|
39082
|
+
]),
|
|
39083
|
+
diagnostics: entry.diagnostics
|
|
39084
|
+
});
|
|
39085
|
+
const emptyCache = () => ({
|
|
39086
|
+
version: 1,
|
|
39087
|
+
rulesets: {}
|
|
39088
|
+
});
|
|
39089
|
+
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
39090
|
+
const entries = /* @__PURE__ */ new Map();
|
|
39091
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39092
|
+
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
39093
|
+
const bucket = persisted.rulesets[rulesetHash];
|
|
39094
|
+
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
39095
|
+
for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
|
|
39096
|
+
const decoded = decodeSidecarFileEntry(rawEntry);
|
|
39097
|
+
if (decoded !== null) entries.set(fileKey, decoded);
|
|
39098
|
+
}
|
|
39099
|
+
return entries;
|
|
39100
|
+
};
|
|
39101
|
+
const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
39102
|
+
const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
|
|
39103
|
+
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
39104
|
+
return {
|
|
39105
|
+
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
39106
|
+
store: (fileKey, entry) => {
|
|
39107
|
+
entries.delete(fileKey);
|
|
39108
|
+
entries.set(fileKey, entry);
|
|
39109
|
+
},
|
|
39110
|
+
persist: () => {
|
|
39111
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
39112
|
+
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
39113
|
+
const existingBucket = rulesets[rulesetHash];
|
|
39114
|
+
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
39115
|
+
const ourFiles = {};
|
|
39116
|
+
for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
|
|
39117
|
+
const cappedEntries = Object.entries({
|
|
39118
|
+
...existingFiles,
|
|
39119
|
+
...ourFiles
|
|
39120
|
+
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
39121
|
+
rulesets[rulesetHash] = {
|
|
39122
|
+
updatedAtMs: Date.now(),
|
|
39123
|
+
files: Object.fromEntries(cappedEntries)
|
|
39124
|
+
};
|
|
39125
|
+
atomicWriteJson(cacheFilePath, {
|
|
39126
|
+
version: 1,
|
|
39127
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
39128
|
+
});
|
|
39129
|
+
}
|
|
39130
|
+
};
|
|
39131
|
+
};
|
|
38964
39132
|
const bundledRequire = createRequire(import.meta.url);
|
|
38965
39133
|
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
38966
39134
|
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
@@ -39960,13 +40128,6 @@ const validateRuleRegistration = () => {
|
|
|
39960
40128
|
].filter((entry) => entry !== null).join("; ");
|
|
39961
40129
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
39962
40130
|
};
|
|
39963
|
-
const hashFileContents = (filePath) => {
|
|
39964
|
-
try {
|
|
39965
|
-
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39966
|
-
} catch {
|
|
39967
|
-
return null;
|
|
39968
|
-
}
|
|
39969
|
-
};
|
|
39970
40131
|
/**
|
|
39971
40132
|
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39972
40133
|
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
@@ -40048,6 +40209,106 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
40048
40209
|
NFS.closeSync(fileHandle);
|
|
40049
40210
|
}
|
|
40050
40211
|
};
|
|
40212
|
+
/**
|
|
40213
|
+
* Attributes diagnostics back to the file that produced them by the
|
|
40214
|
+
* normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
|
|
40215
|
+
* attributed — the path forms don't align, so the caller must skip its cache
|
|
40216
|
+
* store rather than risk caching a wrong empty result for a file that
|
|
40217
|
+
* actually had diagnostics.
|
|
40218
|
+
*/
|
|
40219
|
+
const attributeDiagnosticsToFiles = (diagnostics, files) => {
|
|
40220
|
+
const fileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40221
|
+
for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
|
|
40222
|
+
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40223
|
+
for (const diagnostic of diagnostics) {
|
|
40224
|
+
const file = fileByNormalizedPath.get(diagnostic.filePath);
|
|
40225
|
+
if (file === void 0) return null;
|
|
40226
|
+
const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
|
|
40227
|
+
fileDiagnostics.push(diagnostic);
|
|
40228
|
+
diagnosticsByFile.set(file, fileDiagnostics);
|
|
40229
|
+
}
|
|
40230
|
+
return diagnosticsByFile;
|
|
40231
|
+
};
|
|
40232
|
+
/**
|
|
40233
|
+
* Collects each file's cross-file dependency probes (via the plugin's
|
|
40234
|
+
* collectors) with their current answers. Fail-open per file: an unreadable /
|
|
40235
|
+
* unparseable / collector-failing file maps to `null` (no entry stored — it
|
|
40236
|
+
* re-lints next scan). The filesystem is frozen for the scan, so this can run
|
|
40237
|
+
* CONCURRENTLY with an oxlint child-process await — the caller starts it
|
|
40238
|
+
* unawaited before a lint pass and awaits it at store time, hiding the
|
|
40239
|
+
* in-process parse + resolution cost under the subprocess wait. The loop
|
|
40240
|
+
* yields on the cooperative time budget so the child's stdout handling (and
|
|
40241
|
+
* sibling fibers) keep flowing; each per-file collection is synchronous, so
|
|
40242
|
+
* the module-level probe recorder never sees interleaved use. Never rejects.
|
|
40243
|
+
*/
|
|
40244
|
+
const collectSidecarProbesForFiles = async (input) => {
|
|
40245
|
+
const buildProbes = (file) => {
|
|
40246
|
+
const absoluteFilePath = Path.resolve(input.rootDirectory, file);
|
|
40247
|
+
let trace;
|
|
40248
|
+
try {
|
|
40249
|
+
trace = collectCrossFileDependencyProbes({
|
|
40250
|
+
absoluteFilePath,
|
|
40251
|
+
sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
|
|
40252
|
+
ruleIds: input.boundedSidecarRuleIds
|
|
40253
|
+
});
|
|
40254
|
+
} catch {
|
|
40255
|
+
return null;
|
|
40256
|
+
}
|
|
40257
|
+
if (trace === null) return null;
|
|
40258
|
+
const probes = [];
|
|
40259
|
+
for (const contentPath of trace.contentPaths) {
|
|
40260
|
+
const relativePath = input.probeAnswers.toRelativePath(contentPath);
|
|
40261
|
+
probes.push({
|
|
40262
|
+
kind: "content",
|
|
40263
|
+
path: relativePath,
|
|
40264
|
+
answer: input.probeAnswers.answerFor("content", relativePath)
|
|
40265
|
+
});
|
|
40266
|
+
}
|
|
40267
|
+
for (const existencePath of trace.existencePaths) {
|
|
40268
|
+
const relativePath = input.probeAnswers.toRelativePath(existencePath);
|
|
40269
|
+
probes.push({
|
|
40270
|
+
kind: "exists",
|
|
40271
|
+
path: relativePath,
|
|
40272
|
+
answer: input.probeAnswers.answerFor("exists", relativePath)
|
|
40273
|
+
});
|
|
40274
|
+
}
|
|
40275
|
+
return probes;
|
|
40276
|
+
};
|
|
40277
|
+
const probesByFile = /* @__PURE__ */ new Map();
|
|
40278
|
+
let collectSliceStartedAt = performance.now();
|
|
40279
|
+
for (const file of input.files) {
|
|
40280
|
+
probesByFile.set(file, buildProbes(file));
|
|
40281
|
+
if (performance.now() - collectSliceStartedAt >= 12) {
|
|
40282
|
+
await yieldToEventLoop();
|
|
40283
|
+
collectSliceStartedAt = performance.now();
|
|
40284
|
+
}
|
|
40285
|
+
}
|
|
40286
|
+
return probesByFile;
|
|
40287
|
+
};
|
|
40288
|
+
/**
|
|
40289
|
+
* Stores each freshly-linted file's probe set + cross-file diagnostics so
|
|
40290
|
+
* the next scan can replay instead of re-linting.
|
|
40291
|
+
*/
|
|
40292
|
+
const storeSidecarEntries = (input) => {
|
|
40293
|
+
let didStoreAnyEntry = false;
|
|
40294
|
+
for (const pass of input.storablePasses) {
|
|
40295
|
+
if (!pass.isTrusted) continue;
|
|
40296
|
+
const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
|
|
40297
|
+
if (diagnosticsByFile === null) continue;
|
|
40298
|
+
for (const file of pass.files) {
|
|
40299
|
+
const cacheKey = input.cacheKeyByFile.get(file);
|
|
40300
|
+
if (cacheKey === void 0) continue;
|
|
40301
|
+
const probes = pass.probesByFile.get(file);
|
|
40302
|
+
if (probes === void 0 || probes === null) continue;
|
|
40303
|
+
input.sidecarCache.store(cacheKey, {
|
|
40304
|
+
probes,
|
|
40305
|
+
diagnostics: diagnosticsByFile.get(file) ?? []
|
|
40306
|
+
});
|
|
40307
|
+
didStoreAnyEntry = true;
|
|
40308
|
+
}
|
|
40309
|
+
}
|
|
40310
|
+
if (didStoreAnyEntry) input.sidecarCache.persist();
|
|
40311
|
+
};
|
|
40051
40312
|
const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
|
|
40052
40313
|
/**
|
|
40053
40314
|
* Detects an oxlint config-load crash caused by the optional
|
|
@@ -40089,7 +40350,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
40089
40350
|
* 6. always restore disable directives + clean up the temp dir
|
|
40090
40351
|
*/
|
|
40091
40352
|
const runOxlint = async (options) => {
|
|
40092
|
-
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;
|
|
40353
|
+
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;
|
|
40093
40354
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40094
40355
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40095
40356
|
validateRuleRegistration();
|
|
@@ -40107,7 +40368,8 @@ const runOxlint = async (options) => {
|
|
|
40107
40368
|
severityControls,
|
|
40108
40369
|
userPlugins,
|
|
40109
40370
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
40110
|
-
ruleSelection: overrides.ruleSelection
|
|
40371
|
+
ruleSelection: overrides.ruleSelection,
|
|
40372
|
+
sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
|
|
40111
40373
|
});
|
|
40112
40374
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
40113
40375
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -40210,15 +40472,18 @@ const runOxlint = async (options) => {
|
|
|
40210
40472
|
});
|
|
40211
40473
|
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
40212
40474
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40475
|
+
const contentHashByRelativePath = /* @__PURE__ */ new Map();
|
|
40213
40476
|
const missFiles = [];
|
|
40214
40477
|
const replayedDiagnostics = [];
|
|
40215
40478
|
let hashSliceStartedAt = performance.now();
|
|
40216
40479
|
for (const candidateFile of candidateFiles) {
|
|
40217
|
-
const
|
|
40480
|
+
const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
|
|
40481
|
+
const contentHash = hashFileContents(absoluteCandidatePath);
|
|
40218
40482
|
if (contentHash === null) {
|
|
40219
40483
|
missFiles.push(candidateFile);
|
|
40220
40484
|
continue;
|
|
40221
40485
|
}
|
|
40486
|
+
contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
|
|
40222
40487
|
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
40223
40488
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
40224
40489
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
@@ -40230,43 +40495,111 @@ const runOxlint = async (options) => {
|
|
|
40230
40495
|
}
|
|
40231
40496
|
}
|
|
40232
40497
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40498
|
+
const enabledSidecarRuleIds = Object.keys(buildConfig({
|
|
40499
|
+
extendsPaths: [],
|
|
40500
|
+
ruleSelection: "sidecar"
|
|
40501
|
+
}).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
|
|
40502
|
+
const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40503
|
+
const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
|
|
40504
|
+
const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
|
|
40505
|
+
const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
|
|
40506
|
+
const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
|
|
40507
|
+
config: buildConfig({
|
|
40508
|
+
extendsPaths: [],
|
|
40509
|
+
ruleSelection: "sidecar",
|
|
40510
|
+
sidecarRuleIdFilter: boundedSidecarRuleIdSet
|
|
40511
|
+
}),
|
|
40512
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
40513
|
+
ignorePatterns: combinedPatterns,
|
|
40514
|
+
tsconfigContent
|
|
40515
|
+
})) : null;
|
|
40516
|
+
const probeAnswers = createSidecarProbeAnswerResolver({
|
|
40517
|
+
rootDirectory,
|
|
40518
|
+
contentHashByRelativePath
|
|
40519
|
+
});
|
|
40520
|
+
const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40521
|
+
files: missFiles,
|
|
40522
|
+
rootDirectory,
|
|
40523
|
+
boundedSidecarRuleIds,
|
|
40524
|
+
probeAnswers
|
|
40525
|
+
});
|
|
40233
40526
|
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40234
40527
|
extendsPaths: [],
|
|
40235
40528
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40236
40529
|
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40237
40530
|
const missFileSet = new Set(missFiles);
|
|
40238
|
-
const
|
|
40531
|
+
const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
|
|
40532
|
+
const sidecarReplayedDiagnostics = [];
|
|
40533
|
+
const sidecarLintFiles = [];
|
|
40534
|
+
if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
|
|
40535
|
+
else {
|
|
40536
|
+
let verifySliceStartedAt = performance.now();
|
|
40537
|
+
for (const hitFile of hitFiles) {
|
|
40538
|
+
const cacheKey = cacheKeyByFile.get(hitFile);
|
|
40539
|
+
const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
|
|
40540
|
+
if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
|
|
40541
|
+
else sidecarLintFiles.push(hitFile);
|
|
40542
|
+
if (performance.now() - verifySliceStartedAt >= 12) {
|
|
40543
|
+
await yieldToEventLoop();
|
|
40544
|
+
verifySliceStartedAt = performance.now();
|
|
40545
|
+
}
|
|
40546
|
+
}
|
|
40547
|
+
}
|
|
40548
|
+
const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
|
|
40549
|
+
files: sidecarLintFiles,
|
|
40550
|
+
rootDirectory,
|
|
40551
|
+
boundedSidecarRuleIds,
|
|
40552
|
+
probeAnswers
|
|
40553
|
+
});
|
|
40554
|
+
const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
|
|
40555
|
+
if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
|
|
40556
|
+
const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40239
40557
|
extendsPaths: [],
|
|
40240
|
-
ruleSelection: "sidecar"
|
|
40241
|
-
|
|
40558
|
+
ruleSelection: "sidecar",
|
|
40559
|
+
...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
|
|
40560
|
+
}), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
|
|
40561
|
+
const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
|
|
40562
|
+
extendsPaths: [],
|
|
40563
|
+
ruleSelection: "sidecar",
|
|
40564
|
+
sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
|
|
40565
|
+
}), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
|
|
40566
|
+
diagnostics: [],
|
|
40567
|
+
didDropReactHooksJsPlugin: false,
|
|
40568
|
+
hadPartialFailure: false
|
|
40569
|
+
};
|
|
40242
40570
|
const freshCacheableDiagnostics = [];
|
|
40243
40571
|
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40244
40572
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40245
|
-
|
|
40246
|
-
|
|
40247
|
-
|
|
40248
|
-
let isAttributionSound = true;
|
|
40249
|
-
for (const diagnostic of freshCacheableDiagnostics) {
|
|
40250
|
-
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
40251
|
-
if (missFile === void 0) {
|
|
40252
|
-
isAttributionSound = false;
|
|
40253
|
-
break;
|
|
40254
|
-
}
|
|
40255
|
-
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
40256
|
-
fileDiagnostics.push(diagnostic);
|
|
40257
|
-
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
40258
|
-
}
|
|
40259
|
-
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
40573
|
+
if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
|
|
40574
|
+
const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
|
|
40575
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
|
|
40260
40576
|
for (const missFile of missFiles) {
|
|
40261
40577
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40262
40578
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
40263
40579
|
}
|
|
40264
40580
|
cache.persist();
|
|
40265
40581
|
}
|
|
40582
|
+
if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
|
|
40583
|
+
sidecarCache,
|
|
40584
|
+
cacheKeyByFile,
|
|
40585
|
+
storablePasses: [{
|
|
40586
|
+
files: sidecarLintFiles,
|
|
40587
|
+
diagnostics: boundedSidecarResult.diagnostics,
|
|
40588
|
+
isTrusted: !boundedSidecarResult.hadPartialFailure,
|
|
40589
|
+
probesByFile: await sidecarProbesTask
|
|
40590
|
+
}, {
|
|
40591
|
+
files: missFiles,
|
|
40592
|
+
diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
|
|
40593
|
+
isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
|
|
40594
|
+
probesByFile: await missProbesTask
|
|
40595
|
+
}]
|
|
40596
|
+
});
|
|
40266
40597
|
return dedupeDiagnostics([
|
|
40267
40598
|
...replayedDiagnostics,
|
|
40268
40599
|
...fullResult.diagnostics,
|
|
40269
|
-
...
|
|
40600
|
+
...sidecarReplayedDiagnostics,
|
|
40601
|
+
...boundedSidecarResult.diagnostics,
|
|
40602
|
+
...unboundedSidecarResult.diagnostics
|
|
40270
40603
|
]);
|
|
40271
40604
|
}
|
|
40272
40605
|
const baseArgs = makeBaseArgs(configPath);
|
|
@@ -40364,6 +40697,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40364
40697
|
const concurrency = yield* OxlintConcurrency;
|
|
40365
40698
|
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40366
40699
|
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
40700
|
+
const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
|
|
40367
40701
|
const collectedFailures = [];
|
|
40368
40702
|
const diagnostics = yield* tryPromise({
|
|
40369
40703
|
try: (signal) => runOxlint({
|
|
@@ -40382,7 +40716,9 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
40382
40716
|
},
|
|
40383
40717
|
onFileProgress: input.onFileProgress,
|
|
40384
40718
|
perFileLintCacheEnabled,
|
|
40719
|
+
sidecarLintCacheEnabled,
|
|
40385
40720
|
onCacheStats: input.onCacheStats,
|
|
40721
|
+
onSidecarStats: input.onSidecarStats,
|
|
40386
40722
|
spawnTimeoutMs,
|
|
40387
40723
|
outputMaxBytes,
|
|
40388
40724
|
concurrency,
|
|
@@ -41160,6 +41496,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41160
41496
|
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
|
|
41161
41497
|
onCacheOutcome: (didHitCache) => {
|
|
41162
41498
|
deadCodeCacheHit = didHitCache;
|
|
41499
|
+
},
|
|
41500
|
+
onSummaryCacheStats: (stats) => {
|
|
41501
|
+
deadCodeSummaryCacheHits = stats.hits;
|
|
41502
|
+
deadCodeSummaryCacheMisses = stats.misses;
|
|
41163
41503
|
}
|
|
41164
41504
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41165
41505
|
yield* set(deadCodeFailure, {
|
|
@@ -41188,7 +41528,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41188
41528
|
let lastReportedTotalFileCount = 0;
|
|
41189
41529
|
let lintCacheHitFileCount = null;
|
|
41190
41530
|
let lintCacheTotalFileCount = null;
|
|
41531
|
+
let lintSidecarReplayedFileCount = null;
|
|
41532
|
+
let lintSidecarTotalFileCount = null;
|
|
41191
41533
|
let deadCodeCacheHit = null;
|
|
41534
|
+
let deadCodeSummaryCacheHits = null;
|
|
41535
|
+
let deadCodeSummaryCacheMisses = null;
|
|
41192
41536
|
const baseLintStream = linterService.run({
|
|
41193
41537
|
rootDirectory: scanDirectory,
|
|
41194
41538
|
project,
|
|
@@ -41208,6 +41552,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41208
41552
|
lintCacheHitFileCount = cacheHitFileCount;
|
|
41209
41553
|
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
41210
41554
|
},
|
|
41555
|
+
onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
|
|
41556
|
+
lintSidecarReplayedFileCount = sidecarReplayedFileCount;
|
|
41557
|
+
lintSidecarTotalFileCount = sidecarConsideredFileCount;
|
|
41558
|
+
},
|
|
41211
41559
|
deadlineEpochMs: input.deadlineEpochMs
|
|
41212
41560
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
41213
41561
|
yield* set(lintFailure, {
|
|
@@ -41318,7 +41666,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
41318
41666
|
securityScanFailed,
|
|
41319
41667
|
lintCacheHitFileCount,
|
|
41320
41668
|
lintCacheTotalFileCount,
|
|
41669
|
+
lintSidecarReplayedFileCount,
|
|
41670
|
+
lintSidecarTotalFileCount,
|
|
41321
41671
|
deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
|
|
41672
|
+
deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
|
|
41673
|
+
deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
|
|
41322
41674
|
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
41323
41675
|
};
|
|
41324
41676
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
@@ -43698,5 +44050,5 @@ const startLanguageServer = () => {
|
|
|
43698
44050
|
};
|
|
43699
44051
|
//#endregion
|
|
43700
44052
|
export { startLanguageServer };
|
|
43701
|
-
!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]="
|
|
43702
|
-
//# debugId=
|
|
44053
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="17a3f533-8248-511a-859b-584197cefba6")}catch(e){}}();
|
|
44054
|
+
//# debugId=17a3f533-8248-511a-859b-584197cefba6
|