react-doctor 0.7.6-dev.0d11bd5 → 0.7.6-dev.17418

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
@@ -32959,6 +32959,7 @@ const IGNORED_DIRECTORIES = new Set([
32959
32959
  "out",
32960
32960
  "storybook-static"
32961
32961
  ]);
32962
+ const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
32962
32963
  const MILLISECONDS_PER_SECOND = 1e3;
32963
32964
  const SCORE_API_URL = "https://www.react.doctor/api/score";
32964
32965
  const FETCH_TIMEOUT_MS = 1e4;
@@ -33194,15 +33195,13 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
33194
33195
  const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
33195
33196
  const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
33196
33197
  const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
33197
- const UTF8_BOM_CHARACTER = "";
33198
- const stripUtf8Bom = (content) => content.startsWith(UTF8_BOM_CHARACTER) ? content.slice(1) : content;
33199
33198
  const cachedPackageJsons = /* @__PURE__ */ new Map();
33200
33199
  const clearPackageJsonCache = () => {
33201
33200
  cachedPackageJsons.clear();
33202
33201
  };
33203
33202
  const readPackageJsonUncached = (packageJsonPath) => {
33204
33203
  try {
33205
- return JSON.parse(stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8")));
33204
+ return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
33206
33205
  } catch (error) {
33207
33206
  if (error instanceof SyntaxError) return {};
33208
33207
  if (isErrnoException(error)) {
@@ -36769,6 +36768,7 @@ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
36769
36768
  const PNPM_LOCKFILE = "pnpm-lock.yaml";
36770
36769
  const PACKAGE_JSON_FILE = "package.json";
36771
36770
  const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
36771
+ const UTF8_BOM_CHAR = "";
36772
36772
  const HARDENING_SETTING_KEYS = new Set([
36773
36773
  "minimumReleaseAge",
36774
36774
  "blockExoticSubdeps",
@@ -36793,11 +36793,12 @@ const stripInlineComment = (rawValue) => {
36793
36793
  return rawValue;
36794
36794
  };
36795
36795
  const unquote = (rawValue) => rawValue.replace(/^["']|["']$/g, "");
36796
+ const stripBom = (rawContent) => rawContent.startsWith(UTF8_BOM_CHAR) ? rawContent.slice(1) : rawContent;
36796
36797
  const parseHardeningSettings = (content) => {
36797
36798
  let minimumReleaseAge = null;
36798
36799
  let blockExoticSubdeps = null;
36799
36800
  let trustPolicy = null;
36800
- const lines = stripUtf8Bom(content).split(/\r?\n/);
36801
+ const lines = stripBom(content).split(/\r?\n/);
36801
36802
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
36802
36803
  const lineText = lines[lineIndex];
36803
36804
  if (lineText === void 0) continue;
@@ -36831,7 +36832,7 @@ const isPnpmManagedProject = (rootDirectory) => {
36831
36832
  const packageJsonPath = Path.join(rootDirectory, PACKAGE_JSON_FILE);
36832
36833
  if (!isFile(packageJsonPath)) return false;
36833
36834
  try {
36834
- const packageJsonRaw = stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8"));
36835
+ const packageJsonRaw = NFS.readFileSync(packageJsonPath, "utf-8");
36835
36836
  const packageJson = JSON.parse(packageJsonRaw);
36836
36837
  if (packageJson !== null && typeof packageJson === "object" && "packageManager" in packageJson && typeof packageJson.packageManager === "string" && packageJson.packageManager.startsWith("pnpm@")) return true;
36837
36838
  } catch {
@@ -37493,7 +37494,9 @@ const highlighter = {
37493
37494
  gray: import_picocolors.default.gray,
37494
37495
  bold: import_picocolors.default.bold
37495
37496
  };
37496
- const computeExplicitLintIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter(isLintableSourceFile) : void 0;
37497
+ const NEXT_ENTRY_FILE_PATTERN = /^(?:\.\/)?(?:src\/)?(?:middleware|proxy)\.(?:tsx?|jsx?|mts|mjs)$/;
37498
+ const shouldIncludeExplicitLintPath = (filePath, project) => JSX_FILE_PATTERN.test(filePath) || project?.framework === "nextjs" && NEXT_ENTRY_FILE_PATTERN.test(filePath);
37499
+ const computeExplicitLintIncludePaths = (includePaths, project) => includePaths.length > 0 ? includePaths.filter((filePath) => shouldIncludeExplicitLintPath(filePath, project)) : void 0;
37497
37500
  const LINGUIST_ATTRIBUTE_PATTERN = /^linguist-(?:vendored|generated)(?:=([a-zA-Z0-9]+))?$/i;
37498
37501
  const FALSY_VALUES = new Set([
37499
37502
  "false",
@@ -38335,11 +38338,11 @@ const resolveDeadCodeTimeout = (input) => {
38335
38338
  phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
38336
38339
  };
38337
38340
  };
38338
- const resolveLintIncludePaths = (rootDirectory, userConfig) => {
38341
+ const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
38339
38342
  if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
38340
38343
  const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
38341
38344
  return listSourceFiles(rootDirectory).filter((filePath) => {
38342
- if (!isLintableSourceFile(filePath)) return false;
38345
+ if (!shouldIncludeExplicitLintPath(filePath, project)) return false;
38343
38346
  return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
38344
38347
  });
38345
38348
  };
@@ -38557,7 +38560,6 @@ const parseGithubViewerPermission = (stdout) => {
38557
38560
  return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
38558
38561
  };
38559
38562
  const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
38560
- const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
38561
38563
  /**
38562
38564
  * `Git` wraps every `git`-via-subprocess call react-doctor makes
38563
38565
  * behind a `Context.Service`. The production layer (`layerNode`)
@@ -38628,14 +38630,6 @@ var Git = class Git extends Service()("react-doctor/Git") {
38628
38630
  args,
38629
38631
  directory
38630
38632
  });
38631
- const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
38632
- "ls-files",
38633
- "-z",
38634
- "--others",
38635
- "--exclude-standard",
38636
- ...includePaths.length > 0 ? ["--", ...includePaths] : []
38637
- ]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
38638
- const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
38639
38633
  const currentBranch = (directory) => runGit(directory, [
38640
38634
  "rev-parse",
38641
38635
  "--abbrev-ref",
@@ -38758,7 +38752,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38758
38752
  githubViewerPermission,
38759
38753
  branchExists,
38760
38754
  mergeBase,
38761
- diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
38755
+ diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
38762
38756
  if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
38763
38757
  if (explicitBaseBranch !== void 0) {
38764
38758
  const range = parseGitDiffRange(explicitBaseBranch);
@@ -38787,7 +38781,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38787
38781
  "HEAD"
38788
38782
  ]);
38789
38783
  if (uncommitted.status !== 0) return null;
38790
- const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
38784
+ const files = splitNullSeparated(uncommitted.stdout);
38791
38785
  if (files.length === 0) return null;
38792
38786
  return {
38793
38787
  currentBranch: resolvedCurrentBranch,
@@ -38817,7 +38811,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38817
38811
  currentBranch: resolvedCurrentBranch,
38818
38812
  baseBranch,
38819
38813
  diffBaseRef: mergeBaseRef,
38820
- changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
38814
+ changedFiles: splitNullSeparated(diff.stdout),
38821
38815
  isCurrentChanges: false
38822
38816
  };
38823
38817
  }).pipe(withSpan("Git.diffSelection")),
@@ -38863,7 +38857,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38863
38857
  stdout: result.stdout
38864
38858
  };
38865
38859
  }).pipe(withSpan("Git.grep")),
38866
- changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
38860
+ changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
38867
38861
  if (files.length === 0) return [];
38868
38862
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
38869
38863
  const result = yield* runGit(directory, [
@@ -38877,14 +38871,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38877
38871
  ...files
38878
38872
  ]);
38879
38873
  if (result.status !== 0) return null;
38880
- const changedLineRanges = parseChangedLineRanges(result.stdout);
38881
- if (cached || !includeUntracked) return changedLineRanges;
38882
- const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
38883
- if (untrackedFilePaths === null) return changedLineRanges;
38884
- return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
38885
- file,
38886
- ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
38887
- }))];
38874
+ return parseChangedLineRanges(result.stdout);
38888
38875
  }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
38889
38876
  });
38890
38877
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
@@ -39636,12 +39623,6 @@ const mapWithConcurrency = async (items, concurrency, task) => {
39636
39623
  if (errors.length > 0) throw errors[0];
39637
39624
  return results;
39638
39625
  };
39639
- const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
39640
- let lineNumber = 1;
39641
- const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
39642
- for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === 10) lineNumber++;
39643
- return lineNumber;
39644
- };
39645
39626
  const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
39646
39627
  const ACCESSORS_MIN_VERSION = "3.15.0";
39647
39628
  const supportsAccessors = (reanimatedVersion) => {
@@ -40213,6 +40194,13 @@ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
40213
40194
  visit(sourceFile);
40214
40195
  return foundRange;
40215
40196
  };
40197
+ const NEWLINE_BYTE = 10;
40198
+ const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
40199
+ let lineNumber = 1;
40200
+ const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
40201
+ for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
40202
+ return lineNumber;
40203
+ };
40216
40204
  /**
40217
40205
  * Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
40218
40206
  * whose enclosing function also carries a `react-hooks-js` (React
@@ -40393,11 +40381,11 @@ const buildRelatedLocations = (labels, filePath) => {
40393
40381
  }
40394
40382
  return related;
40395
40383
  };
40396
- const isOxlintSpan = (value) => isRecord(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
40397
- const isOxlintLabel = (value) => isRecord(value) && isOxlintSpan(value.span);
40398
- const isMappableOxlintDiagnostic = (value) => isRecord(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
40399
- const isEngineFailureDiagnostic = (value) => !isRecord(value) || typeof value.filename !== "string" || value.filename.length === 0;
40400
- const isOxlintOutput = (value) => isRecord(value) && Array.isArray(value.diagnostics);
40384
+ const isOxlintOutput = (value) => {
40385
+ if (typeof value !== "object" || value === null) return false;
40386
+ const candidate = value;
40387
+ return Array.isArray(candidate.diagnostics);
40388
+ };
40401
40389
  /**
40402
40390
  * Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
40403
40391
  * Tolerates the leading-notice-line shape oxlint sometimes prints
@@ -40416,59 +40404,23 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
40416
40404
  throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
40417
40405
  }
40418
40406
  if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
40419
- const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
40420
- if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
40421
40407
  const minifiedFileCache = /* @__PURE__ */ new Map();
40422
- const sourceBufferCache = /* @__PURE__ */ new Map();
40423
- const sourceLinesCache = /* @__PURE__ */ new Map();
40424
- const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
40425
- const readSourceBuffer = (filename) => {
40426
- const absolutePath = resolveAbsolutePath(filename);
40427
- const cached = sourceBufferCache.get(absolutePath);
40428
- if (cached !== void 0) return cached;
40429
- let sourceBuffer;
40430
- try {
40431
- sourceBuffer = NFS.readFileSync(absolutePath);
40432
- } catch {
40433
- sourceBuffer = null;
40434
- }
40435
- sourceBufferCache.set(absolutePath, sourceBuffer);
40436
- return sourceBuffer;
40437
- };
40438
- const readSourceLines = (filename) => {
40439
- const absolutePath = resolveAbsolutePath(filename);
40440
- const cached = sourceLinesCache.get(absolutePath);
40441
- if (cached !== void 0) return cached;
40442
- const sourceBuffer = readSourceBuffer(filename);
40443
- const sourceLines = sourceBuffer ? sourceBuffer.toString("utf8").split("\n") : null;
40444
- sourceLinesCache.set(absolutePath, sourceLines);
40445
- return sourceLines;
40446
- };
40447
40408
  const isMinifiedDiagnosticFile = (filename) => {
40448
- const absolutePath = resolveAbsolutePath(filename);
40409
+ const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
40449
40410
  const cached = minifiedFileCache.get(absolutePath);
40450
40411
  if (cached !== void 0) return cached;
40451
40412
  const minified = isMinifiedSource(absolutePath);
40452
40413
  minifiedFileCache.set(absolutePath, minified);
40453
40414
  return minified;
40454
40415
  };
40455
- return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
40416
+ return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
40456
40417
  const { plugin, rule } = parseRuleCode(diagnostic.code);
40457
40418
  const primaryLabel = diagnostic.labels[0];
40458
40419
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
40459
40420
  const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
40460
40421
  const primarySpan = primaryLabel?.span;
40461
- const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
40462
40422
  const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
40463
40423
  const category = resolveDiagnosticCategory(plugin, rule);
40464
- const matchByOccurrence = resolveMatchByOccurrence(rule, category);
40465
- const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
40466
- const primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
40467
- const primaryLine = sourceLines?.[primaryLineIndex];
40468
- const primaryColumnIndex = primarySpan ? primarySpan.column - 1 : -1;
40469
- const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
40470
- const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
40471
- const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
40472
40424
  return {
40473
40425
  filePath: normalizedFilePath,
40474
40426
  plugin,
@@ -40482,11 +40434,10 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
40482
40434
  column: primarySpan?.column ?? 0,
40483
40435
  ...primarySpan ? {
40484
40436
  offset: primarySpan.offset,
40485
- length: primarySpan.length,
40486
- ...primarySpanEndLine !== void 0 ? { endLine: primarySpanEndLine } : {}
40437
+ length: primarySpan.length
40487
40438
  } : {},
40488
40439
  category,
40489
- ...matchByOccurrence ? { matchByOccurrence: true } : {},
40440
+ ...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
40490
40441
  ...relatedLocations.length > 0 ? { relatedLocations } : {}
40491
40442
  };
40492
40443
  }), rootDirectory);
@@ -42273,7 +42224,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
42273
42224
  directory: scanDirectory,
42274
42225
  repo
42275
42226
  }).pipe(orElseSucceed(() => null)) : succeed$2(null));
42276
- const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
42227
+ const lintIncludePaths = (input.skipJsxIncludeFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths], project)) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config, project);
42277
42228
  const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
42278
42229
  const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
42279
42230
  const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
@@ -42768,7 +42719,7 @@ const runEditorScan = async (input) => {
42768
42719
  warnings,
42769
42720
  isCi: false,
42770
42721
  resolveLocalGithubViewerPermission: false,
42771
- skipExplicitIncludePathFilter: true
42722
+ skipJsxIncludeFilter: true
42772
42723
  }).pipe(withSpan("runEditorScan", { attributes: {
42773
42724
  "editor.lint": lint,
42774
42725
  "editor.runDeadCode": runDeadCode
@@ -43531,7 +43482,7 @@ const createLintCache = (input) => {
43531
43482
  let persistTimer = null;
43532
43483
  try {
43533
43484
  const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
43534
- if (parsed.version === 2 && parsed.fingerprint === input.fingerprint) for (const [fsPath, entry] of Object.entries(parsed.entries)) entries.set(fsPath, entry);
43485
+ if (parsed.version === 1 && parsed.fingerprint === input.fingerprint) for (const [fsPath, entry] of Object.entries(parsed.entries)) entries.set(fsPath, entry);
43535
43486
  } catch {}
43536
43487
  const persist = () => {
43537
43488
  if (persistTimer) {
@@ -43542,7 +43493,7 @@ const createLintCache = (input) => {
43542
43493
  dirty = false;
43543
43494
  try {
43544
43495
  const payload = {
43545
- version: 2,
43496
+ version: 1,
43546
43497
  fingerprint: input.fingerprint,
43547
43498
  entries: Object.fromEntries(entries)
43548
43499
  };
@@ -43555,14 +43506,14 @@ const createLintCache = (input) => {
43555
43506
  }
43556
43507
  };
43557
43508
  return {
43558
- lookup: (fsPath, identity) => {
43509
+ lookup: (fsPath, stat) => {
43559
43510
  const entry = entries.get(fsPath);
43560
- if (entry !== void 0 && entry.contentHash === identity.contentHash) return entry.diagnostics;
43511
+ if (entry !== void 0 && entry.mtimeMs === stat.mtimeMs && entry.size === stat.size) return entry.diagnostics;
43561
43512
  return null;
43562
43513
  },
43563
- store: (fsPath, identity, diagnostics) => {
43514
+ store: (fsPath, stat, diagnostics) => {
43564
43515
  entries.set(fsPath, {
43565
- ...identity,
43516
+ ...stat,
43566
43517
  diagnostics
43567
43518
  });
43568
43519
  dirty = true;
@@ -43651,9 +43602,16 @@ const resolveDiagnosticFsPath = (rawFilePath, scanDirectory, projectDirectory, o
43651
43602
  }
43652
43603
  return normalizeFsPath(absolute);
43653
43604
  };
43654
- const readFileIdentity = (fsPath) => {
43655
- const contentHash = hashFileContents(fsPath);
43656
- return contentHash === null ? null : { contentHash };
43605
+ const statSafe = (fsPath) => {
43606
+ try {
43607
+ const stat = fs.statSync(fsPath);
43608
+ return {
43609
+ mtimeMs: stat.mtimeMs,
43610
+ size: stat.size
43611
+ };
43612
+ } catch {
43613
+ return null;
43614
+ }
43657
43615
  };
43658
43616
  /**
43659
43617
  * Outcome for a per-file request served without running oxlint (every
@@ -43679,7 +43637,7 @@ const outcomeWithoutScan = (request, byFile, requestedPaths) => ({
43679
43637
  * overlay tree (unsaved buffers) or disk, groups diagnostics by canonical
43680
43638
  * absolute path, and reports stale-detection metadata.
43681
43639
  *
43682
- * A persistent per-file lint cache (keyed by content, namespaced by a
43640
+ * A persistent per-file lint cache (keyed by mtime + size, namespaced by a
43683
43641
  * config fingerprint) short-circuits unchanged files so a re-opened editor
43684
43642
  * or repeated workspace scan skips the oxlint subprocess for everything it
43685
43643
  * hasn't edited. The cache applies only to disk-based, lint-only, per-file
@@ -43709,15 +43667,15 @@ const createScanRunner = (options) => {
43709
43667
  const requestedPaths = request.priority === "background" && !request.useOverlay && options.isOpen ? allRequested.filter((fsPath) => !options.isOpen?.(fsPath)) : allRequested;
43710
43668
  const cache = cacheEnabled && !isWholeProject && !request.useOverlay && !request.runDeadCode ? getCache(projectDirectory) : null;
43711
43669
  const byFile = /* @__PURE__ */ new Map();
43712
- const identityByPath = /* @__PURE__ */ new Map();
43670
+ const statByPath = /* @__PURE__ */ new Map();
43713
43671
  let filesToScan = requestedPaths;
43714
43672
  if (cache) {
43715
43673
  const uncached = [];
43716
43674
  for (const fsPath of requestedPaths) {
43717
- const identity = readFileIdentity(fsPath);
43718
- if (identity) {
43719
- identityByPath.set(fsPath, identity);
43720
- const hit = cache.lookup(fsPath, identity);
43675
+ const stat = statSafe(fsPath);
43676
+ if (stat) {
43677
+ statByPath.set(fsPath, stat);
43678
+ const hit = cache.lookup(fsPath, stat);
43721
43679
  if (hit !== null) {
43722
43680
  if (hit.length > 0) byFile.set(fsPath, hit);
43723
43681
  continue;
@@ -43760,9 +43718,9 @@ const createScanRunner = (options) => {
43760
43718
  }
43761
43719
  if (cache && result.ok && !result.skipped && !result.didLintFail && result.lintPartialFailures.length === 0) {
43762
43720
  for (const fsPath of filesToScan) {
43763
- const identity = identityByPath.get(fsPath);
43764
- if (!identity) continue;
43765
- cache.store(fsPath, identity, byFile.get(fsPath) ?? []);
43721
+ const stat = statByPath.get(fsPath);
43722
+ if (!stat) continue;
43723
+ cache.store(fsPath, stat, byFile.get(fsPath) ?? []);
43766
43724
  }
43767
43725
  cache.schedulePersist();
43768
43726
  }
@@ -44542,7 +44500,6 @@ const METRIC = {
44542
44500
  cliInvoked: "cli.invoked",
44543
44501
  cliError: "cli.error",
44544
44502
  cliEnvironmentError: "cli.env_error",
44545
- stagedSnapshotDivergence: "staged.snapshot_divergence",
44546
44503
  projectDetected: "project.detected",
44547
44504
  projectPathSelected: "project.path_selected",
44548
44505
  projectConfigSelected: "project.config_selected",
@@ -44897,5 +44854,5 @@ const startLanguageServer = () => {
44897
44854
  };
44898
44855
  //#endregion
44899
44856
  export { startLanguageServer };
44900
- !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]="8ed74459-2a69-556c-bb95-8cdebdb094cd")}catch(e){}}();
44901
- //# debugId=8ed74459-2a69-556c-bb95-8cdebdb094cd
44857
+ !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]="b77898db-d8e8-531a-a32b-9de2d1a63635")}catch(e){}}();
44858
+ //# debugId=b77898db-d8e8-531a-a32b-9de2d1a63635
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.7.6-dev.0d11bd5",
3
+ "version": "0.7.6-dev.0017418",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -66,7 +66,7 @@
66
66
  "vscode-uri": "^3.1.0",
67
67
  "yaml": "^2.9.0",
68
68
  "deslop-js": "0.7.6",
69
- "oxlint-plugin-react-doctor": "0.7.6-dev.0d11bd5"
69
+ "oxlint-plugin-react-doctor": "0.7.6-dev.0017418"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/babel__code-frame": "^7.27.0",