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

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,15 +32986,11 @@ 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
32989
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
32993
- const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
32994
32990
  const SUPPLY_CHAIN_PLUGIN = "socket";
32995
32991
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
32996
32992
  const SUPPLY_CHAIN_CATEGORY = "Security";
32997
32993
  const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
32998
- const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
32999
32994
  const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
33000
32995
  const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
33001
32996
  const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
@@ -35653,29 +35648,6 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
35653
35648
  return true;
35654
35649
  } }) {};
35655
35650
  /**
35656
- * Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
35657
- * active. Defaults ON — warm rescans replay the cross-file rules' cached
35658
- * diagnostics for every file whose dependency fingerprint (the probe set the
35659
- * plugin's collectors recorded) still matches the tree, and re-lint only the
35660
- * rest. Only reachable when the per-file lint cache itself is on (the
35661
- * sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
35662
- * `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
35663
- *
35664
- * - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
35665
- * - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
35666
- * per-file cache but run the always-fresh sidecar over every hit
35667
- * (the pre-cache behavior).
35668
- *
35669
- * Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
35670
- */
35671
- var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
35672
- const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
35673
- const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
35674
- if (CACHE_DISABLED_VALUES.has(noCache)) return false;
35675
- if (CACHE_DISABLED_VALUES.has(noSidecarCache)) return false;
35676
- return true;
35677
- } }) {};
35678
- /**
35679
35651
  * Whether the whole-project dead-code result cache
35680
35652
  * (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
35681
35653
  * whose inputs (source tree, manifests, configs, analyzer version) are
@@ -37351,37 +37323,51 @@ const failOpenReadJson = (filePath, fallback) => {
37351
37323
  return fallback;
37352
37324
  }
37353
37325
  };
37354
- const hashFileContents = (filePath) => {
37355
- try {
37356
- return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
37357
- } catch {
37358
- return null;
37359
- }
37360
- };
37361
- const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
37362
- const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
37363
- const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
37326
+ const ANALYZED_FILE_EXTENSIONS = new Set([
37327
+ ".ts",
37328
+ ".tsx",
37329
+ ".js",
37330
+ ".jsx",
37331
+ ".mts",
37332
+ ".mjs",
37333
+ ".cts",
37334
+ ".cjs",
37335
+ ".mdx",
37336
+ ".astro",
37337
+ ".graphql",
37338
+ ".gql",
37339
+ ".css",
37340
+ ".scss",
37341
+ ".vue",
37342
+ ".svelte"
37343
+ ]);
37344
+ const ANALYZED_MANIFEST_NAMES = new Set([
37345
+ "package.json",
37346
+ "knip.json",
37347
+ "app.json",
37348
+ "pnpm-workspace.yaml",
37349
+ "lerna.json",
37350
+ "pnpm-lock.yaml",
37351
+ "package-lock.json",
37352
+ "yarn.lock",
37353
+ "bun.lock",
37354
+ "bun.lockb",
37355
+ "deno.lock",
37356
+ ".gitignore"
37357
+ ]);
37364
37358
  const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
37365
37359
  const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
37366
- /**
37367
- * Stat snapshot of every file the analysis reads, keyed by root-relative
37368
- * `/`-separated path. Taken BEFORE the (long) analysis so a stored result is
37369
- * verified against the tree it started from.
37370
- */
37371
- const collectAnalyzedFileStats = (rootDirectory) => {
37372
- const statByRelativePath = /* @__PURE__ */ new Map();
37360
+ const collectAnalyzedFileFingerprints = (rootDirectory) => {
37361
+ const fingerprints = [];
37373
37362
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
37374
37363
  if (!isFingerprintedFile(name)) continue;
37375
37364
  try {
37376
37365
  const fileStat = NFS.statSync(absolutePath);
37377
37366
  const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
37378
- statByRelativePath.set(relativePath, {
37379
- mtimeMs: fileStat.mtimeMs,
37380
- size: fileStat.size
37381
- });
37367
+ fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
37382
37368
  } catch {}
37383
37369
  }
37384
- return statByRelativePath;
37370
+ return fingerprints.sort();
37385
37371
  };
37386
37372
  const bundledRequire$2 = createRequire(import.meta.url);
37387
37373
  const resolveDeslopVersion = () => {
@@ -37393,85 +37379,33 @@ const resolveDeslopVersion = () => {
37393
37379
  }
37394
37380
  };
37395
37381
  const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
37396
- schemaVersion: 2,
37397
- coreVersion: input.coreVersion,
37382
+ schemaVersion: 1,
37398
37383
  deslopVersion: resolveDeslopVersion(),
37399
37384
  deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
37400
37385
  entryPatterns: input.entryPatterns,
37401
37386
  ignorePatterns: input.ignorePatterns,
37402
- tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
37387
+ tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
37388
+ files: collectAnalyzedFileFingerprints(input.rootDirectory)
37403
37389
  })).digest("hex");
37404
- const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
37390
+ const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
37405
37391
  const decodeCachedDiagnostics = (raw) => {
37406
37392
  try {
37407
- for (const entry of raw) validateDiagnostic$2(entry);
37393
+ for (const entry of raw) validateDiagnostic$1(entry);
37408
37394
  return raw;
37409
37395
  } catch {
37410
37396
  return null;
37411
37397
  }
37412
37398
  };
37413
- const isPersistedFileIdentity = (value) => Array.isArray(value) && value.length === 3 && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "string";
37414
- const lookupDeadCodeResultCache = (input) => {
37415
- const cacheFilePath = Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME);
37416
- const persisted = failOpenReadJson(cacheFilePath, null);
37417
- if (persisted === null || !isRecord(persisted) || persisted.version !== 2 || persisted.key !== input.cacheKey || !isRecord(persisted.files) || !Array.isArray(persisted.diagnostics)) return null;
37418
- const storedFileEntries = Object.entries(persisted.files);
37419
- if (storedFileEntries.length !== input.currentFileStats.size) return null;
37420
- const repairedFiles = {};
37421
- let repairedCount = 0;
37422
- for (const [relativePath, storedIdentity] of storedFileEntries) {
37423
- if (!isPersistedFileIdentity(storedIdentity)) return null;
37424
- const currentStat = input.currentFileStats.get(relativePath);
37425
- if (currentStat === void 0) return null;
37426
- const [storedMtimeMs, storedSize, storedContentHash] = storedIdentity;
37427
- if (currentStat.mtimeMs === storedMtimeMs && currentStat.size === storedSize) {
37428
- repairedFiles[relativePath] = storedIdentity;
37429
- continue;
37430
- }
37431
- if (currentStat.size !== storedSize) return null;
37432
- const currentContentHash = hashFileContents(Path.join(input.rootDirectory, relativePath));
37433
- if (currentContentHash === null || currentContentHash !== storedContentHash) return null;
37434
- repairedFiles[relativePath] = [
37435
- currentStat.mtimeMs,
37436
- currentStat.size,
37437
- storedContentHash
37438
- ];
37439
- repairedCount += 1;
37440
- }
37441
- const diagnostics = decodeCachedDiagnostics(persisted.diagnostics);
37442
- if (diagnostics === null) return null;
37443
- if (repairedCount > 0) atomicWriteJson(cacheFilePath, {
37444
- version: 2,
37445
- key: input.cacheKey,
37446
- files: repairedFiles,
37447
- diagnostics: persisted.diagnostics
37448
- });
37449
- return diagnostics;
37399
+ const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
37400
+ const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
37401
+ if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
37402
+ return decodeCachedDiagnostics(persisted.diagnostics);
37450
37403
  };
37451
- const storeDeadCodeResultCache = (input) => {
37452
- const persistedFiles = {};
37453
- for (const [relativePath, snapshotStat] of input.snapshotFileStats) {
37454
- const absolutePath = Path.join(input.rootDirectory, relativePath);
37455
- const contentHash = hashFileContents(absolutePath);
37456
- if (contentHash === null) return;
37457
- let currentStat;
37458
- try {
37459
- currentStat = NFS.statSync(absolutePath);
37460
- } catch {
37461
- return;
37462
- }
37463
- if (currentStat.mtimeMs !== snapshotStat.mtimeMs || currentStat.size !== snapshotStat.size) return;
37464
- persistedFiles[relativePath] = [
37465
- snapshotStat.mtimeMs,
37466
- snapshotStat.size,
37467
- contentHash
37468
- ];
37469
- }
37470
- atomicWriteJson(Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37471
- version: 2,
37472
- key: input.cacheKey,
37473
- files: persistedFiles,
37474
- diagnostics: input.diagnostics
37404
+ const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
37405
+ atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37406
+ version: 1,
37407
+ key: cacheKey,
37408
+ diagnostics
37475
37409
  });
37476
37410
  };
37477
37411
  const readSystemFacts = () => ({
@@ -37610,14 +37544,6 @@ process.stdin.on("end", () => {
37610
37544
  circularDependencies: result.circularDependencies.map((cycle) => ({
37611
37545
  files: cycle.files,
37612
37546
  })),
37613
- ...(result.incrementalCacheStats
37614
- ? {
37615
- summaryCacheStats: {
37616
- hits: result.incrementalCacheStats.summaryHits,
37617
- misses: result.incrementalCacheStats.summaryMisses,
37618
- },
37619
- }
37620
- : {}),
37621
37547
  });
37622
37548
 
37623
37549
  const serializeError = (error) =>
@@ -37641,9 +37567,6 @@ process.stdin.on("end", () => {
37641
37567
  ...(workerInput.ignorePatterns.length > 0
37642
37568
  ? { ignorePatterns: workerInput.ignorePatterns }
37643
37569
  : {}),
37644
- ...(workerInput.incrementalCachePath
37645
- ? { incrementalCachePath: workerInput.incrementalCachePath }
37646
- : {}),
37647
37570
  // We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
37648
37571
  // unusedDependencies, circularDependencies). Everything else deslop can compute
37649
37572
  // is pure wasted work for us, and it's the bulk of the runtime:
@@ -37654,18 +37577,12 @@ process.stdin.on("end", () => {
37654
37577
  // are the single most expensive pass — duplicate-block detection alone was
37655
37578
  // ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
37656
37579
  // speedup on a large repo.
37657
- // - reportRedundancy: the DRY-pattern detectors (duplicate types/constants,
37658
- // simplifiable functions, identity wrappers, …) — ~120 ms of discarded
37659
- // output per scan, and skipping them lets the incremental cache drop the
37660
- // DRY-pattern summary fields (the largest slice of the cache file).
37661
- // All are provably safe: the consumed graph findings are computed by their own
37580
+ // Both are provably safe: the consumed graph findings are computed by their own
37662
37581
  // detectors, independent of these passes (confirmed byte-identical on
37663
- // excalidraw + mui-material + sentry; re-verified on sentry after the
37664
- // reportRedundancy flip). tsConfigPath stays — the module resolver needs it
37665
- // for path-alias resolution in the import graph.
37582
+ // excalidraw + mui-material + sentry). tsConfigPath stays — the module resolver
37583
+ // needs it for path-alias resolution in the import graph.
37666
37584
  semantic: { enabled: false },
37667
37585
  reportCodeQuality: false,
37668
- reportRedundancy: false,
37669
37586
  };
37670
37587
  const result = await analyze(defineConfig(config));
37671
37588
  emit({ ok: true, result: normalizeResult(result) });
@@ -37749,23 +37666,13 @@ const parseCircularDependencies = (value) => {
37749
37666
  }
37750
37667
  return circularDependencies;
37751
37668
  };
37752
- const parseSummaryCacheStats = (value) => {
37753
- if (!isRecord(value)) return void 0;
37754
- if (typeof value.hits !== "number" || typeof value.misses !== "number") return void 0;
37755
- return {
37756
- hits: value.hits,
37757
- misses: value.misses
37758
- };
37759
- };
37760
37669
  const parseDeadCodeWorkerResult = (value) => {
37761
37670
  if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
37762
- const summaryCacheStats = parseSummaryCacheStats(value.summaryCacheStats);
37763
37671
  return {
37764
37672
  unusedFiles: parseUnusedFiles(value.unusedFiles),
37765
37673
  unusedExports: parseUnusedExports(value.unusedExports),
37766
37674
  unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
37767
- circularDependencies: parseCircularDependencies(value.circularDependencies),
37768
- ...summaryCacheStats ? { summaryCacheStats } : {}
37675
+ circularDependencies: parseCircularDependencies(value.circularDependencies)
37769
37676
  };
37770
37677
  };
37771
37678
  const parseDeadCodeWorkerError = (value) => {
@@ -37881,26 +37788,18 @@ const checkDeadCode = async (options) => {
37881
37788
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37882
37789
  const tsConfigPath = resolveTsConfigPath(rootDirectory);
37883
37790
  const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
37884
- const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
37885
- const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
37791
+ const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
37886
37792
  rootDirectory,
37887
37793
  entryPatterns,
37888
37794
  ignorePatterns,
37889
37795
  tsConfigPath,
37890
- deslopJsModuleSpecifier,
37891
- coreVersion: CORE_PACKAGE_VERSION
37892
- });
37893
- if (cacheKey !== null && fileStatsSnapshot !== null) {
37894
- const cachedDiagnostics = lookupDeadCodeResultCache({
37895
- cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
37896
- cacheKey,
37897
- rootDirectory,
37898
- currentFileStats: fileStatsSnapshot
37899
- });
37796
+ deslopJsModuleSpecifier
37797
+ }) : null;
37798
+ if (cacheKey !== null) {
37799
+ const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
37900
37800
  options.onCacheOutcome?.(cachedDiagnostics !== null);
37901
37801
  if (cachedDiagnostics !== null) return [...cachedDiagnostics];
37902
37802
  }
37903
- const incrementalCachePath = options.cacheEnabled === true ? Path.join(resolveReactDoctorCacheDir(rootDirectory), DEAD_CODE_SUMMARY_CACHE_FILENAME) : void 0;
37904
37803
  const spawnAndRun = () => {
37905
37804
  return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37906
37805
  rootDirectory,
@@ -37908,12 +37807,10 @@ const checkDeadCode = async (options) => {
37908
37807
  tsConfigPath,
37909
37808
  ignorePatterns,
37910
37809
  deslopJsModuleSpecifier,
37911
- parseConcurrency: options.parseConcurrency,
37912
- incrementalCachePath
37810
+ parseConcurrency: options.parseConcurrency
37913
37811
  }), options.workerTimeoutMs ?? 12e4, options.abortSignal);
37914
37812
  };
37915
37813
  const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
37916
- if (result.summaryCacheStats !== void 0) options.onSummaryCacheStats?.(result.summaryCacheStats);
37917
37814
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
37918
37815
  const diagnostics = [];
37919
37816
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -37970,13 +37867,7 @@ const checkDeadCode = async (options) => {
37970
37867
  category: DEAD_CODE_CATEGORY
37971
37868
  });
37972
37869
  }
37973
- if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
37974
- cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
37975
- cacheKey,
37976
- rootDirectory,
37977
- snapshotFileStats: fileStatsSnapshot,
37978
- diagnostics
37979
- });
37870
+ if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
37980
37871
  return diagnostics;
37981
37872
  };
37982
37873
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -38087,8 +37978,7 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
38087
37978
  workerTimeoutMs: input.workerTimeoutMs,
38088
37979
  abortSignal: signal,
38089
37980
  cacheEnabled,
38090
- onCacheOutcome: input.onCacheOutcome,
38091
- onSummaryCacheStats: input.onSummaryCacheStats
37981
+ onCacheOutcome: input.onCacheOutcome
38092
37982
  }),
38093
37983
  catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
38094
37984
  }).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
@@ -38958,7 +38848,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38958
38848
  }
38959
38849
  return enabled;
38960
38850
  };
38961
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
38851
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
38962
38852
  const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38963
38853
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38964
38854
  const jsPlugins = [];
@@ -38970,7 +38860,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38970
38860
  if (!rule) continue;
38971
38861
  if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
38972
38862
  if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
38973
- if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
38974
38863
  if (rule.scan !== void 0) continue;
38975
38864
  if (customRulesOnly && registryEntry.originallyExternal) continue;
38976
38865
  if (rule.framework !== "global" && !rule.requires) continue;
@@ -39015,23 +38904,23 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
39015
38904
  }
39016
38905
  };
39017
38906
  };
39018
- const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
38907
+ const validateDiagnostic = decodeUnknownSync(Diagnostic);
39019
38908
  const decodeFileDiagnostics = (raw) => {
39020
38909
  if (!Array.isArray(raw)) return null;
39021
38910
  try {
39022
- for (const entry of raw) validateDiagnostic$1(entry);
38911
+ for (const entry of raw) validateDiagnostic(entry);
39023
38912
  return raw;
39024
38913
  } catch {
39025
38914
  return null;
39026
38915
  }
39027
38916
  };
39028
- const emptyCache$1 = () => ({
38917
+ const emptyCache = () => ({
39029
38918
  version: 1,
39030
38919
  rulesets: {}
39031
38920
  });
39032
- const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
38921
+ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
39033
38922
  const entries = /* @__PURE__ */ new Map();
39034
- const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
38923
+ const persisted = failOpenReadJson(cacheFilePath, emptyCache());
39035
38924
  if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
39036
38925
  const bucket = persisted.rulesets[rulesetHash];
39037
38926
  if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
@@ -39043,149 +38932,20 @@ const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
39043
38932
  };
39044
38933
  const createFileLintCache = (cacheDirectory, rulesetHash) => {
39045
38934
  const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
39046
- const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
38935
+ const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
39047
38936
  return {
39048
38937
  lookup: (fileKey) => entries.get(fileKey) ?? null,
39049
38938
  store: (fileKey, diagnostics) => {
39050
38939
  entries.delete(fileKey);
39051
38940
  entries.set(fileKey, diagnostics);
39052
38941
  },
39053
- persist: () => {
39054
- const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
39055
- const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
39056
- const existingBucket = rulesets[rulesetHash];
39057
- const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
39058
- const ourFiles = {};
39059
- for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
39060
- const cappedEntries = Object.entries({
39061
- ...existingFiles,
39062
- ...ourFiles
39063
- }).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
39064
- rulesets[rulesetHash] = {
39065
- updatedAtMs: Date.now(),
39066
- files: Object.fromEntries(cappedEntries)
39067
- };
39068
- atomicWriteJson(cacheFilePath, {
39069
- version: 1,
39070
- rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
39071
- });
39072
- }
39073
- };
39074
- };
39075
- const ABSENT_CONTENT_ANSWER = "absent";
39076
- /**
39077
- * Answers dependency probes for the sidecar lint cache — used both when
39078
- * STORING an entry (turn the collector's probe paths into answers) and when
39079
- * VERIFYING one (re-answer each stored probe against the current tree).
39080
- * Store and verify must agree exactly, so both go through this one resolver.
39081
- *
39082
- * - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
39083
- * (missing, a directory, permission failure). Source files reuse the
39084
- * hashes the cache-partition loop already computed
39085
- * (`contentHashByRelativePath`); everything else (package.json,
39086
- * tsconfig, ignored files) is hashed on demand.
39087
- * - `exists` — the existence class a module-resolution candidate probe
39088
- * sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
39089
- * `statSync` follows symlinks, matching the resolvers).
39090
- *
39091
- * Answers are memoized per scan: the filesystem is frozen for the run, and
39092
- * hot dependency paths (a barrel imported by hundreds of files, the shared
39093
- * tsconfig chain) repeat across many files' probe sets.
39094
- */
39095
- const createSidecarProbeAnswerResolver = (input) => {
39096
- const contentAnswerByPath = /* @__PURE__ */ new Map();
39097
- const existsAnswerByPath = /* @__PURE__ */ new Map();
39098
- const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
39099
- const contentAnswer = (relativePath) => {
39100
- const memoized = contentAnswerByPath.get(relativePath);
39101
- if (memoized !== void 0) return memoized;
39102
- const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
39103
- contentAnswerByPath.set(relativePath, answer);
39104
- return answer;
39105
- };
39106
- const existsAnswer = (relativePath) => {
39107
- const memoized = existsAnswerByPath.get(relativePath);
39108
- if (memoized !== void 0) return memoized;
39109
- let answer;
39110
- try {
39111
- const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
39112
- answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
39113
- } catch {
39114
- answer = "none";
39115
- }
39116
- existsAnswerByPath.set(relativePath, answer);
39117
- return answer;
39118
- };
39119
- return {
39120
- answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
39121
- toRelativePath
39122
- };
39123
- };
39124
- const validateDiagnostic = decodeUnknownSync(Diagnostic);
39125
- const isProbeKind = (value) => value === "content" || value === "exists";
39126
- const decodeSidecarFileEntry = (raw) => {
39127
- if (!isRecord(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
39128
- const probes = [];
39129
- for (const rawProbe of raw.probes) {
39130
- if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
39131
- const [kind, probePath, answer] = rawProbe;
39132
- if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
39133
- probes.push({
39134
- kind,
39135
- path: probePath,
39136
- answer
39137
- });
39138
- }
39139
- try {
39140
- for (const entry of raw.diagnostics) validateDiagnostic(entry);
39141
- } catch {
39142
- return null;
39143
- }
39144
- return {
39145
- probes,
39146
- diagnostics: raw.diagnostics
39147
- };
39148
- };
39149
- const encodeSidecarFileEntry = (entry) => ({
39150
- probes: entry.probes.map((probe) => [
39151
- probe.kind,
39152
- probe.path,
39153
- probe.answer
39154
- ]),
39155
- diagnostics: entry.diagnostics
39156
- });
39157
- const emptyCache = () => ({
39158
- version: 1,
39159
- rulesets: {}
39160
- });
39161
- const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
39162
- const entries = /* @__PURE__ */ new Map();
39163
- const persisted = failOpenReadJson(cacheFilePath, emptyCache());
39164
- if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
39165
- const bucket = persisted.rulesets[rulesetHash];
39166
- if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
39167
- for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
39168
- const decoded = decodeSidecarFileEntry(rawEntry);
39169
- if (decoded !== null) entries.set(fileKey, decoded);
39170
- }
39171
- return entries;
39172
- };
39173
- const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
39174
- const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
39175
- const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
39176
- return {
39177
- lookup: (fileKey) => entries.get(fileKey) ?? null,
39178
- store: (fileKey, entry) => {
39179
- entries.delete(fileKey);
39180
- entries.set(fileKey, entry);
39181
- },
39182
38942
  persist: () => {
39183
38943
  const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
39184
38944
  const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
39185
38945
  const existingBucket = rulesets[rulesetHash];
39186
38946
  const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
39187
38947
  const ourFiles = {};
39188
- for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
38948
+ for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
39189
38949
  const cappedEntries = Object.entries({
39190
38950
  ...existingFiles,
39191
38951
  ...ourFiles
@@ -40200,6 +39960,13 @@ const validateRuleRegistration = () => {
40200
39960
  ].filter((entry) => entry !== null).join("; ");
40201
39961
  console.warn(`[react-doctor] rule-registration drift: ${detail}`);
40202
39962
  };
39963
+ const hashFileContents = (filePath) => {
39964
+ try {
39965
+ return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
39966
+ } catch {
39967
+ return null;
39968
+ }
39969
+ };
40203
39970
  /**
40204
39971
  * Balanced LPT (longest-processing-time-first) lint batch planner. Where
40205
39972
  * `batchIncludePaths` greedily fills 100-file batches and leaves a small
@@ -40281,106 +40048,6 @@ const writeOxlintConfig = (configPath, configToWrite) => {
40281
40048
  NFS.closeSync(fileHandle);
40282
40049
  }
40283
40050
  };
40284
- /**
40285
- * Attributes diagnostics back to the file that produced them by the
40286
- * normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
40287
- * attributed — the path forms don't align, so the caller must skip its cache
40288
- * store rather than risk caching a wrong empty result for a file that
40289
- * actually had diagnostics.
40290
- */
40291
- const attributeDiagnosticsToFiles = (diagnostics, files) => {
40292
- const fileByNormalizedPath = /* @__PURE__ */ new Map();
40293
- for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
40294
- const diagnosticsByFile = /* @__PURE__ */ new Map();
40295
- for (const diagnostic of diagnostics) {
40296
- const file = fileByNormalizedPath.get(diagnostic.filePath);
40297
- if (file === void 0) return null;
40298
- const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
40299
- fileDiagnostics.push(diagnostic);
40300
- diagnosticsByFile.set(file, fileDiagnostics);
40301
- }
40302
- return diagnosticsByFile;
40303
- };
40304
- /**
40305
- * Collects each file's cross-file dependency probes (via the plugin's
40306
- * collectors) with their current answers. Fail-open per file: an unreadable /
40307
- * unparseable / collector-failing file maps to `null` (no entry stored — it
40308
- * re-lints next scan). The filesystem is frozen for the scan, so this can run
40309
- * CONCURRENTLY with an oxlint child-process await — the caller starts it
40310
- * unawaited before a lint pass and awaits it at store time, hiding the
40311
- * in-process parse + resolution cost under the subprocess wait. The loop
40312
- * yields on the cooperative time budget so the child's stdout handling (and
40313
- * sibling fibers) keep flowing; each per-file collection is synchronous, so
40314
- * the module-level probe recorder never sees interleaved use. Never rejects.
40315
- */
40316
- const collectSidecarProbesForFiles = async (input) => {
40317
- const buildProbes = (file) => {
40318
- const absoluteFilePath = Path.resolve(input.rootDirectory, file);
40319
- let trace;
40320
- try {
40321
- trace = collectCrossFileDependencyProbes({
40322
- absoluteFilePath,
40323
- sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
40324
- ruleIds: input.boundedSidecarRuleIds
40325
- });
40326
- } catch {
40327
- return null;
40328
- }
40329
- if (trace === null) return null;
40330
- const probes = [];
40331
- for (const contentPath of trace.contentPaths) {
40332
- const relativePath = input.probeAnswers.toRelativePath(contentPath);
40333
- probes.push({
40334
- kind: "content",
40335
- path: relativePath,
40336
- answer: input.probeAnswers.answerFor("content", relativePath)
40337
- });
40338
- }
40339
- for (const existencePath of trace.existencePaths) {
40340
- const relativePath = input.probeAnswers.toRelativePath(existencePath);
40341
- probes.push({
40342
- kind: "exists",
40343
- path: relativePath,
40344
- answer: input.probeAnswers.answerFor("exists", relativePath)
40345
- });
40346
- }
40347
- return probes;
40348
- };
40349
- const probesByFile = /* @__PURE__ */ new Map();
40350
- let collectSliceStartedAt = performance.now();
40351
- for (const file of input.files) {
40352
- probesByFile.set(file, buildProbes(file));
40353
- if (performance.now() - collectSliceStartedAt >= 12) {
40354
- await yieldToEventLoop();
40355
- collectSliceStartedAt = performance.now();
40356
- }
40357
- }
40358
- return probesByFile;
40359
- };
40360
- /**
40361
- * Stores each freshly-linted file's probe set + cross-file diagnostics so
40362
- * the next scan can replay instead of re-linting.
40363
- */
40364
- const storeSidecarEntries = (input) => {
40365
- let didStoreAnyEntry = false;
40366
- for (const pass of input.storablePasses) {
40367
- if (!pass.isTrusted) continue;
40368
- const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
40369
- if (diagnosticsByFile === null) continue;
40370
- for (const file of pass.files) {
40371
- const cacheKey = input.cacheKeyByFile.get(file);
40372
- if (cacheKey === void 0) continue;
40373
- const probes = pass.probesByFile.get(file);
40374
- if (probes === void 0 || probes === null) continue;
40375
- input.sidecarCache.store(cacheKey, {
40376
- probes,
40377
- diagnostics: diagnosticsByFile.get(file) ?? []
40378
- });
40379
- didStoreAnyEntry = true;
40380
- }
40381
- }
40382
- if (didStoreAnyEntry) input.sidecarCache.persist();
40383
- };
40384
40051
  const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
40385
40052
  /**
40386
40053
  * Detects an oxlint config-load crash caused by the optional
@@ -40422,7 +40089,7 @@ const reactHooksJsPluginDropNote = (error) => {
40422
40089
  * 6. always restore disable directives + clean up the temp dir
40423
40090
  */
40424
40091
  const runOxlint = async (options) => {
40425
- const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
40092
+ const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
40426
40093
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
40427
40094
  const severityControls = buildRuleSeverityControls(userConfig);
40428
40095
  validateRuleRegistration();
@@ -40440,8 +40107,7 @@ const runOxlint = async (options) => {
40440
40107
  severityControls,
40441
40108
  userPlugins,
40442
40109
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
40443
- ruleSelection: overrides.ruleSelection,
40444
- sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
40110
+ ruleSelection: overrides.ruleSelection
40445
40111
  });
40446
40112
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
40447
40113
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -40544,18 +40210,15 @@ const runOxlint = async (options) => {
40544
40210
  });
40545
40211
  const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
40546
40212
  const cacheKeyByFile = /* @__PURE__ */ new Map();
40547
- const contentHashByRelativePath = /* @__PURE__ */ new Map();
40548
40213
  const missFiles = [];
40549
40214
  const replayedDiagnostics = [];
40550
40215
  let hashSliceStartedAt = performance.now();
40551
40216
  for (const candidateFile of candidateFiles) {
40552
- const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
40553
- const contentHash = hashFileContents(absoluteCandidatePath);
40217
+ const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
40554
40218
  if (contentHash === null) {
40555
40219
  missFiles.push(candidateFile);
40556
40220
  continue;
40557
40221
  }
40558
- contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
40559
40222
  const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
40560
40223
  cacheKeyByFile.set(candidateFile, cacheKey);
40561
40224
  const cachedDiagnostics = cache.lookup(cacheKey);
@@ -40567,111 +40230,43 @@ const runOxlint = async (options) => {
40567
40230
  }
40568
40231
  }
40569
40232
  const cacheHitFileCount = candidateFiles.length - missFiles.length;
40570
- const enabledSidecarRuleIds = Object.keys(buildConfig({
40571
- extendsPaths: [],
40572
- ruleSelection: "sidecar"
40573
- }).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
40574
- const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
40575
- const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
40576
- const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
40577
- const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
40578
- const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
40579
- config: buildConfig({
40580
- extendsPaths: [],
40581
- ruleSelection: "sidecar",
40582
- sidecarRuleIdFilter: boundedSidecarRuleIdSet
40583
- }),
40584
- toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
40585
- ignorePatterns: combinedPatterns,
40586
- tsconfigContent
40587
- })) : null;
40588
- const probeAnswers = createSidecarProbeAnswerResolver({
40589
- rootDirectory,
40590
- contentHashByRelativePath
40591
- });
40592
- const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
40593
- files: missFiles,
40594
- rootDirectory,
40595
- boundedSidecarRuleIds,
40596
- probeAnswers
40597
- });
40598
40233
  const fullResult = await runConfigOverFiles((overrides) => buildConfig({
40599
40234
  extendsPaths: [],
40600
40235
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
40601
40236
  }), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
40602
40237
  const missFileSet = new Set(missFiles);
40603
- const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
40604
- const sidecarReplayedDiagnostics = [];
40605
- const sidecarLintFiles = [];
40606
- if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
40607
- else {
40608
- let verifySliceStartedAt = performance.now();
40609
- for (const hitFile of hitFiles) {
40610
- const cacheKey = cacheKeyByFile.get(hitFile);
40611
- const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
40612
- if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
40613
- else sidecarLintFiles.push(hitFile);
40614
- if (performance.now() - verifySliceStartedAt >= 12) {
40615
- await yieldToEventLoop();
40616
- verifySliceStartedAt = performance.now();
40617
- }
40618
- }
40619
- }
40620
- const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
40621
- files: sidecarLintFiles,
40622
- rootDirectory,
40623
- boundedSidecarRuleIds,
40624
- probeAnswers
40625
- });
40626
- const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
40627
- if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
40628
- const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
40629
- extendsPaths: [],
40630
- ruleSelection: "sidecar",
40631
- ...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
40632
- }), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
40633
- const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
40238
+ const sidecarResult = await runConfigOverFiles(() => buildConfig({
40634
40239
  extendsPaths: [],
40635
- ruleSelection: "sidecar",
40636
- sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
40637
- }), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
40638
- diagnostics: [],
40639
- didDropReactHooksJsPlugin: false,
40640
- hadPartialFailure: false
40641
- };
40240
+ ruleSelection: "sidecar"
40241
+ }), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
40642
40242
  const freshCacheableDiagnostics = [];
40643
40243
  for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
40644
40244
  onCacheStats?.(cacheHitFileCount, candidateFiles.length);
40645
- if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
40646
- const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
40647
- if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
40245
+ const missFileByNormalizedPath = /* @__PURE__ */ new Map();
40246
+ for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
40247
+ const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
40248
+ let isAttributionSound = true;
40249
+ for (const diagnostic of freshCacheableDiagnostics) {
40250
+ const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
40251
+ if (missFile === void 0) {
40252
+ isAttributionSound = false;
40253
+ break;
40254
+ }
40255
+ const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
40256
+ fileDiagnostics.push(diagnostic);
40257
+ freshDiagnosticsByFile.set(missFile, fileDiagnostics);
40258
+ }
40259
+ if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
40648
40260
  for (const missFile of missFiles) {
40649
40261
  const cacheKey = cacheKeyByFile.get(missFile);
40650
40262
  if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
40651
40263
  }
40652
40264
  cache.persist();
40653
40265
  }
40654
- if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
40655
- sidecarCache,
40656
- cacheKeyByFile,
40657
- storablePasses: [{
40658
- files: sidecarLintFiles,
40659
- diagnostics: boundedSidecarResult.diagnostics,
40660
- isTrusted: !boundedSidecarResult.hadPartialFailure,
40661
- probesByFile: await sidecarProbesTask
40662
- }, {
40663
- files: missFiles,
40664
- diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
40665
- isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
40666
- probesByFile: await missProbesTask
40667
- }]
40668
- });
40669
40266
  return dedupeDiagnostics([
40670
40267
  ...replayedDiagnostics,
40671
40268
  ...fullResult.diagnostics,
40672
- ...sidecarReplayedDiagnostics,
40673
- ...boundedSidecarResult.diagnostics,
40674
- ...unboundedSidecarResult.diagnostics
40269
+ ...sidecarResult.diagnostics
40675
40270
  ]);
40676
40271
  }
40677
40272
  const baseArgs = makeBaseArgs(configPath);
@@ -40769,7 +40364,6 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
40769
40364
  const concurrency = yield* OxlintConcurrency;
40770
40365
  const lintBatchOrdering = yield* LintBatchOrdering;
40771
40366
  const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
40772
- const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
40773
40367
  const collectedFailures = [];
40774
40368
  const diagnostics = yield* tryPromise({
40775
40369
  try: (signal) => runOxlint({
@@ -40788,9 +40382,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
40788
40382
  },
40789
40383
  onFileProgress: input.onFileProgress,
40790
40384
  perFileLintCacheEnabled,
40791
- sidecarLintCacheEnabled,
40792
40385
  onCacheStats: input.onCacheStats,
40793
- onSidecarStats: input.onSidecarStats,
40794
40386
  spawnTimeoutMs,
40795
40387
  outputMaxBytes,
40796
40388
  concurrency,
@@ -41212,20 +40804,6 @@ const writeCachedSocketBody = (cacheFile, body) => {
41212
40804
  }));
41213
40805
  } catch {}
41214
40806
  };
41215
- const pruneExpiredSocketCache = (cacheDirectory) => {
41216
- try {
41217
- const supplyChainCacheDirectory = Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR);
41218
- const expiryThresholdMs = Date.now() - SUPPLY_CHAIN_CACHE_TTL_MS;
41219
- for (const entryName of NFS.readdirSync(supplyChainCacheDirectory)) {
41220
- const entryPath = Path.join(supplyChainCacheDirectory, entryName);
41221
- try {
41222
- if (NFS.statSync(entryPath).mtimeMs < expiryThresholdMs) NFS.rmSync(entryPath);
41223
- } catch {
41224
- continue;
41225
- }
41226
- }
41227
- } catch {}
41228
- };
41229
40807
  const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
41230
40808
  const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
41231
40809
  if (cacheFile !== null) {
@@ -41353,7 +40931,6 @@ const checkSupplyChain = (input) => gen(function* () {
41353
40931
  const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
41354
40932
  if (dependencies.length === 0) return [];
41355
40933
  const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
41356
- if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
41357
40934
  const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
41358
40935
  const diagnostics = [];
41359
40936
  for (let index = 0; index < dependencies.length; index += 1) {
@@ -41583,10 +41160,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41583
41160
  workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
41584
41161
  onCacheOutcome: (didHitCache) => {
41585
41162
  deadCodeCacheHit = didHitCache;
41586
- },
41587
- onSummaryCacheStats: (stats) => {
41588
- deadCodeSummaryCacheHits = stats.hits;
41589
- deadCodeSummaryCacheMisses = stats.misses;
41590
41163
  }
41591
41164
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41592
41165
  yield* set(deadCodeFailure, {
@@ -41615,11 +41188,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41615
41188
  let lastReportedTotalFileCount = 0;
41616
41189
  let lintCacheHitFileCount = null;
41617
41190
  let lintCacheTotalFileCount = null;
41618
- let lintSidecarReplayedFileCount = null;
41619
- let lintSidecarTotalFileCount = null;
41620
41191
  let deadCodeCacheHit = null;
41621
- let deadCodeSummaryCacheHits = null;
41622
- let deadCodeSummaryCacheMisses = null;
41623
41192
  const baseLintStream = linterService.run({
41624
41193
  rootDirectory: scanDirectory,
41625
41194
  project,
@@ -41639,10 +41208,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41639
41208
  lintCacheHitFileCount = cacheHitFileCount;
41640
41209
  lintCacheTotalFileCount = totalConsideredFileCount;
41641
41210
  },
41642
- onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
41643
- lintSidecarReplayedFileCount = sidecarReplayedFileCount;
41644
- lintSidecarTotalFileCount = sidecarConsideredFileCount;
41645
- },
41646
41211
  deadlineEpochMs: input.deadlineEpochMs
41647
41212
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41648
41213
  yield* set(lintFailure, {
@@ -41753,11 +41318,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41753
41318
  securityScanFailed,
41754
41319
  lintCacheHitFileCount,
41755
41320
  lintCacheTotalFileCount,
41756
- lintSidecarReplayedFileCount,
41757
- lintSidecarTotalFileCount,
41758
41321
  deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
41759
- deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
41760
- deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
41761
41322
  suppressedRuleCounts: transform.summarizeSuppressions()
41762
41323
  };
41763
41324
  }).pipe(withSpan("runInspect", { attributes: {
@@ -42070,9 +41631,11 @@ const computeConfigFingerprint = (projectDirectory, version) => {
42070
41631
  const parts = [`v=${version}`];
42071
41632
  let directory = projectDirectory;
42072
41633
  for (;;) {
42073
- for (const filename of CONFIG_FINGERPRINT_FILENAMES) {
42074
- const contentHash = hashFileContents(path.join(directory, filename));
42075
- if (contentHash !== null) parts.push(`${directory}/${filename}=${contentHash}`);
41634
+ for (const filename of CONFIG_FINGERPRINT_FILENAMES) try {
41635
+ const stat = fs.statSync(path.join(directory, filename));
41636
+ parts.push(`${directory}/${filename}=${stat.mtimeMs}:${stat.size}`);
41637
+ } catch {
41638
+ continue;
42076
41639
  }
42077
41640
  const parent = path.dirname(directory);
42078
41641
  if (parent === directory) break;
@@ -44135,5 +43698,5 @@ const startLanguageServer = () => {
44135
43698
  };
44136
43699
  //#endregion
44137
43700
  export { startLanguageServer };
44138
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="35802bcf-f0e9-56fb-8cd9-6aee4dd99007")}catch(e){}}();
44139
- //# debugId=35802bcf-f0e9-56fb-8cd9-6aee4dd99007
43701
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="46e851cc-7b86-5c73-afb0-e09296beab28")}catch(e){}}();
43702
+ //# debugId=46e851cc-7b86-5c73-afb0-e09296beab28