react-doctor 0.6.3-dev.ea9a775 → 0.6.3

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]="b55ef8b2-8e9d-512b-81de-a01f6ce3e60e")}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]="8565c6ea-39f3-51e6-895a-ae9f7c026d0e")}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_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
+ 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";
13
13
  import * as OS from "node:os";
14
14
  import os from "node:os";
15
15
  import { parseJSON5 } from "confbox";
@@ -18,7 +18,6 @@ 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";
22
21
  import { gzipSync } from "node:zlib";
23
22
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
24
23
  /**
@@ -32928,10 +32927,6 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
32928
32927
  const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
32929
32928
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
32930
32929
  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";
32933
- const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
32934
- const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
32935
32930
  const SUPPLY_CHAIN_PLUGIN = "socket";
32936
32931
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
32937
32932
  const SUPPLY_CHAIN_CATEGORY = "Security";
@@ -35618,48 +35613,6 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
35618
35613
  if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
35619
35614
  return true;
35620
35615
  } }) {};
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
- /**
35645
- * Whether the whole-project dead-code result cache
35646
- * (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
35647
- * whose inputs (source tree, manifests, configs, analyzer version) are
35648
- * unchanged replays the stored diagnostics instead of re-running the
35649
- * analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
35650
- *
35651
- * - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
35652
- * - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
35653
- *
35654
- * Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
35655
- */
35656
- var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
35657
- const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
35658
- const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
35659
- if (CACHE_DISABLED_VALUES.has(noCache)) return false;
35660
- if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
35661
- return true;
35662
- } }) {};
35663
35616
  const DIAGNOSTIC_SURFACES = [
35664
35617
  "cli",
35665
35618
  "prComment",
@@ -37348,80 +37301,6 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
37348
37301
  return [...seen].filter((pattern) => pattern.length > 0);
37349
37302
  };
37350
37303
  const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
37351
- const atomicWriteJson = (filePath, value) => {
37352
- try {
37353
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
37354
- const temporaryPath = `${filePath}.${process.pid}.tmp`;
37355
- NFS.writeFileSync(temporaryPath, JSON.stringify(value));
37356
- NFS.renameSync(temporaryPath, filePath);
37357
- } catch {
37358
- return;
37359
- }
37360
- };
37361
- const failOpenReadJson = (filePath, fallback) => {
37362
- try {
37363
- return JSON.parse(NFS.readFileSync(filePath, "utf8"));
37364
- } catch {
37365
- return fallback;
37366
- }
37367
- };
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]);
37371
- const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
37372
- const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
37373
- const collectAnalyzedFileFingerprints = (rootDirectory) => {
37374
- const fingerprints = [];
37375
- for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
37376
- if (!isFingerprintedFile(name)) continue;
37377
- try {
37378
- const fileStat = NFS.statSync(absolutePath);
37379
- const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
37380
- fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
37381
- } catch {}
37382
- }
37383
- return fingerprints.sort();
37384
- };
37385
- const bundledRequire$2 = createRequire(import.meta.url);
37386
- const resolveDeslopVersion = () => {
37387
- try {
37388
- const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
37389
- return isRecord(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
37390
- } catch {
37391
- return "unknown";
37392
- }
37393
- };
37394
- const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
37395
- schemaVersion: 1,
37396
- coreVersion: input.coreVersion,
37397
- deslopVersion: resolveDeslopVersion(),
37398
- deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
37399
- entryPatterns: input.entryPatterns,
37400
- ignorePatterns: input.ignorePatterns,
37401
- tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
37402
- files: collectAnalyzedFileFingerprints(input.rootDirectory)
37403
- })).digest("hex");
37404
- const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
37405
- const decodeCachedDiagnostics = (raw) => {
37406
- try {
37407
- for (const entry of raw) validateDiagnostic$2(entry);
37408
- return raw;
37409
- } catch {
37410
- return null;
37411
- }
37412
- };
37413
- const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
37414
- const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
37415
- if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
37416
- return decodeCachedDiagnostics(persisted.diagnostics);
37417
- };
37418
- const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
37419
- atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37420
- version: 1,
37421
- key: cacheKey,
37422
- diagnostics
37423
- });
37424
- };
37425
37304
  const readSystemFacts = () => ({
37426
37305
  availableCores: os.availableParallelism(),
37427
37306
  totalMemoryBytes: os.totalmem(),
@@ -37490,21 +37369,6 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
37490
37369
  releaseSlot();
37491
37370
  }
37492
37371
  };
37493
- const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
37494
- const resolveReactDoctorCacheDir = (projectDirectory) => {
37495
- const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
37496
- if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
37497
- const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
37498
- if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
37499
- return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
37500
- };
37501
- const resolveUserCacheScope = () => {
37502
- try {
37503
- return String(process.getuid?.() ?? os.userInfo().username);
37504
- } catch {
37505
- return "shared";
37506
- }
37507
- };
37508
37372
  /**
37509
37373
  * Resolves a path to its canonical, symlink-free form, falling back to
37510
37374
  * the input when it cannot be realpath'd (broken symlink, permission
@@ -37558,14 +37422,6 @@ process.stdin.on("end", () => {
37558
37422
  circularDependencies: result.circularDependencies.map((cycle) => ({
37559
37423
  files: cycle.files,
37560
37424
  })),
37561
- ...(result.incrementalCacheStats
37562
- ? {
37563
- summaryCacheStats: {
37564
- hits: result.incrementalCacheStats.summaryHits,
37565
- misses: result.incrementalCacheStats.summaryMisses,
37566
- },
37567
- }
37568
- : {}),
37569
37425
  });
37570
37426
 
37571
37427
  const serializeError = (error) =>
@@ -37589,9 +37445,6 @@ process.stdin.on("end", () => {
37589
37445
  ...(workerInput.ignorePatterns.length > 0
37590
37446
  ? { ignorePatterns: workerInput.ignorePatterns }
37591
37447
  : {}),
37592
- ...(workerInput.incrementalCachePath
37593
- ? { incrementalCachePath: workerInput.incrementalCachePath }
37594
- : {}),
37595
37448
  // We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
37596
37449
  // unusedDependencies, circularDependencies). Everything else deslop can compute
37597
37450
  // is pure wasted work for us, and it's the bulk of the runtime:
@@ -37602,18 +37455,12 @@ process.stdin.on("end", () => {
37602
37455
  // are the single most expensive pass — duplicate-block detection alone was
37603
37456
  // ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
37604
37457
  // speedup on a large repo.
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
37458
+ // Both are provably safe: the consumed graph findings are computed by their own
37610
37459
  // detectors, independent of these passes (confirmed byte-identical on
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.
37460
+ // excalidraw + mui-material + sentry). tsConfigPath stays — the module resolver
37461
+ // needs it for path-alias resolution in the import graph.
37614
37462
  semantic: { enabled: false },
37615
37463
  reportCodeQuality: false,
37616
- reportRedundancy: false,
37617
37464
  };
37618
37465
  const result = await analyze(defineConfig(config));
37619
37466
  emit({ ok: true, result: normalizeResult(result) });
@@ -37697,23 +37544,13 @@ const parseCircularDependencies = (value) => {
37697
37544
  }
37698
37545
  return circularDependencies;
37699
37546
  };
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
- };
37708
37547
  const parseDeadCodeWorkerResult = (value) => {
37709
37548
  if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
37710
- const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
37711
37549
  return {
37712
37550
  unusedFiles: parseUnusedFiles(value.unusedFiles),
37713
37551
  unusedExports: parseUnusedExports(value.unusedExports),
37714
37552
  unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
37715
- circularDependencies: parseCircularDependencies(value.circularDependencies),
37716
- ...summaryCacheStats ? { summaryCacheStats } : {}
37553
+ circularDependencies: parseCircularDependencies(value.circularDependencies)
37717
37554
  };
37718
37555
  };
37719
37556
  const parseDeadCodeWorkerError = (value) => {
@@ -37827,35 +37664,17 @@ const checkDeadCode = async (options) => {
37827
37664
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37828
37665
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37829
37666
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37830
- const tsConfigPath = resolveTsConfigPath(rootDirectory);
37831
- const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
37832
- const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
37833
- rootDirectory,
37834
- entryPatterns,
37835
- ignorePatterns,
37836
- tsConfigPath,
37837
- deslopJsModuleSpecifier,
37838
- coreVersion: CORE_PACKAGE_VERSION
37839
- }) : null;
37840
- if (cacheKey !== null) {
37841
- const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
37842
- options.onCacheOutcome?.(cachedDiagnostics !== null);
37843
- if (cachedDiagnostics !== null) return [...cachedDiagnostics];
37844
- }
37845
- const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
37846
37667
  const spawnAndRun = () => {
37847
37668
  return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37848
37669
  rootDirectory,
37849
37670
  entryPatterns,
37850
- tsConfigPath,
37671
+ tsConfigPath: resolveTsConfigPath(rootDirectory),
37851
37672
  ignorePatterns,
37852
- deslopJsModuleSpecifier,
37853
- parseConcurrency: options.parseConcurrency,
37854
- incrementalCachePath
37673
+ deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
37674
+ parseConcurrency: options.parseConcurrency
37855
37675
  }), options.workerTimeoutMs ?? 12e4, options.abortSignal);
37856
37676
  };
37857
37677
  const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
37858
- if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
37859
37678
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
37860
37679
  const diagnostics = [];
37861
37680
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -37912,7 +37731,6 @@ const checkDeadCode = async (options) => {
37912
37731
  category: DEAD_CODE_CATEGORY
37913
37732
  });
37914
37733
  }
37915
- if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
37916
37734
  return diagnostics;
37917
37735
  };
37918
37736
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -38015,16 +37833,12 @@ var Config = class Config extends Service()("react-doctor/Config") {
38015
37833
  */
38016
37834
  var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
38017
37835
  static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
38018
- const cacheEnabled = yield* DeadCodeResultCacheEnabled;
38019
37836
  return yield* tryPromise({
38020
37837
  try: (signal) => checkDeadCode({
38021
37838
  rootDirectory: input.rootDirectory,
38022
37839
  parseConcurrency: input.parseConcurrency,
38023
37840
  workerTimeoutMs: input.workerTimeoutMs,
38024
- abortSignal: signal,
38025
- cacheEnabled,
38026
- onCacheOutcome: input.onCacheOutcome,
38027
- onSummaryCacheStats: input.onSummaryCacheStats
37841
+ abortSignal: signal
38028
37842
  }),
38029
37843
  catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
38030
37844
  }).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
@@ -38894,7 +38708,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38894
38708
  }
38895
38709
  return enabled;
38896
38710
  };
38897
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
38711
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
38898
38712
  const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38899
38713
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38900
38714
  const jsPlugins = [];
@@ -38906,7 +38720,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38906
38720
  if (!rule) continue;
38907
38721
  if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
38908
38722
  if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
38909
- if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
38910
38723
  if (rule.scan !== void 0) continue;
38911
38724
  if (customRulesOnly && registryEntry.originallyExternal) continue;
38912
38725
  if (rule.framework !== "global" && !rule.requires) continue;
@@ -38951,152 +38764,33 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38951
38764
  }
38952
38765
  };
38953
38766
  };
38954
- const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
38955
- const decodeFileDiagnostics = (raw) => {
38956
- if (!Array.isArray(raw)) return null;
38767
+ const atomicWriteJson = (filePath, value) => {
38957
38768
  try {
38958
- for (const entry of raw) validateDiagnostic$1(entry);
38959
- return raw;
38769
+ NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
38770
+ const temporaryPath = `${filePath}.${process.pid}.tmp`;
38771
+ NFS.writeFileSync(temporaryPath, JSON.stringify(value));
38772
+ NFS.renameSync(temporaryPath, filePath);
38960
38773
  } catch {
38961
- return null;
38962
- }
38963
- };
38964
- const emptyCache$1 = () => ({
38965
- version: 1,
38966
- rulesets: {}
38967
- });
38968
- const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
38969
- const entries = /* @__PURE__ */ new Map();
38970
- const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
38971
- if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
38972
- const bucket = persisted.rulesets[rulesetHash];
38973
- if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
38974
- for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
38975
- const decoded = decodeFileDiagnostics(rawDiagnostics);
38976
- if (decoded !== null) entries.set(fileKey, decoded);
38774
+ return;
38977
38775
  }
38978
- return entries;
38979
38776
  };
38980
- const createFileLintCache = (cacheDirectory, rulesetHash) => {
38981
- const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
38982
- const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
38983
- return {
38984
- lookup: (fileKey) => entries.get(fileKey) ?? null,
38985
- store: (fileKey, diagnostics) => {
38986
- entries.delete(fileKey);
38987
- entries.set(fileKey, diagnostics);
38988
- },
38989
- persist: () => {
38990
- const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
38991
- const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
38992
- const existingBucket = rulesets[rulesetHash];
38993
- const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
38994
- const ourFiles = {};
38995
- for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
38996
- const cappedEntries = Object.entries({
38997
- ...existingFiles,
38998
- ...ourFiles
38999
- }).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
39000
- rulesets[rulesetHash] = {
39001
- updatedAtMs: Date.now(),
39002
- files: Object.fromEntries(cappedEntries)
39003
- };
39004
- atomicWriteJson(cacheFilePath, {
39005
- version: 1,
39006
- rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
39007
- });
39008
- }
39009
- };
39010
- };
39011
- const hashFileContents = (filePath) => {
38777
+ const failOpenReadJson = (filePath, fallback) => {
39012
38778
  try {
39013
- return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
38779
+ return JSON.parse(NFS.readFileSync(filePath, "utf8"));
39014
38780
  } catch {
39015
- return null;
38781
+ return fallback;
39016
38782
  }
39017
38783
  };
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
38784
  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
- }
38785
+ const decodeFileDiagnostics = (raw) => {
38786
+ if (!Array.isArray(raw)) return null;
39082
38787
  try {
39083
- for (const entry of raw.diagnostics) validateDiagnostic(entry);
38788
+ for (const entry of raw) validateDiagnostic(entry);
38789
+ return raw;
39084
38790
  } catch {
39085
38791
  return null;
39086
38792
  }
39087
- return {
39088
- probes,
39089
- diagnostics: raw.diagnostics
39090
- };
39091
38793
  };
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
38794
  const emptyCache = () => ({
39101
38795
  version: 1,
39102
38796
  rulesets: {}
@@ -39107,20 +38801,20 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
39107
38801
  if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
39108
38802
  const bucket = persisted.rulesets[rulesetHash];
39109
38803
  if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
39110
- for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
39111
- const decoded = decodeSidecarFileEntry(rawEntry);
38804
+ for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
38805
+ const decoded = decodeFileDiagnostics(rawDiagnostics);
39112
38806
  if (decoded !== null) entries.set(fileKey, decoded);
39113
38807
  }
39114
38808
  return entries;
39115
38809
  };
39116
- const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39117
- const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
38810
+ const createFileLintCache = (cacheDirectory, rulesetHash) => {
38811
+ const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
39118
38812
  const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
39119
38813
  return {
39120
38814
  lookup: (fileKey) => entries.get(fileKey) ?? null,
39121
- store: (fileKey, entry) => {
38815
+ store: (fileKey, diagnostics) => {
39122
38816
  entries.delete(fileKey);
39123
- entries.set(fileKey, entry);
38817
+ entries.set(fileKey, diagnostics);
39124
38818
  },
39125
38819
  persist: () => {
39126
38820
  const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
@@ -39128,7 +38822,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39128
38822
  const existingBucket = rulesets[rulesetHash];
39129
38823
  const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
39130
38824
  const ourFiles = {};
39131
- for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
38825
+ for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
39132
38826
  const cappedEntries = Object.entries({
39133
38827
  ...existingFiles,
39134
38828
  ...ourFiles
@@ -40143,6 +39837,13 @@ const validateRuleRegistration = () => {
40143
39837
  ].filter((entry) => entry !== null).join("; ");
40144
39838
  console.warn(`[react-doctor] rule-registration drift: ${detail}`);
40145
39839
  };
39840
+ const hashFileContents = (filePath) => {
39841
+ try {
39842
+ return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
39843
+ } catch {
39844
+ return null;
39845
+ }
39846
+ };
40146
39847
  /**
40147
39848
  * Balanced LPT (longest-processing-time-first) lint batch planner. Where
40148
39849
  * `batchIncludePaths` greedily fills 100-file batches and leaves a small
@@ -40207,6 +39908,21 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
40207
39908
  }
40208
39909
  return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
40209
39910
  };
39911
+ const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
39912
+ const resolveReactDoctorCacheDir = (projectDirectory) => {
39913
+ const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
39914
+ if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
39915
+ const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
39916
+ if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
39917
+ return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
39918
+ };
39919
+ const resolveUserCacheScope = () => {
39920
+ try {
39921
+ return String(process.getuid?.() ?? os.userInfo().username);
39922
+ } catch {
39923
+ return "shared";
39924
+ }
39925
+ };
40210
39926
  /**
40211
39927
  * Atomically (re)writes the generated oxlintrc.json. Used twice in
40212
39928
  * the runner: once for the primary scan, once for the
@@ -40224,106 +39940,6 @@ const writeOxlintConfig = (configPath, configToWrite) => {
40224
39940
  NFS.closeSync(fileHandle);
40225
39941
  }
40226
39942
  };
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
- };
40327
39943
  const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
40328
39944
  /**
40329
39945
  * Detects an oxlint config-load crash caused by the optional
@@ -40365,7 +39981,7 @@ const reactHooksJsPluginDropNote = (error) => {
40365
39981
  * 6. always restore disable directives + clean up the temp dir
40366
39982
  */
40367
39983
  const runOxlint = async (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;
39984
+ 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;
40369
39985
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
40370
39986
  const severityControls = buildRuleSeverityControls(userConfig);
40371
39987
  validateRuleRegistration();
@@ -40383,8 +39999,7 @@ const runOxlint = async (options) => {
40383
39999
  severityControls,
40384
40000
  userPlugins,
40385
40001
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
40386
- ruleSelection: overrides.ruleSelection,
40387
- sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
40002
+ ruleSelection: overrides.ruleSelection
40388
40003
  });
40389
40004
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
40390
40005
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -40487,18 +40102,15 @@ const runOxlint = async (options) => {
40487
40102
  });
40488
40103
  const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
40489
40104
  const cacheKeyByFile = /* @__PURE__ */ new Map();
40490
- const contentHashByRelativePath = /* @__PURE__ */ new Map();
40491
40105
  const missFiles = [];
40492
40106
  const replayedDiagnostics = [];
40493
40107
  let hashSliceStartedAt = performance.now();
40494
40108
  for (const candidateFile of candidateFiles) {
40495
- const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
40496
- const contentHash = hashFileContents(absoluteCandidatePath);
40109
+ const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
40497
40110
  if (contentHash === null) {
40498
40111
  missFiles.push(candidateFile);
40499
40112
  continue;
40500
40113
  }
40501
- contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
40502
40114
  const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
40503
40115
  cacheKeyByFile.set(candidateFile, cacheKey);
40504
40116
  const cachedDiagnostics = cache.lookup(cacheKey);
@@ -40510,111 +40122,43 @@ const runOxlint = async (options) => {
40510
40122
  }
40511
40123
  }
40512
40124
  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
- });
40541
40125
  const fullResult = await runConfigOverFiles((overrides) => buildConfig({
40542
40126
  extendsPaths: [],
40543
40127
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
40544
40128
  }), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
40545
40129
  const missFileSet = new Set(missFiles);
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({
40130
+ const sidecarResult = await runConfigOverFiles(() => buildConfig({
40572
40131
  extendsPaths: [],
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
- };
40132
+ ruleSelection: "sidecar"
40133
+ }), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
40585
40134
  const freshCacheableDiagnostics = [];
40586
40135
  for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
40587
40136
  onCacheStats?.(cacheHitFileCount, candidateFiles.length);
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) {
40137
+ const missFileByNormalizedPath = /* @__PURE__ */ new Map();
40138
+ for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
40139
+ const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
40140
+ let isAttributionSound = true;
40141
+ for (const diagnostic of freshCacheableDiagnostics) {
40142
+ const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
40143
+ if (missFile === void 0) {
40144
+ isAttributionSound = false;
40145
+ break;
40146
+ }
40147
+ const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
40148
+ fileDiagnostics.push(diagnostic);
40149
+ freshDiagnosticsByFile.set(missFile, fileDiagnostics);
40150
+ }
40151
+ if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
40591
40152
  for (const missFile of missFiles) {
40592
40153
  const cacheKey = cacheKeyByFile.get(missFile);
40593
40154
  if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
40594
40155
  }
40595
40156
  cache.persist();
40596
40157
  }
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
- });
40612
40158
  return dedupeDiagnostics([
40613
40159
  ...replayedDiagnostics,
40614
40160
  ...fullResult.diagnostics,
40615
- ...sidecarReplayedDiagnostics,
40616
- ...boundedSidecarResult.diagnostics,
40617
- ...unboundedSidecarResult.diagnostics
40161
+ ...sidecarResult.diagnostics
40618
40162
  ]);
40619
40163
  }
40620
40164
  const baseArgs = makeBaseArgs(configPath);
@@ -40712,7 +40256,6 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
40712
40256
  const concurrency = yield* OxlintConcurrency;
40713
40257
  const lintBatchOrdering = yield* LintBatchOrdering;
40714
40258
  const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
40715
- const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
40716
40259
  const collectedFailures = [];
40717
40260
  const diagnostics = yield* tryPromise({
40718
40261
  try: (signal) => runOxlint({
@@ -40731,9 +40274,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
40731
40274
  },
40732
40275
  onFileProgress: input.onFileProgress,
40733
40276
  perFileLintCacheEnabled,
40734
- sidecarLintCacheEnabled,
40735
40277
  onCacheStats: input.onCacheStats,
40736
- onSidecarStats: input.onSidecarStats,
40737
40278
  spawnTimeoutMs,
40738
40279
  outputMaxBytes,
40739
40280
  concurrency,
@@ -41508,14 +41049,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41508
41049
  const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
41509
41050
  rootDirectory: scanDirectory,
41510
41051
  parseConcurrency: deadCodeParseConcurrency,
41511
- workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
41512
- onCacheOutcome: (didHitCache) => {
41513
- deadCodeCacheHit = didHitCache;
41514
- },
41515
- onSummaryCacheStats: (stats) => {
41516
- deadCodeSummaryCacheHits = stats.hits;
41517
- deadCodeSummaryCacheMisses = stats.misses;
41518
- }
41052
+ workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
41519
41053
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41520
41054
  yield* set(deadCodeFailure, {
41521
41055
  didFail: true,
@@ -41543,11 +41077,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41543
41077
  let lastReportedTotalFileCount = 0;
41544
41078
  let lintCacheHitFileCount = null;
41545
41079
  let lintCacheTotalFileCount = null;
41546
- let lintSidecarReplayedFileCount = null;
41547
- let lintSidecarTotalFileCount = null;
41548
- let deadCodeCacheHit = null;
41549
- let deadCodeSummaryCacheHits = null;
41550
- let deadCodeSummaryCacheMisses = null;
41551
41080
  const baseLintStream = linterService.run({
41552
41081
  rootDirectory: scanDirectory,
41553
41082
  project,
@@ -41567,10 +41096,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41567
41096
  lintCacheHitFileCount = cacheHitFileCount;
41568
41097
  lintCacheTotalFileCount = totalConsideredFileCount;
41569
41098
  },
41570
- onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
41571
- lintSidecarReplayedFileCount = sidecarReplayedFileCount;
41572
- lintSidecarTotalFileCount = sidecarConsideredFileCount;
41573
- },
41574
41099
  deadlineEpochMs: input.deadlineEpochMs
41575
41100
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41576
41101
  yield* set(lintFailure, {
@@ -41681,11 +41206,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41681
41206
  securityScanFailed,
41682
41207
  lintCacheHitFileCount,
41683
41208
  lintCacheTotalFileCount,
41684
- lintSidecarReplayedFileCount,
41685
- lintSidecarTotalFileCount,
41686
- deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
41687
- deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
41688
- deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
41689
41209
  suppressedRuleCounts: transform.summarizeSuppressions()
41690
41210
  };
41691
41211
  }).pipe(withSpan("runInspect", { attributes: {
@@ -42326,4 +41846,4 @@ const toJsonReport = (result, options) => buildJsonReport({
42326
41846
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
42327
41847
 
42328
41848
  //# sourceMappingURL=index.js.map
42329
- //# debugId=b55ef8b2-8e9d-512b-81de-a01f6ce3e60e
41849
+ //# debugId=8565c6ea-39f3-51e6-895a-ae9f7c026d0e