react-doctor 0.6.3-dev.e257a5e → 0.6.3-dev.ea9a775

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="fb390a00-3616-5547-b572-1ce120b0e1a2")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b55ef8b2-8e9d-512b-81de-a01f6ce3e60e")}catch(e){}}();
3
3
  import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import * as NFS from "node:fs";
@@ -9,7 +9,7 @@ import path from "node:path";
9
9
  import * as NodeChildProcess from "node:child_process";
10
10
  import { execFileSync, spawn, spawnSync } from "node:child_process";
11
11
  import * as ts from "typescript";
12
- import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
12
+ import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
13
13
  import * as OS from "node:os";
14
14
  import os from "node:os";
15
15
  import { parseJSON5 } from "confbox";
@@ -18,6 +18,7 @@ import { fileURLToPath } from "node:url";
18
18
  import { createJiti } from "jiti";
19
19
  import * as Crypto from "node:crypto";
20
20
  import crypto, { createHash } from "node:crypto";
21
+ import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
21
22
  import { gzipSync } from "node:zlib";
22
23
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
23
24
  /**
@@ -32927,7 +32928,10 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
32927
32928
  const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
32928
32929
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
32929
32930
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
32931
+ const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
32932
+ const CORE_PACKAGE_VERSION = "0.6.3";
32930
32933
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
32934
+ const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
32931
32935
  const SUPPLY_CHAIN_PLUGIN = "socket";
32932
32936
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
32933
32937
  const SUPPLY_CHAIN_CATEGORY = "Security";
@@ -35615,6 +35619,29 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
35615
35619
  return true;
35616
35620
  } }) {};
35617
35621
  /**
35622
+ * Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
35623
+ * active. Defaults ON — warm rescans replay the cross-file rules' cached
35624
+ * diagnostics for every file whose dependency fingerprint (the probe set the
35625
+ * plugin's collectors recorded) still matches the tree, and re-lint only the
35626
+ * rest. Only reachable when the per-file lint cache itself is on (the
35627
+ * sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
35628
+ * `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
35629
+ *
35630
+ * - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
35631
+ * - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
35632
+ * per-file cache but run the always-fresh sidecar over every hit
35633
+ * (the pre-cache behavior).
35634
+ *
35635
+ * Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
35636
+ */
35637
+ var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
35638
+ const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
35639
+ const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
35640
+ if (CACHE_DISABLED_VALUES.has(noCache)) return false;
35641
+ if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
35642
+ return true;
35643
+ } }) {};
35644
+ /**
35618
35645
  * Whether the whole-project dead-code result cache
35619
35646
  * (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
35620
35647
  * whose inputs (source tree, manifests, configs, analyzer version) are
@@ -37338,38 +37365,9 @@ const failOpenReadJson = (filePath, fallback) => {
37338
37365
  return fallback;
37339
37366
  }
37340
37367
  };
37341
- const ANALYZED_FILE_EXTENSIONS = new Set([
37342
- ".ts",
37343
- ".tsx",
37344
- ".js",
37345
- ".jsx",
37346
- ".mts",
37347
- ".mjs",
37348
- ".cts",
37349
- ".cjs",
37350
- ".mdx",
37351
- ".astro",
37352
- ".graphql",
37353
- ".gql",
37354
- ".css",
37355
- ".scss",
37356
- ".vue",
37357
- ".svelte"
37358
- ]);
37359
- const ANALYZED_MANIFEST_NAMES = new Set([
37360
- "package.json",
37361
- "knip.json",
37362
- "app.json",
37363
- "pnpm-workspace.yaml",
37364
- "lerna.json",
37365
- "pnpm-lock.yaml",
37366
- "package-lock.json",
37367
- "yarn.lock",
37368
- "bun.lock",
37369
- "bun.lockb",
37370
- "deno.lock",
37371
- ".gitignore"
37372
- ]);
37368
+ const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
37369
+ const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
37370
+ const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
37373
37371
  const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
37374
37372
  const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
37375
37373
  const collectAnalyzedFileFingerprints = (rootDirectory) => {
@@ -37395,6 +37393,7 @@ const resolveDeslopVersion = () => {
37395
37393
  };
37396
37394
  const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
37397
37395
  schemaVersion: 1,
37396
+ coreVersion: input.coreVersion,
37398
37397
  deslopVersion: resolveDeslopVersion(),
37399
37398
  deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
37400
37399
  entryPatterns: input.entryPatterns,
@@ -37402,10 +37401,10 @@ const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON
37402
37401
  tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
37403
37402
  files: collectAnalyzedFileFingerprints(input.rootDirectory)
37404
37403
  })).digest("hex");
37405
- const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
37404
+ const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
37406
37405
  const decodeCachedDiagnostics = (raw) => {
37407
37406
  try {
37408
- for (const entry of raw) validateDiagnostic$1(entry);
37407
+ for (const entry of raw) validateDiagnostic$2(entry);
37409
37408
  return raw;
37410
37409
  } catch {
37411
37410
  return null;
@@ -37559,6 +37558,14 @@ process.stdin.on("end", () => {
37559
37558
  circularDependencies: result.circularDependencies.map((cycle) => ({
37560
37559
  files: cycle.files,
37561
37560
  })),
37561
+ ...(result.incrementalCacheStats
37562
+ ? {
37563
+ summaryCacheStats: {
37564
+ hits: result.incrementalCacheStats.summaryHits,
37565
+ misses: result.incrementalCacheStats.summaryMisses,
37566
+ },
37567
+ }
37568
+ : {}),
37562
37569
  });
37563
37570
 
37564
37571
  const serializeError = (error) =>
@@ -37582,6 +37589,9 @@ process.stdin.on("end", () => {
37582
37589
  ...(workerInput.ignorePatterns.length > 0
37583
37590
  ? { ignorePatterns: workerInput.ignorePatterns }
37584
37591
  : {}),
37592
+ ...(workerInput.incrementalCachePath
37593
+ ? { incrementalCachePath: workerInput.incrementalCachePath }
37594
+ : {}),
37585
37595
  // We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
37586
37596
  // unusedDependencies, circularDependencies). Everything else deslop can compute
37587
37597
  // is pure wasted work for us, and it's the bulk of the runtime:
@@ -37592,12 +37602,18 @@ process.stdin.on("end", () => {
37592
37602
  // are the single most expensive pass — duplicate-block detection alone was
37593
37603
  // ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
37594
37604
  // speedup on a large repo.
37595
- // Both are provably safe: the consumed graph findings are computed by their own
37605
+ // - reportRedundancy: the DRY-pattern detectors (duplicate types/constants,
37606
+ // simplifiable functions, identity wrappers, …) — ~120 ms of discarded
37607
+ // output per scan, and skipping them lets the incremental cache drop the
37608
+ // DRY-pattern summary fields (the largest slice of the cache file).
37609
+ // All are provably safe: the consumed graph findings are computed by their own
37596
37610
  // detectors, independent of these passes (confirmed byte-identical on
37597
- // excalidraw + mui-material + sentry). tsConfigPath stays — the module resolver
37598
- // needs it for path-alias resolution in the import graph.
37611
+ // excalidraw + mui-material + sentry; re-verified on sentry after the
37612
+ // reportRedundancy flip). tsConfigPath stays — the module resolver needs it
37613
+ // for path-alias resolution in the import graph.
37599
37614
  semantic: { enabled: false },
37600
37615
  reportCodeQuality: false,
37616
+ reportRedundancy: false,
37601
37617
  };
37602
37618
  const result = await analyze(defineConfig(config));
37603
37619
  emit({ ok: true, result: normalizeResult(result) });
@@ -37681,13 +37697,23 @@ const parseCircularDependencies = (value) => {
37681
37697
  }
37682
37698
  return circularDependencies;
37683
37699
  };
37700
+ const parseSummaryCacheStats = (value) => {
37701
+ if (!isRecord(value)) return void 0;
37702
+ if (typeof value.hits !== "number" || typeof value.misses !== "number") return void 0;
37703
+ return {
37704
+ hits: value.hits,
37705
+ misses: value.misses
37706
+ };
37707
+ };
37684
37708
  const parseDeadCodeWorkerResult = (value) => {
37685
37709
  if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
37710
+ const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
37686
37711
  return {
37687
37712
  unusedFiles: parseUnusedFiles(value.unusedFiles),
37688
37713
  unusedExports: parseUnusedExports(value.unusedExports),
37689
37714
  unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
37690
- circularDependencies: parseCircularDependencies(value.circularDependencies)
37715
+ circularDependencies: parseCircularDependencies(value.circularDependencies),
37716
+ ...summaryCacheStats ? { summaryCacheStats } : {}
37691
37717
  };
37692
37718
  };
37693
37719
  const parseDeadCodeWorkerError = (value) => {
@@ -37808,13 +37834,15 @@ const checkDeadCode = async (options) => {
37808
37834
  entryPatterns,
37809
37835
  ignorePatterns,
37810
37836
  tsConfigPath,
37811
- deslopJsModuleSpecifier
37837
+ deslopJsModuleSpecifier,
37838
+ coreVersion: CORE_PACKAGE_VERSION
37812
37839
  }) : null;
37813
37840
  if (cacheKey !== null) {
37814
37841
  const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
37815
37842
  options.onCacheOutcome?.(cachedDiagnostics !== null);
37816
37843
  if (cachedDiagnostics !== null) return [...cachedDiagnostics];
37817
37844
  }
37845
+ const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
37818
37846
  const spawnAndRun = () => {
37819
37847
  return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37820
37848
  rootDirectory,
@@ -37822,10 +37850,12 @@ const checkDeadCode = async (options) => {
37822
37850
  tsConfigPath,
37823
37851
  ignorePatterns,
37824
37852
  deslopJsModuleSpecifier,
37825
- parseConcurrency: options.parseConcurrency
37853
+ parseConcurrency: options.parseConcurrency,
37854
+ incrementalCachePath
37826
37855
  }), options.workerTimeoutMs ?? 12e4, options.abortSignal);
37827
37856
  };
37828
37857
  const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
37858
+ if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
37829
37859
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
37830
37860
  const diagnostics = [];
37831
37861
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -37993,7 +38023,8 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
37993
38023
  workerTimeoutMs: input.workerTimeoutMs,
37994
38024
  abortSignal: signal,
37995
38025
  cacheEnabled,
37996
- onCacheOutcome: input.onCacheOutcome
38026
+ onCacheOutcome: input.onCacheOutcome,
38027
+ onSummaryCacheStats: input.onSummaryCacheStats
37997
38028
  }),
37998
38029
  catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
37999
38030
  }).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
@@ -38863,7 +38894,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38863
38894
  }
38864
38895
  return enabled;
38865
38896
  };
38866
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
38897
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
38867
38898
  const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38868
38899
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38869
38900
  const jsPlugins = [];
@@ -38875,6 +38906,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38875
38906
  if (!rule) continue;
38876
38907
  if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
38877
38908
  if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
38909
+ if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
38878
38910
  if (rule.scan !== void 0) continue;
38879
38911
  if (customRulesOnly && registryEntry.originallyExternal) continue;
38880
38912
  if (rule.framework !== "global" && !rule.requires) continue;
@@ -38919,23 +38951,23 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38919
38951
  }
38920
38952
  };
38921
38953
  };
38922
- const validateDiagnostic = decodeUnknownSync(Diagnostic);
38954
+ const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
38923
38955
  const decodeFileDiagnostics = (raw) => {
38924
38956
  if (!Array.isArray(raw)) return null;
38925
38957
  try {
38926
- for (const entry of raw) validateDiagnostic(entry);
38958
+ for (const entry of raw) validateDiagnostic$1(entry);
38927
38959
  return raw;
38928
38960
  } catch {
38929
38961
  return null;
38930
38962
  }
38931
38963
  };
38932
- const emptyCache = () => ({
38964
+ const emptyCache$1 = () => ({
38933
38965
  version: 1,
38934
38966
  rulesets: {}
38935
38967
  });
38936
- const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
38968
+ const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
38937
38969
  const entries = /* @__PURE__ */ new Map();
38938
- const persisted = failOpenReadJson(cacheFilePath, emptyCache());
38970
+ const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
38939
38971
  if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
38940
38972
  const bucket = persisted.rulesets[rulesetHash];
38941
38973
  if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
@@ -38947,7 +38979,7 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
38947
38979
  };
38948
38980
  const createFileLintCache = (cacheDirectory, rulesetHash) => {
38949
38981
  const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
38950
- const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
38982
+ const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
38951
38983
  return {
38952
38984
  lookup: (fileKey) => entries.get(fileKey) ?? null,
38953
38985
  store: (fileKey, diagnostics) => {
@@ -38955,7 +38987,7 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
38955
38987
  entries.set(fileKey, diagnostics);
38956
38988
  },
38957
38989
  persist: () => {
38958
- const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
38990
+ const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
38959
38991
  const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
38960
38992
  const existingBucket = rulesets[rulesetHash];
38961
38993
  const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
@@ -38976,6 +39008,142 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
38976
39008
  }
38977
39009
  };
38978
39010
  };
39011
+ const hashFileContents = (filePath) => {
39012
+ try {
39013
+ return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
39014
+ } catch {
39015
+ return null;
39016
+ }
39017
+ };
39018
+ const ABSENT_CONTENT_ANSWER = "absent";
39019
+ /**
39020
+ * Answers dependency probes for the sidecar lint cache — used both when
39021
+ * STORING an entry (turn the collector's probe paths into answers) and when
39022
+ * VERIFYING one (re-answer each stored probe against the current tree).
39023
+ * Store and verify must agree exactly, so both go through this one resolver.
39024
+ *
39025
+ * - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
39026
+ * (missing, a directory, permission failure). Source files reuse the
39027
+ * hashes the cache-partition loop already computed
39028
+ * (`contentHashByRelativePath`); everything else (package.json,
39029
+ * tsconfig, ignored files) is hashed on demand.
39030
+ * - `exists` — the existence class a module-resolution candidate probe
39031
+ * sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
39032
+ * `statSync` follows symlinks, matching the resolvers).
39033
+ *
39034
+ * Answers are memoized per scan: the filesystem is frozen for the run, and
39035
+ * hot dependency paths (a barrel imported by hundreds of files, the shared
39036
+ * tsconfig chain) repeat across many files' probe sets.
39037
+ */
39038
+ const createSidecarProbeAnswerResolver = (input) => {
39039
+ const contentAnswerByPath = /* @__PURE__ */ new Map();
39040
+ const existsAnswerByPath = /* @__PURE__ */ new Map();
39041
+ const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
39042
+ const contentAnswer = (relativePath) => {
39043
+ const memoized = contentAnswerByPath.get(relativePath);
39044
+ if (memoized !== void 0) return memoized;
39045
+ const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
39046
+ contentAnswerByPath.set(relativePath, answer);
39047
+ return answer;
39048
+ };
39049
+ const existsAnswer = (relativePath) => {
39050
+ const memoized = existsAnswerByPath.get(relativePath);
39051
+ if (memoized !== void 0) return memoized;
39052
+ let answer;
39053
+ try {
39054
+ const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
39055
+ answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
39056
+ } catch {
39057
+ answer = "none";
39058
+ }
39059
+ existsAnswerByPath.set(relativePath, answer);
39060
+ return answer;
39061
+ };
39062
+ return {
39063
+ answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
39064
+ toRelativePath
39065
+ };
39066
+ };
39067
+ const validateDiagnostic = decodeUnknownSync(Diagnostic);
39068
+ const isProbeKind = (value) => value === "content" || value === "exists";
39069
+ const decodeSidecarFileEntry = (raw) => {
39070
+ if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
39071
+ const probes = [];
39072
+ for (const rawProbe of raw.probes) {
39073
+ if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
39074
+ const [kind, probePath, answer] = rawProbe;
39075
+ if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
39076
+ probes.push({
39077
+ kind,
39078
+ path: probePath,
39079
+ answer
39080
+ });
39081
+ }
39082
+ try {
39083
+ for (const entry of raw.diagnostics) validateDiagnostic(entry);
39084
+ } catch {
39085
+ return null;
39086
+ }
39087
+ return {
39088
+ probes,
39089
+ diagnostics: raw.diagnostics
39090
+ };
39091
+ };
39092
+ const encodeSidecarFileEntry = (entry) => ({
39093
+ probes: entry.probes.map((probe) => [
39094
+ probe.kind,
39095
+ probe.path,
39096
+ probe.answer
39097
+ ]),
39098
+ diagnostics: entry.diagnostics
39099
+ });
39100
+ const emptyCache = () => ({
39101
+ version: 1,
39102
+ rulesets: {}
39103
+ });
39104
+ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
39105
+ const entries = /* @__PURE__ */ new Map();
39106
+ const persisted = failOpenReadJson(cacheFilePath, emptyCache());
39107
+ if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
39108
+ const bucket = persisted.rulesets[rulesetHash];
39109
+ if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
39110
+ for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
39111
+ const decoded = decodeSidecarFileEntry(rawEntry);
39112
+ if (decoded !== null) entries.set(fileKey, decoded);
39113
+ }
39114
+ return entries;
39115
+ };
39116
+ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39117
+ const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
39118
+ const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
39119
+ return {
39120
+ lookup: (fileKey) => entries.get(fileKey) ?? null,
39121
+ store: (fileKey, entry) => {
39122
+ entries.delete(fileKey);
39123
+ entries.set(fileKey, entry);
39124
+ },
39125
+ persist: () => {
39126
+ const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
39127
+ const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
39128
+ const existingBucket = rulesets[rulesetHash];
39129
+ const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
39130
+ const ourFiles = {};
39131
+ for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
39132
+ const cappedEntries = Object.entries({
39133
+ ...existingFiles,
39134
+ ...ourFiles
39135
+ }).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
39136
+ rulesets[rulesetHash] = {
39137
+ updatedAtMs: Date.now(),
39138
+ files: Object.fromEntries(cappedEntries)
39139
+ };
39140
+ atomicWriteJson(cacheFilePath, {
39141
+ version: 1,
39142
+ rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
39143
+ });
39144
+ }
39145
+ };
39146
+ };
38979
39147
  const bundledRequire = createRequire(import.meta.url);
38980
39148
  const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
38981
39149
  const resolveChildNodeVersion = (nodeBinaryPath) => {
@@ -39975,13 +40143,6 @@ const validateRuleRegistration = () => {
39975
40143
  ].filter((entry) => entry !== null).join("; ");
39976
40144
  console.warn(`[react-doctor] rule-registration drift: ${detail}`);
39977
40145
  };
39978
- const hashFileContents = (filePath) => {
39979
- try {
39980
- return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
39981
- } catch {
39982
- return null;
39983
- }
39984
- };
39985
40146
  /**
39986
40147
  * Balanced LPT (longest-processing-time-first) lint batch planner. Where
39987
40148
  * `batchIncludePaths` greedily fills 100-file batches and leaves a small
@@ -40063,6 +40224,106 @@ const writeOxlintConfig = (configPath, configToWrite) => {
40063
40224
  NFS.closeSync(fileHandle);
40064
40225
  }
40065
40226
  };
40227
+ /**
40228
+ * Attributes diagnostics back to the file that produced them by the
40229
+ * normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
40230
+ * attributed — the path forms don't align, so the caller must skip its cache
40231
+ * store rather than risk caching a wrong empty result for a file that
40232
+ * actually had diagnostics.
40233
+ */
40234
+ const attributeDiagnosticsToFiles = (diagnostics, files) => {
40235
+ const fileByNormalizedPath = /* @__PURE__ */ new Map();
40236
+ for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
40237
+ const diagnosticsByFile = /* @__PURE__ */ new Map();
40238
+ for (const diagnostic of diagnostics) {
40239
+ const file = fileByNormalizedPath.get(diagnostic.filePath);
40240
+ if (file === void 0) return null;
40241
+ const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
40242
+ fileDiagnostics.push(diagnostic);
40243
+ diagnosticsByFile.set(file, fileDiagnostics);
40244
+ }
40245
+ return diagnosticsByFile;
40246
+ };
40247
+ /**
40248
+ * Collects each file's cross-file dependency probes (via the plugin's
40249
+ * collectors) with their current answers. Fail-open per file: an unreadable /
40250
+ * unparseable / collector-failing file maps to `null` (no entry stored — it
40251
+ * re-lints next scan). The filesystem is frozen for the scan, so this can run
40252
+ * CONCURRENTLY with an oxlint child-process await — the caller starts it
40253
+ * unawaited before a lint pass and awaits it at store time, hiding the
40254
+ * in-process parse + resolution cost under the subprocess wait. The loop
40255
+ * yields on the cooperative time budget so the child's stdout handling (and
40256
+ * sibling fibers) keep flowing; each per-file collection is synchronous, so
40257
+ * the module-level probe recorder never sees interleaved use. Never rejects.
40258
+ */
40259
+ const collectSidecarProbesForFiles = async (input) => {
40260
+ const buildProbes = (file) => {
40261
+ const absoluteFilePath = Path.resolve(input.rootDirectory, file);
40262
+ let trace;
40263
+ try {
40264
+ trace = collectCrossFileDependencyProbes({
40265
+ absoluteFilePath,
40266
+ sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
40267
+ ruleIds: input.boundedSidecarRuleIds
40268
+ });
40269
+ } catch {
40270
+ return null;
40271
+ }
40272
+ if (trace === null) return null;
40273
+ const probes = [];
40274
+ for (const contentPath of trace.contentPaths) {
40275
+ const relativePath = input.probeAnswers.toRelativePath(contentPath);
40276
+ probes.push({
40277
+ kind: "content",
40278
+ path: relativePath,
40279
+ answer: input.probeAnswers.answerFor("content", relativePath)
40280
+ });
40281
+ }
40282
+ for (const existencePath of trace.existencePaths) {
40283
+ const relativePath = input.probeAnswers.toRelativePath(existencePath);
40284
+ probes.push({
40285
+ kind: "exists",
40286
+ path: relativePath,
40287
+ answer: input.probeAnswers.answerFor("exists", relativePath)
40288
+ });
40289
+ }
40290
+ return probes;
40291
+ };
40292
+ const probesByFile = /* @__PURE__ */ new Map();
40293
+ let collectSliceStartedAt = performance.now();
40294
+ for (const file of input.files) {
40295
+ probesByFile.set(file, buildProbes(file));
40296
+ if (performance.now() - collectSliceStartedAt >= 12) {
40297
+ await yieldToEventLoop();
40298
+ collectSliceStartedAt = performance.now();
40299
+ }
40300
+ }
40301
+ return probesByFile;
40302
+ };
40303
+ /**
40304
+ * Stores each freshly-linted file's probe set + cross-file diagnostics so
40305
+ * the next scan can replay instead of re-linting.
40306
+ */
40307
+ const storeSidecarEntries = (input) => {
40308
+ let didStoreAnyEntry = false;
40309
+ for (const pass of input.storablePasses) {
40310
+ if (!pass.isTrusted) continue;
40311
+ const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
40312
+ if (diagnosticsByFile === null) continue;
40313
+ for (const file of pass.files) {
40314
+ const cacheKey = input.cacheKeyByFile.get(file);
40315
+ if (cacheKey === void 0) continue;
40316
+ const probes = pass.probesByFile.get(file);
40317
+ if (probes === void 0 || probes === null) continue;
40318
+ input.sidecarCache.store(cacheKey, {
40319
+ probes,
40320
+ diagnostics: diagnosticsByFile.get(file) ?? []
40321
+ });
40322
+ didStoreAnyEntry = true;
40323
+ }
40324
+ }
40325
+ if (didStoreAnyEntry) input.sidecarCache.persist();
40326
+ };
40066
40327
  const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
40067
40328
  /**
40068
40329
  * Detects an oxlint config-load crash caused by the optional
@@ -40104,7 +40365,7 @@ const reactHooksJsPluginDropNote = (error) => {
40104
40365
  * 6. always restore disable directives + clean up the temp dir
40105
40366
  */
40106
40367
  const runOxlint = async (options) => {
40107
- 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;
40368
+ 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;
40108
40369
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
40109
40370
  const severityControls = buildRuleSeverityControls(userConfig);
40110
40371
  validateRuleRegistration();
@@ -40122,7 +40383,8 @@ const runOxlint = async (options) => {
40122
40383
  severityControls,
40123
40384
  userPlugins,
40124
40385
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
40125
- ruleSelection: overrides.ruleSelection
40386
+ ruleSelection: overrides.ruleSelection,
40387
+ sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
40126
40388
  });
40127
40389
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
40128
40390
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -40225,15 +40487,18 @@ const runOxlint = async (options) => {
40225
40487
  });
40226
40488
  const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
40227
40489
  const cacheKeyByFile = /* @__PURE__ */ new Map();
40490
+ const contentHashByRelativePath = /* @__PURE__ */ new Map();
40228
40491
  const missFiles = [];
40229
40492
  const replayedDiagnostics = [];
40230
40493
  let hashSliceStartedAt = performance.now();
40231
40494
  for (const candidateFile of candidateFiles) {
40232
- const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
40495
+ const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
40496
+ const contentHash = hashFileContents(absoluteCandidatePath);
40233
40497
  if (contentHash === null) {
40234
40498
  missFiles.push(candidateFile);
40235
40499
  continue;
40236
40500
  }
40501
+ contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
40237
40502
  const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
40238
40503
  cacheKeyByFile.set(candidateFile, cacheKey);
40239
40504
  const cachedDiagnostics = cache.lookup(cacheKey);
@@ -40245,43 +40510,111 @@ const runOxlint = async (options) => {
40245
40510
  }
40246
40511
  }
40247
40512
  const cacheHitFileCount = candidateFiles.length - missFiles.length;
40513
+ const enabledSidecarRuleIds = Object.keys(buildConfig({
40514
+ extendsPaths: [],
40515
+ ruleSelection: "sidecar"
40516
+ }).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
40517
+ const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
40518
+ const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
40519
+ const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
40520
+ const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
40521
+ const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
40522
+ config: buildConfig({
40523
+ extendsPaths: [],
40524
+ ruleSelection: "sidecar",
40525
+ sidecarRuleIdFilter: boundedSidecarRuleIdSet
40526
+ }),
40527
+ toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
40528
+ ignorePatterns: combinedPatterns,
40529
+ tsconfigContent
40530
+ })) : null;
40531
+ const probeAnswers = createSidecarProbeAnswerResolver({
40532
+ rootDirectory,
40533
+ contentHashByRelativePath
40534
+ });
40535
+ const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
40536
+ files: missFiles,
40537
+ rootDirectory,
40538
+ boundedSidecarRuleIds,
40539
+ probeAnswers
40540
+ });
40248
40541
  const fullResult = await runConfigOverFiles((overrides) => buildConfig({
40249
40542
  extendsPaths: [],
40250
40543
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
40251
40544
  }), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
40252
40545
  const missFileSet = new Set(missFiles);
40253
- const sidecarResult = await runConfigOverFiles(() => buildConfig({
40546
+ const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
40547
+ const sidecarReplayedDiagnostics = [];
40548
+ const sidecarLintFiles = [];
40549
+ if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
40550
+ else {
40551
+ let verifySliceStartedAt = performance.now();
40552
+ for (const hitFile of hitFiles) {
40553
+ const cacheKey = cacheKeyByFile.get(hitFile);
40554
+ const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
40555
+ if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
40556
+ else sidecarLintFiles.push(hitFile);
40557
+ if (performance.now() - verifySliceStartedAt >= 12) {
40558
+ await yieldToEventLoop();
40559
+ verifySliceStartedAt = performance.now();
40560
+ }
40561
+ }
40562
+ }
40563
+ const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
40564
+ files: sidecarLintFiles,
40565
+ rootDirectory,
40566
+ boundedSidecarRuleIds,
40567
+ probeAnswers
40568
+ });
40569
+ const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
40570
+ if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
40571
+ const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
40254
40572
  extendsPaths: [],
40255
- ruleSelection: "sidecar"
40256
- }), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
40573
+ ruleSelection: "sidecar",
40574
+ ...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
40575
+ }), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
40576
+ const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
40577
+ extendsPaths: [],
40578
+ ruleSelection: "sidecar",
40579
+ sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
40580
+ }), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
40581
+ diagnostics: [],
40582
+ didDropReactHooksJsPlugin: false,
40583
+ hadPartialFailure: false
40584
+ };
40257
40585
  const freshCacheableDiagnostics = [];
40258
40586
  for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
40259
40587
  onCacheStats?.(cacheHitFileCount, candidateFiles.length);
40260
- const missFileByNormalizedPath = /* @__PURE__ */ new Map();
40261
- for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
40262
- const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
40263
- let isAttributionSound = true;
40264
- for (const diagnostic of freshCacheableDiagnostics) {
40265
- const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
40266
- if (missFile === void 0) {
40267
- isAttributionSound = false;
40268
- break;
40269
- }
40270
- const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
40271
- fileDiagnostics.push(diagnostic);
40272
- freshDiagnosticsByFile.set(missFile, fileDiagnostics);
40273
- }
40274
- if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
40588
+ if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
40589
+ const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
40590
+ if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
40275
40591
  for (const missFile of missFiles) {
40276
40592
  const cacheKey = cacheKeyByFile.get(missFile);
40277
40593
  if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
40278
40594
  }
40279
40595
  cache.persist();
40280
40596
  }
40597
+ if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
40598
+ sidecarCache,
40599
+ cacheKeyByFile,
40600
+ storablePasses: [{
40601
+ files: sidecarLintFiles,
40602
+ diagnostics: boundedSidecarResult.diagnostics,
40603
+ isTrusted: !boundedSidecarResult.hadPartialFailure,
40604
+ probesByFile: await sidecarProbesTask
40605
+ }, {
40606
+ files: missFiles,
40607
+ diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
40608
+ isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
40609
+ probesByFile: await missProbesTask
40610
+ }]
40611
+ });
40281
40612
  return dedupeDiagnostics([
40282
40613
  ...replayedDiagnostics,
40283
40614
  ...fullResult.diagnostics,
40284
- ...sidecarResult.diagnostics
40615
+ ...sidecarReplayedDiagnostics,
40616
+ ...boundedSidecarResult.diagnostics,
40617
+ ...unboundedSidecarResult.diagnostics
40285
40618
  ]);
40286
40619
  }
40287
40620
  const baseArgs = makeBaseArgs(configPath);
@@ -40379,6 +40712,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
40379
40712
  const concurrency = yield* OxlintConcurrency;
40380
40713
  const lintBatchOrdering = yield* LintBatchOrdering;
40381
40714
  const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
40715
+ const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
40382
40716
  const collectedFailures = [];
40383
40717
  const diagnostics = yield* tryPromise({
40384
40718
  try: (signal) => runOxlint({
@@ -40397,7 +40731,9 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
40397
40731
  },
40398
40732
  onFileProgress: input.onFileProgress,
40399
40733
  perFileLintCacheEnabled,
40734
+ sidecarLintCacheEnabled,
40400
40735
  onCacheStats: input.onCacheStats,
40736
+ onSidecarStats: input.onSidecarStats,
40401
40737
  spawnTimeoutMs,
40402
40738
  outputMaxBytes,
40403
40739
  concurrency,
@@ -41175,6 +41511,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41175
41511
  workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
41176
41512
  onCacheOutcome: (didHitCache) => {
41177
41513
  deadCodeCacheHit = didHitCache;
41514
+ },
41515
+ onSummaryCacheStats: (stats) => {
41516
+ deadCodeSummaryCacheHits = stats.hits;
41517
+ deadCodeSummaryCacheMisses = stats.misses;
41178
41518
  }
41179
41519
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41180
41520
  yield* set(deadCodeFailure, {
@@ -41203,7 +41543,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41203
41543
  let lastReportedTotalFileCount = 0;
41204
41544
  let lintCacheHitFileCount = null;
41205
41545
  let lintCacheTotalFileCount = null;
41546
+ let lintSidecarReplayedFileCount = null;
41547
+ let lintSidecarTotalFileCount = null;
41206
41548
  let deadCodeCacheHit = null;
41549
+ let deadCodeSummaryCacheHits = null;
41550
+ let deadCodeSummaryCacheMisses = null;
41207
41551
  const baseLintStream = linterService.run({
41208
41552
  rootDirectory: scanDirectory,
41209
41553
  project,
@@ -41223,6 +41567,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41223
41567
  lintCacheHitFileCount = cacheHitFileCount;
41224
41568
  lintCacheTotalFileCount = totalConsideredFileCount;
41225
41569
  },
41570
+ onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
41571
+ lintSidecarReplayedFileCount = sidecarReplayedFileCount;
41572
+ lintSidecarTotalFileCount = sidecarConsideredFileCount;
41573
+ },
41226
41574
  deadlineEpochMs: input.deadlineEpochMs
41227
41575
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41228
41576
  yield* set(lintFailure, {
@@ -41333,7 +41681,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41333
41681
  securityScanFailed,
41334
41682
  lintCacheHitFileCount,
41335
41683
  lintCacheTotalFileCount,
41684
+ lintSidecarReplayedFileCount,
41685
+ lintSidecarTotalFileCount,
41336
41686
  deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
41687
+ deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
41688
+ deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
41337
41689
  suppressedRuleCounts: transform.summarizeSuppressions()
41338
41690
  };
41339
41691
  }).pipe(withSpan("runInspect", { attributes: {
@@ -41974,4 +42326,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41974
42326
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41975
42327
 
41976
42328
  //# sourceMappingURL=index.js.map
41977
- //# debugId=fb390a00-3616-5547-b572-1ce120b0e1a2
42329
+ //# debugId=b55ef8b2-8e9d-512b-81de-a01f6ce3e60e