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/lsp.js CHANGED
@@ -8,14 +8,13 @@ import path from "node:path";
8
8
  import * as NodeChildProcess from "node:child_process";
9
9
  import { execFileSync, spawn, spawnSync } from "node:child_process";
10
10
  import * as ts from "typescript";
11
- import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_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";
11
+ import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
12
12
  import { parseJSON5 } from "confbox";
13
13
  import * as NodeUrl from "node:url";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import { createJiti } from "jiti";
16
16
  import * as Crypto from "node:crypto";
17
17
  import crypto, { createHash } from "node:crypto";
18
- import { ANALYZED_MANIFEST_FILENAMES, DEFAULT_EXTENSIONS } from "deslop-js/analyzed-inputs";
19
18
  import { gzipSync } from "node:zlib";
20
19
  import { CodeActionKind, CodeActionTriggerKind, DidChangeWatchedFilesNotification, DocumentDiagnosticReportKind, FileChangeType, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/node.js";
21
20
  import { TextDocument } from "vscode-languageserver-textdocument";
@@ -32987,10 +32986,6 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
32987
32986
  const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
32988
32987
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
32989
32988
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
32990
- const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
32991
- const CORE_PACKAGE_VERSION = "0.6.3";
32992
- const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
32993
- const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
32994
32989
  const SUPPLY_CHAIN_PLUGIN = "socket";
32995
32990
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
32996
32991
  const SUPPLY_CHAIN_CATEGORY = "Security";
@@ -35651,48 +35646,6 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
35651
35646
  if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
35652
35647
  return true;
35653
35648
  } }) {};
35654
- /**
35655
- * Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
35656
- * active. Defaults ON — warm rescans replay the cross-file rules' cached
35657
- * diagnostics for every file whose dependency fingerprint (the probe set the
35658
- * plugin's collectors recorded) still matches the tree, and re-lint only the
35659
- * rest. Only reachable when the per-file lint cache itself is on (the
35660
- * sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
35661
- * `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
35662
- *
35663
- * - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
35664
- * - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
35665
- * per-file cache but run the always-fresh sidecar over every hit
35666
- * (the pre-cache behavior).
35667
- *
35668
- * Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
35669
- */
35670
- var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
35671
- const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
35672
- const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
35673
- if (CACHE_DISABLED_VALUES.has(noCache)) return false;
35674
- if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
35675
- return true;
35676
- } }) {};
35677
- /**
35678
- * Whether the whole-project dead-code result cache
35679
- * (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
35680
- * whose inputs (source tree, manifests, configs, analyzer version) are
35681
- * unchanged replays the stored diagnostics instead of re-running the
35682
- * analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
35683
- *
35684
- * - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
35685
- * - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
35686
- *
35687
- * Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
35688
- */
35689
- var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
35690
- const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
35691
- const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
35692
- if (CACHE_DISABLED_VALUES.has(noCache)) return false;
35693
- if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
35694
- return true;
35695
- } }) {};
35696
35649
  const DIAGNOSTIC_SURFACES = [
35697
35650
  "cli",
35698
35651
  "prComment",
@@ -37333,80 +37286,6 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
37333
37286
  return [...seen].filter((pattern) => pattern.length > 0);
37334
37287
  };
37335
37288
  const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
37336
- const atomicWriteJson = (filePath, value) => {
37337
- try {
37338
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
37339
- const temporaryPath = `${filePath}.${process.pid}.tmp`;
37340
- NFS.writeFileSync(temporaryPath, JSON.stringify(value));
37341
- NFS.renameSync(temporaryPath, filePath);
37342
- } catch {
37343
- return;
37344
- }
37345
- };
37346
- const failOpenReadJson = (filePath, fallback) => {
37347
- try {
37348
- return JSON.parse(NFS.readFileSync(filePath, "utf8"));
37349
- } catch {
37350
- return fallback;
37351
- }
37352
- };
37353
- const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
37354
- const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
37355
- const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
37356
- const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
37357
- const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
37358
- const collectAnalyzedFileFingerprints = (rootDirectory) => {
37359
- const fingerprints = [];
37360
- for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
37361
- if (!isFingerprintedFile(name)) continue;
37362
- try {
37363
- const fileStat = NFS.statSync(absolutePath);
37364
- const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
37365
- fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
37366
- } catch {}
37367
- }
37368
- return fingerprints.sort();
37369
- };
37370
- const bundledRequire$2 = createRequire(import.meta.url);
37371
- const resolveDeslopVersion = () => {
37372
- try {
37373
- const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
37374
- return isRecord(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
37375
- } catch {
37376
- return "unknown";
37377
- }
37378
- };
37379
- const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
37380
- schemaVersion: 1,
37381
- coreVersion: input.coreVersion,
37382
- deslopVersion: resolveDeslopVersion(),
37383
- deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
37384
- entryPatterns: input.entryPatterns,
37385
- ignorePatterns: input.ignorePatterns,
37386
- tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
37387
- files: collectAnalyzedFileFingerprints(input.rootDirectory)
37388
- })).digest("hex");
37389
- const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
37390
- const decodeCachedDiagnostics = (raw) => {
37391
- try {
37392
- for (const entry of raw) validateDiagnostic$2(entry);
37393
- return raw;
37394
- } catch {
37395
- return null;
37396
- }
37397
- };
37398
- const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
37399
- const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
37400
- if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
37401
- return decodeCachedDiagnostics(persisted.diagnostics);
37402
- };
37403
- const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
37404
- atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37405
- version: 1,
37406
- key: cacheKey,
37407
- diagnostics
37408
- });
37409
- };
37410
37289
  const readSystemFacts = () => ({
37411
37290
  availableCores: os.availableParallelism(),
37412
37291
  totalMemoryBytes: os.totalmem(),
@@ -37475,21 +37354,6 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
37475
37354
  releaseSlot();
37476
37355
  }
37477
37356
  };
37478
- const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
37479
- const resolveReactDoctorCacheDir = (projectDirectory) => {
37480
- const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
37481
- if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
37482
- const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
37483
- if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
37484
- return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
37485
- };
37486
- const resolveUserCacheScope = () => {
37487
- try {
37488
- return String(process.getuid?.() ?? os.userInfo().username);
37489
- } catch {
37490
- return "shared";
37491
- }
37492
- };
37493
37357
  /**
37494
37358
  * Resolves a path to its canonical, symlink-free form, falling back to
37495
37359
  * the input when it cannot be realpath'd (broken symlink, permission
@@ -37543,14 +37407,6 @@ process.stdin.on("end", () => {
37543
37407
  circularDependencies: result.circularDependencies.map((cycle) => ({
37544
37408
  files: cycle.files,
37545
37409
  })),
37546
- ...(result.incrementalCacheStats
37547
- ? {
37548
- summaryCacheStats: {
37549
- hits: result.incrementalCacheStats.summaryHits,
37550
- misses: result.incrementalCacheStats.summaryMisses,
37551
- },
37552
- }
37553
- : {}),
37554
37410
  });
37555
37411
 
37556
37412
  const serializeError = (error) =>
@@ -37574,9 +37430,6 @@ process.stdin.on("end", () => {
37574
37430
  ...(workerInput.ignorePatterns.length > 0
37575
37431
  ? { ignorePatterns: workerInput.ignorePatterns }
37576
37432
  : {}),
37577
- ...(workerInput.incrementalCachePath
37578
- ? { incrementalCachePath: workerInput.incrementalCachePath }
37579
- : {}),
37580
37433
  // We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
37581
37434
  // unusedDependencies, circularDependencies). Everything else deslop can compute
37582
37435
  // is pure wasted work for us, and it's the bulk of the runtime:
@@ -37587,18 +37440,12 @@ process.stdin.on("end", () => {
37587
37440
  // are the single most expensive pass — duplicate-block detection alone was
37588
37441
  // ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
37589
37442
  // speedup on a large repo.
37590
- // - reportRedundancy: the DRY-pattern detectors (duplicate types/constants,
37591
- // simplifiable functions, identity wrappers, …) — ~120 ms of discarded
37592
- // output per scan, and skipping them lets the incremental cache drop the
37593
- // DRY-pattern summary fields (the largest slice of the cache file).
37594
- // All are provably safe: the consumed graph findings are computed by their own
37443
+ // Both are provably safe: the consumed graph findings are computed by their own
37595
37444
  // detectors, independent of these passes (confirmed byte-identical on
37596
- // excalidraw + mui-material + sentry; re-verified on sentry after the
37597
- // reportRedundancy flip). tsConfigPath stays — the module resolver needs it
37598
- // for path-alias resolution in the import graph.
37445
+ // excalidraw + mui-material + sentry). tsConfigPath stays — the module resolver
37446
+ // needs it for path-alias resolution in the import graph.
37599
37447
  semantic: { enabled: false },
37600
37448
  reportCodeQuality: false,
37601
- reportRedundancy: false,
37602
37449
  };
37603
37450
  const result = await analyze(defineConfig(config));
37604
37451
  emit({ ok: true, result: normalizeResult(result) });
@@ -37682,23 +37529,13 @@ const parseCircularDependencies = (value) => {
37682
37529
  }
37683
37530
  return circularDependencies;
37684
37531
  };
37685
- const parseSummaryCacheStats = (value) => {
37686
- if (!isRecord(value)) return void 0;
37687
- if (typeof value.hits !== "number" || typeof value.misses !== "number") return void 0;
37688
- return {
37689
- hits: value.hits,
37690
- misses: value.misses
37691
- };
37692
- };
37693
37532
  const parseDeadCodeWorkerResult = (value) => {
37694
37533
  if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
37695
- const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
37696
37534
  return {
37697
37535
  unusedFiles: parseUnusedFiles(value.unusedFiles),
37698
37536
  unusedExports: parseUnusedExports(value.unusedExports),
37699
37537
  unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
37700
- circularDependencies: parseCircularDependencies(value.circularDependencies),
37701
- ...summaryCacheStats ? { summaryCacheStats } : {}
37538
+ circularDependencies: parseCircularDependencies(value.circularDependencies)
37702
37539
  };
37703
37540
  };
37704
37541
  const parseDeadCodeWorkerError = (value) => {
@@ -37812,35 +37649,17 @@ const checkDeadCode = async (options) => {
37812
37649
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37813
37650
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37814
37651
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37815
- const tsConfigPath = resolveTsConfigPath(rootDirectory);
37816
- const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
37817
- const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
37818
- rootDirectory,
37819
- entryPatterns,
37820
- ignorePatterns,
37821
- tsConfigPath,
37822
- deslopJsModuleSpecifier,
37823
- coreVersion: CORE_PACKAGE_VERSION
37824
- }) : null;
37825
- if (cacheKey !== null) {
37826
- const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
37827
- options.onCacheOutcome?.(cachedDiagnostics !== null);
37828
- if (cachedDiagnostics !== null) return [...cachedDiagnostics];
37829
- }
37830
- const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
37831
37652
  const spawnAndRun = () => {
37832
37653
  return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37833
37654
  rootDirectory,
37834
37655
  entryPatterns,
37835
- tsConfigPath,
37656
+ tsConfigPath: resolveTsConfigPath(rootDirectory),
37836
37657
  ignorePatterns,
37837
- deslopJsModuleSpecifier,
37838
- parseConcurrency: options.parseConcurrency,
37839
- incrementalCachePath
37658
+ deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
37659
+ parseConcurrency: options.parseConcurrency
37840
37660
  }), options.workerTimeoutMs ?? 12e4, options.abortSignal);
37841
37661
  };
37842
37662
  const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
37843
- if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
37844
37663
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
37845
37664
  const diagnostics = [];
37846
37665
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -37897,7 +37716,6 @@ const checkDeadCode = async (options) => {
37897
37716
  category: DEAD_CODE_CATEGORY
37898
37717
  });
37899
37718
  }
37900
- if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
37901
37719
  return diagnostics;
37902
37720
  };
37903
37721
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -38000,16 +37818,12 @@ var Config = class Config extends Service()("react-doctor/Config") {
38000
37818
  */
38001
37819
  var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
38002
37820
  static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
38003
- const cacheEnabled = yield* DeadCodeResultCacheEnabled;
38004
37821
  return yield* tryPromise({
38005
37822
  try: (signal) => checkDeadCode({
38006
37823
  rootDirectory: input.rootDirectory,
38007
37824
  parseConcurrency: input.parseConcurrency,
38008
37825
  workerTimeoutMs: input.workerTimeoutMs,
38009
- abortSignal: signal,
38010
- cacheEnabled,
38011
- onCacheOutcome: input.onCacheOutcome,
38012
- onSummaryCacheStats: input.onSummaryCacheStats
37826
+ abortSignal: signal
38013
37827
  }),
38014
37828
  catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
38015
37829
  }).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
@@ -38879,7 +38693,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38879
38693
  }
38880
38694
  return enabled;
38881
38695
  };
38882
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
38696
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
38883
38697
  const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38884
38698
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38885
38699
  const jsPlugins = [];
@@ -38891,7 +38705,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38891
38705
  if (!rule) continue;
38892
38706
  if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
38893
38707
  if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
38894
- if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
38895
38708
  if (rule.scan !== void 0) continue;
38896
38709
  if (customRulesOnly && registryEntry.originallyExternal) continue;
38897
38710
  if (rule.framework !== "global" && !rule.requires) continue;
@@ -38936,152 +38749,33 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38936
38749
  }
38937
38750
  };
38938
38751
  };
38939
- const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
38940
- const decodeFileDiagnostics = (raw) => {
38941
- if (!Array.isArray(raw)) return null;
38752
+ const atomicWriteJson = (filePath, value) => {
38942
38753
  try {
38943
- for (const entry of raw) validateDiagnostic$1(entry);
38944
- return raw;
38754
+ NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
38755
+ const temporaryPath = `${filePath}.${process.pid}.tmp`;
38756
+ NFS.writeFileSync(temporaryPath, JSON.stringify(value));
38757
+ NFS.renameSync(temporaryPath, filePath);
38945
38758
  } catch {
38946
- return null;
38947
- }
38948
- };
38949
- const emptyCache$1 = () => ({
38950
- version: 1,
38951
- rulesets: {}
38952
- });
38953
- const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
38954
- const entries = /* @__PURE__ */ new Map();
38955
- const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
38956
- if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
38957
- const bucket = persisted.rulesets[rulesetHash];
38958
- if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
38959
- for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
38960
- const decoded = decodeFileDiagnostics(rawDiagnostics);
38961
- if (decoded !== null) entries.set(fileKey, decoded);
38759
+ return;
38962
38760
  }
38963
- return entries;
38964
38761
  };
38965
- const createFileLintCache = (cacheDirectory, rulesetHash) => {
38966
- const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
38967
- const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
38968
- return {
38969
- lookup: (fileKey) => entries.get(fileKey) ?? null,
38970
- store: (fileKey, diagnostics) => {
38971
- entries.delete(fileKey);
38972
- entries.set(fileKey, diagnostics);
38973
- },
38974
- persist: () => {
38975
- const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
38976
- const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
38977
- const existingBucket = rulesets[rulesetHash];
38978
- const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
38979
- const ourFiles = {};
38980
- for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
38981
- const cappedEntries = Object.entries({
38982
- ...existingFiles,
38983
- ...ourFiles
38984
- }).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
38985
- rulesets[rulesetHash] = {
38986
- updatedAtMs: Date.now(),
38987
- files: Object.fromEntries(cappedEntries)
38988
- };
38989
- atomicWriteJson(cacheFilePath, {
38990
- version: 1,
38991
- rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
38992
- });
38993
- }
38994
- };
38995
- };
38996
- const hashFileContents = (filePath) => {
38762
+ const failOpenReadJson = (filePath, fallback) => {
38997
38763
  try {
38998
- return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
38764
+ return JSON.parse(NFS.readFileSync(filePath, "utf8"));
38999
38765
  } catch {
39000
- return null;
38766
+ return fallback;
39001
38767
  }
39002
38768
  };
39003
- const ABSENT_CONTENT_ANSWER = "absent";
39004
- /**
39005
- * Answers dependency probes for the sidecar lint cache — used both when
39006
- * STORING an entry (turn the collector's probe paths into answers) and when
39007
- * VERIFYING one (re-answer each stored probe against the current tree).
39008
- * Store and verify must agree exactly, so both go through this one resolver.
39009
- *
39010
- * - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
39011
- * (missing, a directory, permission failure). Source files reuse the
39012
- * hashes the cache-partition loop already computed
39013
- * (`contentHashByRelativePath`); everything else (package.json,
39014
- * tsconfig, ignored files) is hashed on demand.
39015
- * - `exists` — the existence class a module-resolution candidate probe
39016
- * sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
39017
- * `statSync` follows symlinks, matching the resolvers).
39018
- *
39019
- * Answers are memoized per scan: the filesystem is frozen for the run, and
39020
- * hot dependency paths (a barrel imported by hundreds of files, the shared
39021
- * tsconfig chain) repeat across many files' probe sets.
39022
- */
39023
- const createSidecarProbeAnswerResolver = (input) => {
39024
- const contentAnswerByPath = /* @__PURE__ */ new Map();
39025
- const existsAnswerByPath = /* @__PURE__ */ new Map();
39026
- const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
39027
- const contentAnswer = (relativePath) => {
39028
- const memoized = contentAnswerByPath.get(relativePath);
39029
- if (memoized !== void 0) return memoized;
39030
- const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
39031
- contentAnswerByPath.set(relativePath, answer);
39032
- return answer;
39033
- };
39034
- const existsAnswer = (relativePath) => {
39035
- const memoized = existsAnswerByPath.get(relativePath);
39036
- if (memoized !== void 0) return memoized;
39037
- let answer;
39038
- try {
39039
- const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
39040
- answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
39041
- } catch {
39042
- answer = "none";
39043
- }
39044
- existsAnswerByPath.set(relativePath, answer);
39045
- return answer;
39046
- };
39047
- return {
39048
- answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
39049
- toRelativePath
39050
- };
39051
- };
39052
38769
  const validateDiagnostic = decodeUnknownSync(Diagnostic);
39053
- const isProbeKind = (value) => value === "content" || value === "exists";
39054
- const decodeSidecarFileEntry = (raw) => {
39055
- if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
39056
- const probes = [];
39057
- for (const rawProbe of raw.probes) {
39058
- if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
39059
- const [kind, probePath, answer] = rawProbe;
39060
- if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
39061
- probes.push({
39062
- kind,
39063
- path: probePath,
39064
- answer
39065
- });
39066
- }
38770
+ const decodeFileDiagnostics = (raw) => {
38771
+ if (!Array.isArray(raw)) return null;
39067
38772
  try {
39068
- for (const entry of raw.diagnostics) validateDiagnostic(entry);
38773
+ for (const entry of raw) validateDiagnostic(entry);
38774
+ return raw;
39069
38775
  } catch {
39070
38776
  return null;
39071
38777
  }
39072
- return {
39073
- probes,
39074
- diagnostics: raw.diagnostics
39075
- };
39076
38778
  };
39077
- const encodeSidecarFileEntry = (entry) => ({
39078
- probes: entry.probes.map((probe) => [
39079
- probe.kind,
39080
- probe.path,
39081
- probe.answer
39082
- ]),
39083
- diagnostics: entry.diagnostics
39084
- });
39085
38779
  const emptyCache = () => ({
39086
38780
  version: 1,
39087
38781
  rulesets: {}
@@ -39092,20 +38786,20 @@ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
39092
38786
  if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
39093
38787
  const bucket = persisted.rulesets[rulesetHash];
39094
38788
  if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
39095
- for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
39096
- const decoded = decodeSidecarFileEntry(rawEntry);
38789
+ for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
38790
+ const decoded = decodeFileDiagnostics(rawDiagnostics);
39097
38791
  if (decoded !== null) entries.set(fileKey, decoded);
39098
38792
  }
39099
38793
  return entries;
39100
38794
  };
39101
- const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39102
- const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
38795
+ const createFileLintCache = (cacheDirectory, rulesetHash) => {
38796
+ const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
39103
38797
  const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
39104
38798
  return {
39105
38799
  lookup: (fileKey) => entries.get(fileKey) ?? null,
39106
- store: (fileKey, entry) => {
38800
+ store: (fileKey, diagnostics) => {
39107
38801
  entries.delete(fileKey);
39108
- entries.set(fileKey, entry);
38802
+ entries.set(fileKey, diagnostics);
39109
38803
  },
39110
38804
  persist: () => {
39111
38805
  const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
@@ -39113,7 +38807,7 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39113
38807
  const existingBucket = rulesets[rulesetHash];
39114
38808
  const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
39115
38809
  const ourFiles = {};
39116
- for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
38810
+ for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
39117
38811
  const cappedEntries = Object.entries({
39118
38812
  ...existingFiles,
39119
38813
  ...ourFiles
@@ -40128,6 +39822,13 @@ const validateRuleRegistration = () => {
40128
39822
  ].filter((entry) => entry !== null).join("; ");
40129
39823
  console.warn(`[react-doctor] rule-registration drift: ${detail}`);
40130
39824
  };
39825
+ const hashFileContents = (filePath) => {
39826
+ try {
39827
+ return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
39828
+ } catch {
39829
+ return null;
39830
+ }
39831
+ };
40131
39832
  /**
40132
39833
  * Balanced LPT (longest-processing-time-first) lint batch planner. Where
40133
39834
  * `batchIncludePaths` greedily fills 100-file batches and leaves a small
@@ -40192,6 +39893,21 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
40192
39893
  }
40193
39894
  return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
40194
39895
  };
39896
+ const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
39897
+ const resolveReactDoctorCacheDir = (projectDirectory) => {
39898
+ const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
39899
+ if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
39900
+ const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
39901
+ if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
39902
+ return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
39903
+ };
39904
+ const resolveUserCacheScope = () => {
39905
+ try {
39906
+ return String(process.getuid?.() ?? os.userInfo().username);
39907
+ } catch {
39908
+ return "shared";
39909
+ }
39910
+ };
40195
39911
  /**
40196
39912
  * Atomically (re)writes the generated oxlintrc.json. Used twice in
40197
39913
  * the runner: once for the primary scan, once for the
@@ -40209,106 +39925,6 @@ const writeOxlintConfig = (configPath, configToWrite) => {
40209
39925
  NFS.closeSync(fileHandle);
40210
39926
  }
40211
39927
  };
40212
- /**
40213
- * Attributes diagnostics back to the file that produced them by the
40214
- * normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
40215
- * attributed — the path forms don't align, so the caller must skip its cache
40216
- * store rather than risk caching a wrong empty result for a file that
40217
- * actually had diagnostics.
40218
- */
40219
- const attributeDiagnosticsToFiles = (diagnostics, files) => {
40220
- const fileByNormalizedPath = /* @__PURE__ */ new Map();
40221
- for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
40222
- const diagnosticsByFile = /* @__PURE__ */ new Map();
40223
- for (const diagnostic of diagnostics) {
40224
- const file = fileByNormalizedPath.get(diagnostic.filePath);
40225
- if (file === void 0) return null;
40226
- const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
40227
- fileDiagnostics.push(diagnostic);
40228
- diagnosticsByFile.set(file, fileDiagnostics);
40229
- }
40230
- return diagnosticsByFile;
40231
- };
40232
- /**
40233
- * Collects each file's cross-file dependency probes (via the plugin's
40234
- * collectors) with their current answers. Fail-open per file: an unreadable /
40235
- * unparseable / collector-failing file maps to `null` (no entry stored — it
40236
- * re-lints next scan). The filesystem is frozen for the scan, so this can run
40237
- * CONCURRENTLY with an oxlint child-process await — the caller starts it
40238
- * unawaited before a lint pass and awaits it at store time, hiding the
40239
- * in-process parse + resolution cost under the subprocess wait. The loop
40240
- * yields on the cooperative time budget so the child's stdout handling (and
40241
- * sibling fibers) keep flowing; each per-file collection is synchronous, so
40242
- * the module-level probe recorder never sees interleaved use. Never rejects.
40243
- */
40244
- const collectSidecarProbesForFiles = async (input) => {
40245
- const buildProbes = (file) => {
40246
- const absoluteFilePath = Path.resolve(input.rootDirectory, file);
40247
- let trace;
40248
- try {
40249
- trace = collectCrossFileDependencyProbes({
40250
- absoluteFilePath,
40251
- sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
40252
- ruleIds: input.boundedSidecarRuleIds
40253
- });
40254
- } catch {
40255
- return null;
40256
- }
40257
- if (trace === null) return null;
40258
- const probes = [];
40259
- for (const contentPath of trace.contentPaths) {
40260
- const relativePath = input.probeAnswers.toRelativePath(contentPath);
40261
- probes.push({
40262
- kind: "content",
40263
- path: relativePath,
40264
- answer: input.probeAnswers.answerFor("content", relativePath)
40265
- });
40266
- }
40267
- for (const existencePath of trace.existencePaths) {
40268
- const relativePath = input.probeAnswers.toRelativePath(existencePath);
40269
- probes.push({
40270
- kind: "exists",
40271
- path: relativePath,
40272
- answer: input.probeAnswers.answerFor("exists", relativePath)
40273
- });
40274
- }
40275
- return probes;
40276
- };
40277
- const probesByFile = /* @__PURE__ */ new Map();
40278
- let collectSliceStartedAt = performance.now();
40279
- for (const file of input.files) {
40280
- probesByFile.set(file, buildProbes(file));
40281
- if (performance.now() - collectSliceStartedAt >= 12) {
40282
- await yieldToEventLoop();
40283
- collectSliceStartedAt = performance.now();
40284
- }
40285
- }
40286
- return probesByFile;
40287
- };
40288
- /**
40289
- * Stores each freshly-linted file's probe set + cross-file diagnostics so
40290
- * the next scan can replay instead of re-linting.
40291
- */
40292
- const storeSidecarEntries = (input) => {
40293
- let didStoreAnyEntry = false;
40294
- for (const pass of input.storablePasses) {
40295
- if (!pass.isTrusted) continue;
40296
- const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
40297
- if (diagnosticsByFile === null) continue;
40298
- for (const file of pass.files) {
40299
- const cacheKey = input.cacheKeyByFile.get(file);
40300
- if (cacheKey === void 0) continue;
40301
- const probes = pass.probesByFile.get(file);
40302
- if (probes === void 0 || probes === null) continue;
40303
- input.sidecarCache.store(cacheKey, {
40304
- probes,
40305
- diagnostics: diagnosticsByFile.get(file) ?? []
40306
- });
40307
- didStoreAnyEntry = true;
40308
- }
40309
- }
40310
- if (didStoreAnyEntry) input.sidecarCache.persist();
40311
- };
40312
39928
  const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
40313
39929
  /**
40314
39930
  * Detects an oxlint config-load crash caused by the optional
@@ -40350,7 +39966,7 @@ const reactHooksJsPluginDropNote = (error) => {
40350
39966
  * 6. always restore disable directives + clean up the temp dir
40351
39967
  */
40352
39968
  const runOxlint = async (options) => {
40353
- const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
39969
+ 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;
40354
39970
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
40355
39971
  const severityControls = buildRuleSeverityControls(userConfig);
40356
39972
  validateRuleRegistration();
@@ -40368,8 +39984,7 @@ const runOxlint = async (options) => {
40368
39984
  severityControls,
40369
39985
  userPlugins,
40370
39986
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
40371
- ruleSelection: overrides.ruleSelection,
40372
- sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
39987
+ ruleSelection: overrides.ruleSelection
40373
39988
  });
40374
39989
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
40375
39990
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -40472,18 +40087,15 @@ const runOxlint = async (options) => {
40472
40087
  });
40473
40088
  const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
40474
40089
  const cacheKeyByFile = /* @__PURE__ */ new Map();
40475
- const contentHashByRelativePath = /* @__PURE__ */ new Map();
40476
40090
  const missFiles = [];
40477
40091
  const replayedDiagnostics = [];
40478
40092
  let hashSliceStartedAt = performance.now();
40479
40093
  for (const candidateFile of candidateFiles) {
40480
- const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
40481
- const contentHash = hashFileContents(absoluteCandidatePath);
40094
+ const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
40482
40095
  if (contentHash === null) {
40483
40096
  missFiles.push(candidateFile);
40484
40097
  continue;
40485
40098
  }
40486
- contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
40487
40099
  const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
40488
40100
  cacheKeyByFile.set(candidateFile, cacheKey);
40489
40101
  const cachedDiagnostics = cache.lookup(cacheKey);
@@ -40495,111 +40107,43 @@ const runOxlint = async (options) => {
40495
40107
  }
40496
40108
  }
40497
40109
  const cacheHitFileCount = candidateFiles.length - missFiles.length;
40498
- const enabledSidecarRuleIds = Object.keys(buildConfig({
40499
- extendsPaths: [],
40500
- ruleSelection: "sidecar"
40501
- }).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
40502
- const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
40503
- const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
40504
- const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
40505
- const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
40506
- const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
40507
- config: buildConfig({
40508
- extendsPaths: [],
40509
- ruleSelection: "sidecar",
40510
- sidecarRuleIdFilter: boundedSidecarRuleIdSet
40511
- }),
40512
- toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
40513
- ignorePatterns: combinedPatterns,
40514
- tsconfigContent
40515
- })) : null;
40516
- const probeAnswers = createSidecarProbeAnswerResolver({
40517
- rootDirectory,
40518
- contentHashByRelativePath
40519
- });
40520
- const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
40521
- files: missFiles,
40522
- rootDirectory,
40523
- boundedSidecarRuleIds,
40524
- probeAnswers
40525
- });
40526
40110
  const fullResult = await runConfigOverFiles((overrides) => buildConfig({
40527
40111
  extendsPaths: [],
40528
40112
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
40529
40113
  }), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
40530
40114
  const missFileSet = new Set(missFiles);
40531
- const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
40532
- const sidecarReplayedDiagnostics = [];
40533
- const sidecarLintFiles = [];
40534
- if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
40535
- else {
40536
- let verifySliceStartedAt = performance.now();
40537
- for (const hitFile of hitFiles) {
40538
- const cacheKey = cacheKeyByFile.get(hitFile);
40539
- const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
40540
- if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
40541
- else sidecarLintFiles.push(hitFile);
40542
- if (performance.now() - verifySliceStartedAt >= 12) {
40543
- await yieldToEventLoop();
40544
- verifySliceStartedAt = performance.now();
40545
- }
40546
- }
40547
- }
40548
- const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
40549
- files: sidecarLintFiles,
40550
- rootDirectory,
40551
- boundedSidecarRuleIds,
40552
- probeAnswers
40553
- });
40554
- const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
40555
- if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
40556
- const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
40115
+ const sidecarResult = await runConfigOverFiles(() => buildConfig({
40557
40116
  extendsPaths: [],
40558
- ruleSelection: "sidecar",
40559
- ...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
40560
- }), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
40561
- const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
40562
- extendsPaths: [],
40563
- ruleSelection: "sidecar",
40564
- sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
40565
- }), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
40566
- diagnostics: [],
40567
- didDropReactHooksJsPlugin: false,
40568
- hadPartialFailure: false
40569
- };
40117
+ ruleSelection: "sidecar"
40118
+ }), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
40570
40119
  const freshCacheableDiagnostics = [];
40571
40120
  for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
40572
40121
  onCacheStats?.(cacheHitFileCount, candidateFiles.length);
40573
- if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
40574
- const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
40575
- if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
40122
+ const missFileByNormalizedPath = /* @__PURE__ */ new Map();
40123
+ for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
40124
+ const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
40125
+ let isAttributionSound = true;
40126
+ for (const diagnostic of freshCacheableDiagnostics) {
40127
+ const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
40128
+ if (missFile === void 0) {
40129
+ isAttributionSound = false;
40130
+ break;
40131
+ }
40132
+ const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
40133
+ fileDiagnostics.push(diagnostic);
40134
+ freshDiagnosticsByFile.set(missFile, fileDiagnostics);
40135
+ }
40136
+ if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
40576
40137
  for (const missFile of missFiles) {
40577
40138
  const cacheKey = cacheKeyByFile.get(missFile);
40578
40139
  if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
40579
40140
  }
40580
40141
  cache.persist();
40581
40142
  }
40582
- if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
40583
- sidecarCache,
40584
- cacheKeyByFile,
40585
- storablePasses: [{
40586
- files: sidecarLintFiles,
40587
- diagnostics: boundedSidecarResult.diagnostics,
40588
- isTrusted: !boundedSidecarResult.hadPartialFailure,
40589
- probesByFile: await sidecarProbesTask
40590
- }, {
40591
- files: missFiles,
40592
- diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
40593
- isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
40594
- probesByFile: await missProbesTask
40595
- }]
40596
- });
40597
40143
  return dedupeDiagnostics([
40598
40144
  ...replayedDiagnostics,
40599
40145
  ...fullResult.diagnostics,
40600
- ...sidecarReplayedDiagnostics,
40601
- ...boundedSidecarResult.diagnostics,
40602
- ...unboundedSidecarResult.diagnostics
40146
+ ...sidecarResult.diagnostics
40603
40147
  ]);
40604
40148
  }
40605
40149
  const baseArgs = makeBaseArgs(configPath);
@@ -40697,7 +40241,6 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
40697
40241
  const concurrency = yield* OxlintConcurrency;
40698
40242
  const lintBatchOrdering = yield* LintBatchOrdering;
40699
40243
  const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
40700
- const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
40701
40244
  const collectedFailures = [];
40702
40245
  const diagnostics = yield* tryPromise({
40703
40246
  try: (signal) => runOxlint({
@@ -40716,9 +40259,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
40716
40259
  },
40717
40260
  onFileProgress: input.onFileProgress,
40718
40261
  perFileLintCacheEnabled,
40719
- sidecarLintCacheEnabled,
40720
40262
  onCacheStats: input.onCacheStats,
40721
- onSidecarStats: input.onSidecarStats,
40722
40263
  spawnTimeoutMs,
40723
40264
  outputMaxBytes,
40724
40265
  concurrency,
@@ -41493,14 +41034,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41493
41034
  const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
41494
41035
  rootDirectory: scanDirectory,
41495
41036
  parseConcurrency: deadCodeParseConcurrency,
41496
- workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
41497
- onCacheOutcome: (didHitCache) => {
41498
- deadCodeCacheHit = didHitCache;
41499
- },
41500
- onSummaryCacheStats: (stats) => {
41501
- deadCodeSummaryCacheHits = stats.hits;
41502
- deadCodeSummaryCacheMisses = stats.misses;
41503
- }
41037
+ workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
41504
41038
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41505
41039
  yield* set(deadCodeFailure, {
41506
41040
  didFail: true,
@@ -41528,11 +41062,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41528
41062
  let lastReportedTotalFileCount = 0;
41529
41063
  let lintCacheHitFileCount = null;
41530
41064
  let lintCacheTotalFileCount = null;
41531
- let lintSidecarReplayedFileCount = null;
41532
- let lintSidecarTotalFileCount = null;
41533
- let deadCodeCacheHit = null;
41534
- let deadCodeSummaryCacheHits = null;
41535
- let deadCodeSummaryCacheMisses = null;
41536
41065
  const baseLintStream = linterService.run({
41537
41066
  rootDirectory: scanDirectory,
41538
41067
  project,
@@ -41552,10 +41081,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41552
41081
  lintCacheHitFileCount = cacheHitFileCount;
41553
41082
  lintCacheTotalFileCount = totalConsideredFileCount;
41554
41083
  },
41555
- onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
41556
- lintSidecarReplayedFileCount = sidecarReplayedFileCount;
41557
- lintSidecarTotalFileCount = sidecarConsideredFileCount;
41558
- },
41559
41084
  deadlineEpochMs: input.deadlineEpochMs
41560
41085
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41561
41086
  yield* set(lintFailure, {
@@ -41666,11 +41191,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41666
41191
  securityScanFailed,
41667
41192
  lintCacheHitFileCount,
41668
41193
  lintCacheTotalFileCount,
41669
- lintSidecarReplayedFileCount,
41670
- lintSidecarTotalFileCount,
41671
- deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
41672
- deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
41673
- deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
41674
41194
  suppressedRuleCounts: transform.summarizeSuppressions()
41675
41195
  };
41676
41196
  }).pipe(withSpan("runInspect", { attributes: {
@@ -44050,5 +43570,5 @@ const startLanguageServer = () => {
44050
43570
  };
44051
43571
  //#endregion
44052
43572
  export { startLanguageServer };
44053
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="17a3f533-8248-511a-859b-584197cefba6")}catch(e){}}();
44054
- //# debugId=17a3f533-8248-511a-859b-584197cefba6
43573
+ !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]="94a57ec3-d5a9-52ea-a743-e91d996065b8")}catch(e){}}();
43574
+ //# debugId=94a57ec3-d5a9-52ea-a743-e91d996065b8