react-doctor 0.5.6-dev.5b742fa → 0.5.6-dev.5d1347e

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.
Files changed (4) hide show
  1. package/dist/cli.js +399 -250
  2. package/dist/index.js +95 -76
  3. package/dist/lsp.js +114 -98
  4. package/package.json +2 -2
package/dist/lsp.js CHANGED
@@ -32761,6 +32761,7 @@ const isLargeMinifiedFile = (absolutePath) => {
32761
32761
  if (sizeBytes < 2e4) return false;
32762
32762
  return isMinifiedSource(absolutePath);
32763
32763
  };
32764
+ const isErrnoException = (error) => error instanceof Error && "code" in error;
32764
32765
  const IGNORABLE_READDIR_ERROR_CODES = new Set([
32765
32766
  "EACCES",
32766
32767
  "EPERM",
@@ -32770,11 +32771,7 @@ const IGNORABLE_READDIR_ERROR_CODES = new Set([
32770
32771
  "ELOOP",
32771
32772
  "ENAMETOOLONG"
32772
32773
  ]);
32773
- const isIgnorableReaddirError = (error) => {
32774
- if (typeof error !== "object" || error === null) return false;
32775
- const errorCode = error.code;
32776
- return typeof errorCode === "string" && IGNORABLE_READDIR_ERROR_CODES.has(errorCode);
32777
- };
32774
+ const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
32778
32775
  const readDirectoryEntries = (directoryPath) => {
32779
32776
  try {
32780
32777
  return NFS.readdirSync(directoryPath, { withFileTypes: true });
@@ -32824,7 +32821,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
32824
32821
  return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
32825
32822
  } catch (error) {
32826
32823
  if (error instanceof SyntaxError) return {};
32827
- if (error instanceof Error && "code" in error) {
32824
+ if (isErrnoException(error)) {
32828
32825
  const { code } = error;
32829
32826
  if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
32830
32827
  }
@@ -33549,17 +33546,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
33549
33546
  return false;
33550
33547
  };
33551
33548
  const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
33552
- const getExpoDependencySpec = (packageJson) => {
33553
- const spec = packageJson.dependencies?.expo ?? packageJson.devDependencies?.expo ?? packageJson.peerDependencies?.expo ?? packageJson.optionalDependencies?.expo;
33549
+ const getDependencySpec = (packageJson, packageName) => {
33550
+ const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
33554
33551
  return typeof spec === "string" ? spec : null;
33555
33552
  };
33556
- const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getExpoDependencySpec);
33553
+ const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
33557
33554
  const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
33558
- const getShopifyFlashListDependencySpec = (packageJson) => {
33559
- const spec = packageJson.dependencies?.["@shopify/flash-list"] ?? packageJson.devDependencies?.["@shopify/flash-list"] ?? packageJson.peerDependencies?.["@shopify/flash-list"] ?? packageJson.optionalDependencies?.["@shopify/flash-list"];
33560
- return typeof spec === "string" ? spec : null;
33561
- };
33562
- const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getShopifyFlashListDependencySpec);
33555
+ const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
33563
33556
  const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
33564
33557
  if (version === null || !isCatalogReference(version)) return version;
33565
33558
  const catalogName = extractCatalogName(version);
@@ -33571,11 +33564,7 @@ const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson,
33571
33564
  if (!isFile(monorepoPackageJsonPath)) return version;
33572
33565
  return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
33573
33566
  };
33574
- const getNextjsDependencySpec = (packageJson) => {
33575
- const spec = packageJson.dependencies?.next ?? packageJson.devDependencies?.next ?? packageJson.peerDependencies?.next ?? packageJson.optionalDependencies?.next;
33576
- return typeof spec === "string" ? spec : null;
33577
- };
33578
- const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getNextjsDependencySpec);
33567
+ const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
33579
33568
  const getPreactVersion = (packageJson) => {
33580
33569
  return {
33581
33570
  ...packageJson.peerDependencies,
@@ -33657,6 +33646,11 @@ const ES_TARGET_YEAR_BY_NAME = {
33657
33646
  esnext: 9999
33658
33647
  };
33659
33648
  /**
33649
+ * tsconfig filenames probed when resolving a project's TypeScript
33650
+ * compiler options — the root config first, then a monorepo base config.
33651
+ */
33652
+ const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
33653
+ /**
33660
33654
  * Project-config files that `StagedFiles.materialize` copies into
33661
33655
  * the temp directory alongside staged sources so oxlint resolves
33662
33656
  * `tsconfig` / `package.json` / lint configs the same way it would
@@ -34178,6 +34172,7 @@ const isTailwindAtLeast = (detected, required) => {
34178
34172
  if (detected.major !== required.major) return detected.major > required.major;
34179
34173
  return detected.minor >= required.minor;
34180
34174
  };
34175
+ const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
34181
34176
  var InvalidGlobPatternError = class extends Error {
34182
34177
  pattern;
34183
34178
  reason;
@@ -34206,7 +34201,7 @@ const compileGlobPattern = (rawPattern) => {
34206
34201
  try {
34207
34202
  return import_picomatch.default.makeRe(normalizeGlobPattern(rawPattern), PICOMATCH_OPTIONS);
34208
34203
  } catch (caughtError) {
34209
- throw new InvalidGlobPatternError(rawPattern, caughtError instanceof Error ? caughtError.message : String(caughtError));
34204
+ throw new InvalidGlobPatternError(rawPattern, messageFromUnknown(caughtError));
34210
34205
  }
34211
34206
  };
34212
34207
  const compileGlobPatternsLenient = (patterns, onInvalid) => {
@@ -35362,7 +35357,6 @@ const PACKAGE_JSON_FILENAME = "package.json";
35362
35357
  const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
35363
35358
  const LEGACY_CONFIG_FILENAME = "react-doctor.config.json";
35364
35359
  const jiti = createJiti(import.meta.url);
35365
- const formatError = (error) => error instanceof Error ? error.message : String(error);
35366
35360
  const importDefaultExport = async (jitiInstance, filePath) => {
35367
35361
  const imported = await jitiInstance.import(filePath);
35368
35362
  return imported?.default ?? imported;
@@ -35394,7 +35388,7 @@ const loadModuleConfig = async (filePath) => {
35394
35388
  try {
35395
35389
  return await importDefaultExport(aliasJiti, filePath);
35396
35390
  } catch (retryError) {
35397
- throw new Error(`${formatError(error)} (retry with ${SELF_PACKAGE_IMPORT_SPECIFIER} aliased to the running react-doctor package also failed: ${formatError(retryError)})`, { cause: retryError });
35391
+ throw new Error(`${messageFromUnknown(error)} (retry with ${SELF_PACKAGE_IMPORT_SPECIFIER} aliased to the running react-doctor package also failed: ${messageFromUnknown(retryError)})`, { cause: retryError });
35398
35392
  }
35399
35393
  }
35400
35394
  };
@@ -35443,7 +35437,7 @@ const loadLegacyConfig = (directory) => {
35443
35437
  }
35444
35438
  warn(`${LEGACY_CONFIG_FILENAME} must contain an object, ignoring.`);
35445
35439
  } catch (error) {
35446
- warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${formatError(error)}`);
35440
+ warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${messageFromUnknown(error)}`);
35447
35441
  }
35448
35442
  return {
35449
35443
  status: "invalid",
@@ -35470,7 +35464,7 @@ const loadConfigFromDirectory = async (directory) => {
35470
35464
  warn(`${CONFIG_BASENAME}.${extension} must export an object, ignoring.`);
35471
35465
  sawBrokenConfigFile = true;
35472
35466
  } catch (error) {
35473
- warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${formatError(error)}`);
35467
+ warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${messageFromUnknown(error)}`);
35474
35468
  sawBrokenConfigFile = true;
35475
35469
  }
35476
35470
  }
@@ -36458,15 +36452,10 @@ const buildCapabilities = (project) => {
36458
36452
  }
36459
36453
  if (project.tailwindVersion !== null) {
36460
36454
  capabilities.add("tailwind");
36461
- const tailwind = parseTailwindMajorMinor(project.tailwindVersion);
36462
- if (isTailwindAtLeast(tailwind, {
36455
+ if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
36463
36456
  major: 3,
36464
36457
  minor: 4
36465
36458
  })) capabilities.add("tailwind:3.4");
36466
- if (tailwind !== null && isTailwindAtLeast(tailwind, {
36467
- major: 4,
36468
- minor: 0
36469
- })) capabilities.add("tailwind:4");
36470
36459
  }
36471
36460
  if (project.zodVersion !== null) {
36472
36461
  capabilities.add("zod");
@@ -36655,7 +36644,7 @@ const readIgnoreFile = (filePath) => {
36655
36644
  try {
36656
36645
  content = NFS.readFileSync(filePath, "utf-8");
36657
36646
  } catch (error) {
36658
- const errnoCode = error?.code;
36647
+ const errnoCode = isErrnoException(error) ? error.code : void 0;
36659
36648
  if (errnoCode && errnoCode !== "ENOENT") runSync(warn$1(`Could not read ignore file ${filePath}: ${errnoCode}`));
36660
36649
  return [];
36661
36650
  }
@@ -36696,8 +36685,8 @@ const collectIgnorePatterns = (rootDirectory) => {
36696
36685
  cachedPatternsByRoot.set(rootDirectory, patterns);
36697
36686
  return patterns;
36698
36687
  };
36688
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36699
36689
  const KNIP_JSON_FILENAME = "knip.json";
36700
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36701
36690
  const readJsonFileSafe = (filePath) => {
36702
36691
  let rawContents;
36703
36692
  try {
@@ -36713,10 +36702,10 @@ const readJsonFileSafe = (filePath) => {
36713
36702
  };
36714
36703
  const readKnipConfig = (rootDirectory) => {
36715
36704
  const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
36716
- if (isRecord$1(knipJson)) return knipJson;
36705
+ if (isRecord(knipJson)) return knipJson;
36717
36706
  const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
36718
- const packageKnipConfig = isRecord$1(packageJson) ? packageJson.knip : null;
36719
- return isRecord$1(packageKnipConfig) ? packageKnipConfig : null;
36707
+ const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
36708
+ return isRecord(packageKnipConfig) ? packageKnipConfig : null;
36720
36709
  };
36721
36710
  const normalizePatternList = (value) => {
36722
36711
  if (typeof value === "string" && value.length > 0) return [value];
@@ -36728,10 +36717,10 @@ const prefixWorkspacePatterns = (workspacePattern, patterns) => {
36728
36717
  return patterns.map((pattern) => pattern.startsWith("!") ? `!${normalizedWorkspacePattern}/${pattern.slice(1)}` : `${normalizedWorkspacePattern}/${pattern}`);
36729
36718
  };
36730
36719
  const collectKnipWorkspacePatterns = (workspaces, settingName) => {
36731
- if (!isRecord$1(workspaces)) return [];
36720
+ if (!isRecord(workspaces)) return [];
36732
36721
  const patterns = [];
36733
36722
  for (const [workspacePattern, workspaceConfig] of Object.entries(workspaces)) {
36734
- if (!isRecord$1(workspaceConfig)) continue;
36723
+ if (!isRecord(workspaceConfig)) continue;
36735
36724
  patterns.push(...prefixWorkspacePatterns(workspacePattern, normalizePatternList(workspaceConfig[settingName])));
36736
36725
  }
36737
36726
  return patterns;
@@ -36776,8 +36765,6 @@ const toCanonicalPath = (filePath) => {
36776
36765
  };
36777
36766
  const DEAD_CODE_PLUGIN = "deslop";
36778
36767
  const DEAD_CODE_CATEGORY = "Maintainability";
36779
- const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
36780
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36781
36768
  const DEAD_CODE_WORKER_SCRIPT = `
36782
36769
  const inputChunks = [];
36783
36770
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -36835,7 +36822,7 @@ process.stdin.on("end", () => {
36835
36822
  });
36836
36823
  `;
36837
36824
  const resolveTsConfigPath = (rootDirectory) => {
36838
- for (const filename of TSCONFIG_FILENAMES$1) {
36825
+ for (const filename of TSCONFIG_FILENAMES) {
36839
36826
  const candidate = Path.join(rootDirectory, filename);
36840
36827
  if (NFS.existsSync(candidate)) return candidate;
36841
36828
  }
@@ -37216,15 +37203,13 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
37216
37203
  })()) }));
37217
37204
  static layerOf = (diagnostics) => succeed$3(DeadCode, DeadCode.of({ run: () => fromIterable$1(diagnostics) }));
37218
37205
  };
37219
- const createNodeReadFileLinesSync = (rootDirectory) => {
37220
- return (filePath) => {
37221
- const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
37222
- try {
37223
- return NFS.readFileSync(absolutePath, "utf-8").split("\n");
37224
- } catch {
37225
- return null;
37226
- }
37227
- };
37206
+ const createNodeReadFileLinesSync = (rootDirectory) => (filePath) => {
37207
+ const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
37208
+ try {
37209
+ return NFS.readFileSync(absolutePath, "utf-8").split("\n");
37210
+ } catch {
37211
+ return null;
37212
+ }
37228
37213
  };
37229
37214
  var Files = class Files extends Service()("react-doctor/Files") {
37230
37215
  static layerNode = succeed$3(Files, Files.of({
@@ -37435,7 +37420,10 @@ var Git = class Git extends Service()("react-doctor/Git") {
37435
37420
  directory: input.directory,
37436
37421
  cause
37437
37422
  }) });
37438
- }));
37423
+ }), withSpan("git.exec", { attributes: {
37424
+ "git.command": input.command,
37425
+ "git.subcommand": input.args[0] ?? ""
37426
+ } }));
37439
37427
  const runGit = (directory, args) => runCommand({
37440
37428
  command: "git",
37441
37429
  args,
@@ -37463,7 +37451,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37463
37451
  ]);
37464
37452
  if (candidates.status !== 0) return null;
37465
37453
  return trimOrNull(candidates.stdout.split("\n")[0] ?? "");
37466
- });
37454
+ }).pipe(withSpan("Git.defaultBranch"));
37467
37455
  const branchExists = (directory, branch) => runGit(directory, [
37468
37456
  "rev-parse",
37469
37457
  "--verify",
@@ -37510,7 +37498,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37510
37498
  const result = resultOption.value;
37511
37499
  if (result.status !== 0) return null;
37512
37500
  return parseGithubViewerPermission(result.stdout);
37513
- }).pipe(catch_$1(() => succeed$2(null)));
37501
+ }).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.githubViewerPermission"));
37514
37502
  /**
37515
37503
  * Resolves a `--diff A..B` / `A...B` commit range into a changed-file
37516
37504
  * selection. Each endpoint is validated with `isSafeGitRevision`
@@ -37624,7 +37612,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37624
37612
  changedFiles: splitNullSeparated(diff.stdout),
37625
37613
  isCurrentChanges: false
37626
37614
  };
37627
- }),
37615
+ }).pipe(withSpan("Git.diffSelection")),
37628
37616
  stagedFilePaths: (directory) => runGit(directory, [
37629
37617
  "diff",
37630
37618
  "--cached",
@@ -37666,7 +37654,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37666
37654
  status: result.status,
37667
37655
  stdout: result.stdout
37668
37656
  };
37669
- }),
37657
+ }).pipe(withSpan("Git.grep")),
37670
37658
  changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
37671
37659
  if (files.length === 0) return [];
37672
37660
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
@@ -37682,7 +37670,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37682
37670
  ]);
37683
37671
  if (result.status !== 0) return null;
37684
37672
  return parseChangedLineRanges(result.stdout);
37685
- })
37673
+ }).pipe(withSpan("Git.changedLineRanges"))
37686
37674
  });
37687
37675
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
37688
37676
  /**
@@ -37897,7 +37885,7 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
37897
37885
  for (const [absolutePath, originalContent] of originalContents) try {
37898
37886
  NFS.writeFileSync(absolutePath, originalContent);
37899
37887
  } catch (error) {
37900
- process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${error instanceof Error ? error.message : String(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
37888
+ process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
37901
37889
  }
37902
37890
  };
37903
37891
  const onExit = () => restore();
@@ -38003,7 +37991,7 @@ const resolveUserPlugin = (spec, configSourceDirectory) => {
38003
37991
  try {
38004
37992
  resolvedSpecifier = isRelative ? Path.resolve(configSourceDirectory, spec) : candidateRequire.resolve(spec);
38005
37993
  } catch (error) {
38006
- warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${error instanceof Error ? error.message : String(error)}`);
37994
+ warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${messageFromUnknown(error)}`);
38007
37995
  return null;
38008
37996
  }
38009
37997
  const { name, ruleNames } = readPluginShape(resolvedSpecifier, (target) => candidateRequire(target));
@@ -38075,8 +38063,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38075
38063
  }
38076
38064
  return enabled;
38077
38065
  };
38078
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
38079
- const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38066
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
38067
+ const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38080
38068
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38081
38069
  const jsPlugins = [];
38082
38070
  if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
@@ -38136,7 +38124,6 @@ const resolveOxlintBinary = () => {
38136
38124
  return Path.join(oxlintPackageDirectory, "bin", "oxlint");
38137
38125
  };
38138
38126
  const resolvePluginPath = () => esmRequire.resolve("oxlint-plugin-react-doctor");
38139
- const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
38140
38127
  const resolveTsConfigRelativePath = (rootDirectory) => {
38141
38128
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
38142
38129
  return null;
@@ -38508,7 +38495,7 @@ const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
38508
38495
  const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
38509
38496
  let currentNode = identifier.parent;
38510
38497
  while (currentNode) {
38511
- if (isScopeNode(currentNode)) {
38498
+ if (isScopeBoundary(currentNode)) {
38512
38499
  if (scopeContainsNonImportBinding(currentNode, currentNode, identifier.text)) return true;
38513
38500
  }
38514
38501
  if (currentNode === sourceFile) return false;
@@ -38599,11 +38586,10 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
38599
38586
  });
38600
38587
  return resolution;
38601
38588
  };
38602
- const isScopeNode = isScopeBoundary;
38603
38589
  const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, visitedDeclarations = /* @__PURE__ */ new Set()) => {
38604
38590
  let currentNode = identifier.parent;
38605
38591
  while (currentNode) {
38606
- if (isScopeNode(currentNode)) {
38592
+ if (isScopeBoundary(currentNode)) {
38607
38593
  const resolution = findResolutionInScope(currentNode, identifier.text, reactImportBindings, sourceFile, visitedDeclarations);
38608
38594
  if (resolution) return resolution;
38609
38595
  }
@@ -38773,9 +38759,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
38773
38759
  try {
38774
38760
  parsed = JSON.parse(sanitizedStdout);
38775
38761
  } catch {
38776
- throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38762
+ throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38777
38763
  }
38778
- if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38764
+ if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38779
38765
  const minifiedFileCache = /* @__PURE__ */ new Map();
38780
38766
  const isMinifiedDiagnosticFile = (filename) => {
38781
38767
  const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
@@ -38851,7 +38837,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
38851
38837
  child.kill("SIGKILL");
38852
38838
  reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
38853
38839
  kind: "timeout",
38854
- detail: `${spawnTimeoutMs / 1e3}s budget exceeded`
38840
+ detail: `${spawnTimeoutMs / MILLISECONDS_PER_SECOND}s budget exceeded`
38855
38841
  }) }));
38856
38842
  }, spawnTimeoutMs);
38857
38843
  timeoutHandle.unref?.();
@@ -39066,6 +39052,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
39066
39052
  NFS.closeSync(fileHandle);
39067
39053
  }
39068
39054
  };
39055
+ const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
39056
+ /**
39057
+ * Detects an oxlint config-load crash caused by the optional
39058
+ * `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
39059
+ * builds the partial-failure note for it; returns `null` when the failure
39060
+ * was anything else.
39061
+ *
39062
+ * oxlint prints a framed error to stdout (not stderr) and exits non-zero
39063
+ * when a `jsPlugins` entry can't be imported; that non-JSON stdout
39064
+ * surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
39065
+ * config load on it, leaving the plugin in would drop every curated
39066
+ * react-doctor diagnostic too — so the caller retries with the plugin
39067
+ * stripped (issue #833). Both markers sit at the start of oxlint's
39068
+ * message, so they survive the `preview` slice even for deep pnpm paths.
39069
+ */
39070
+ const reactHooksJsPluginDropNote = (error) => {
39071
+ if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
39072
+ const { preview } = error.reason;
39073
+ if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
39074
+ const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
39075
+ return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
39076
+ };
39069
39077
  /**
39070
39078
  * The oxlint runner. Composed of three pieces in `runners/oxlint/`:
39071
39079
  *
@@ -39093,15 +39101,16 @@ const runOxlint = async (options) => {
39093
39101
  const pluginPath = resolvePluginPath();
39094
39102
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
39095
39103
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
39096
- const buildConfig = (extendsForThisAttempt) => createOxlintConfig({
39104
+ const buildConfig = (overrides) => createOxlintConfig({
39097
39105
  pluginPath,
39098
39106
  project,
39099
39107
  customRulesOnly,
39100
- extendsPaths: extendsForThisAttempt,
39108
+ extendsPaths: overrides.extendsPaths,
39101
39109
  ignoredTags,
39102
39110
  serverAuthFunctionNames,
39103
39111
  severityControls,
39104
- userPlugins
39112
+ userPlugins,
39113
+ disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
39105
39114
  });
39106
39115
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
39107
39116
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -39137,12 +39146,22 @@ const runOxlint = async (options) => {
39137
39146
  outputMaxBytes,
39138
39147
  concurrency: options.concurrency
39139
39148
  });
39140
- writeOxlintConfig(configPath, buildConfig(extendsPaths));
39149
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
39141
39150
  try {
39142
39151
  return await runBatches();
39143
39152
  } catch (error) {
39153
+ const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
39154
+ if (reactHooksJsDropNote !== null) {
39155
+ writeOxlintConfig(configPath, buildConfig({
39156
+ extendsPaths,
39157
+ disableReactHooksJsPlugin: true
39158
+ }));
39159
+ const diagnostics = await runBatches();
39160
+ onPartialFailure?.(reactHooksJsDropNote);
39161
+ return diagnostics;
39162
+ }
39144
39163
  if (extendsPaths.length === 0) throw error;
39145
- writeOxlintConfig(configPath, buildConfig([]));
39164
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
39146
39165
  return await runBatches();
39147
39166
  }
39148
39167
  } finally {
@@ -39940,7 +39959,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
39940
39959
  }))))))));
39941
39960
  const deadCodeFailureState = yield* get$2(deadCodeFailure);
39942
39961
  const scanElapsedMilliseconds = Date.now() - scanStartTime;
39943
- const scanElapsedSeconds = (scanElapsedMilliseconds / 1e3).toFixed(1);
39962
+ const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
39944
39963
  if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
39945
39964
  else if (input.suppressScanSummary) yield* scanProgress.stop();
39946
39965
  else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
@@ -40154,7 +40173,7 @@ const materializeSourceTree = (input) => gen(function* () {
40154
40173
  static layerNode = effect(StagedFiles, gen(function* () {
40155
40174
  const git = yield* Git;
40156
40175
  return StagedFiles.of({
40157
- discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile))),
40176
+ discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile)), withSpan("StagedFiles.discoverSourceFiles")),
40158
40177
  materialize: ({ directory, stagedFiles, tempDirectory }) => materializeSourceTree({
40159
40178
  directory,
40160
40179
  files: stagedFiles,
@@ -40164,7 +40183,7 @@ const materializeSourceTree = (input) => gen(function* () {
40164
40183
  tempDirectory: tree.tempDirectory,
40165
40184
  stagedFiles: tree.materializedFiles,
40166
40185
  cleanup: tree.cleanup
40167
- })))
40186
+ })), withSpan("StagedFiles.materialize"))
40168
40187
  });
40169
40188
  }));
40170
40189
  /**
@@ -40232,7 +40251,10 @@ const runEditorScan = async (input) => {
40232
40251
  isCi: false,
40233
40252
  resolveLocalGithubViewerPermission: false,
40234
40253
  skipJsxIncludeFilter: true
40235
- }).pipe(provide(layers), provide(layerOtlp)));
40254
+ }).pipe(withSpan("runEditorScan", { attributes: {
40255
+ "editor.lint": lint,
40256
+ "editor.runDeadCode": runDeadCode
40257
+ } }), provide(layers), provide(layerOtlp)));
40236
40258
  if (isSuccess(exit)) {
40237
40259
  const output = exit.value;
40238
40260
  return {
@@ -40262,7 +40284,7 @@ const runEditorScan = async (input) => {
40262
40284
  didDeadCodeFail: false,
40263
40285
  deadCodeFailureReason: null,
40264
40286
  lintPartialFailures: [],
40265
- error: error instanceof Error ? error.message : String(error)
40287
+ error: messageFromUnknown(error)
40266
40288
  };
40267
40289
  };
40268
40290
  /**
@@ -40544,7 +40566,6 @@ const toLspDiagnostic = (input) => {
40544
40566
  data
40545
40567
  };
40546
40568
  };
40547
- const toUri = (absoluteFilePath) => fsPathToUri(absoluteFilePath);
40548
40569
  /**
40549
40570
  * Owns the published-diagnostic state. Maps scan outcomes to LSP
40550
40571
  * diagnostics, publishes complete per-URI replacement sets (so the
@@ -40574,7 +40595,7 @@ var DiagnosticsManager = class {
40574
40595
  const isProtectedPath = (fsPath) => protectOpen && this.isOpen(fsPath);
40575
40596
  for (const [fsPath, coreDiagnostics] of outcome.byFile) {
40576
40597
  if (isProtectedPath(fsPath)) continue;
40577
- const uri = toUri(fsPath);
40598
+ const uri = fsPathToUri(fsPath);
40578
40599
  const text = this.textProvider(fsPath);
40579
40600
  const lspDiagnostics = coreDiagnostics.map((diagnostic) => toLspDiagnostic({
40580
40601
  diagnostic,
@@ -40596,7 +40617,7 @@ var DiagnosticsManager = class {
40596
40617
  for (const fsPath of outcome.requestedPaths) {
40597
40618
  if (isProtectedPath(fsPath)) continue;
40598
40619
  if (outcome.byFile.has(fsPath)) continue;
40599
- const uri = toUri(fsPath);
40620
+ const uri = fsPathToUri(fsPath);
40600
40621
  if (this.byUri.has(uri)) this.byUri.delete(uri);
40601
40622
  this.publish(uri, []);
40602
40623
  }
@@ -40621,7 +40642,7 @@ var DiagnosticsManager = class {
40621
40642
  const set = this.projectUris.get(project) ?? /* @__PURE__ */ new Set();
40622
40643
  for (const uri of liveUris) set.add(uri);
40623
40644
  for (const fsPath of outcome.requestedPaths) {
40624
- const uri = toUri(fsPath);
40645
+ const uri = fsPathToUri(fsPath);
40625
40646
  if (!liveUris.has(uri)) set.delete(uri);
40626
40647
  }
40627
40648
  this.projectUris.set(project, set);
@@ -40649,7 +40670,7 @@ var DiagnosticsManager = class {
40649
40670
  const tracked = this.projectUris.get(project);
40650
40671
  if (!tracked) return;
40651
40672
  const liveUris = /* @__PURE__ */ new Set();
40652
- for (const fsPath of liveFsPaths) liveUris.add(toUri(fsPath));
40673
+ for (const fsPath of liveFsPaths) liveUris.add(fsPathToUri(fsPath));
40653
40674
  for (const uri of [...tracked]) {
40654
40675
  if (liveUris.has(uri)) continue;
40655
40676
  this.byUri.delete(uri);
@@ -40946,7 +40967,7 @@ const createProjectGraph = (options) => {
40946
40967
  });
40947
40968
  }
40948
40969
  } catch (error) {
40949
- logger.warn(`Project discovery failed for ${root}: ${error instanceof Error ? error.message : String(error)}`);
40970
+ logger.warn(`Project discovery failed for ${root}: ${messageFromUnknown(error)}`);
40950
40971
  }
40951
40972
  return [...seen.values()].sort((first, second) => second.directory.length - first.directory.length);
40952
40973
  };
@@ -40974,6 +40995,11 @@ const createProjectGraph = (options) => {
40974
40995
  }
40975
40996
  };
40976
40997
  };
40998
+ const toProjectRelative = (projectDirectory, filePath) => {
40999
+ const relative = Path.relative(projectDirectory, filePath).replace(/\\/g, "/");
41000
+ if (relative.length === 0 || relative.startsWith("../") || Path.isAbsolute(relative)) return null;
41001
+ return relative;
41002
+ };
40977
41003
  const resolveCacheFilePath = (projectDirectory) => {
40978
41004
  const nodeModules = path.join(projectDirectory, "node_modules");
40979
41005
  if (fs.existsSync(nodeModules)) return path.join(nodeModules, ".cache", "react-doctor", "lint-cache.json");
@@ -41008,7 +41034,7 @@ const createLintCache = (input) => {
41008
41034
  fs.writeFileSync(tempPath, JSON.stringify(payload));
41009
41035
  fs.renameSync(tempPath, cacheFilePath);
41010
41036
  } catch (error) {
41011
- logger.warn(`Failed to persist lint cache: ${error instanceof Error ? error.message : String(error)}`);
41037
+ logger.warn(`Failed to persist lint cache: ${messageFromUnknown(error)}`);
41012
41038
  }
41013
41039
  };
41014
41040
  return {
@@ -41034,11 +41060,6 @@ const createLintCache = (input) => {
41034
41060
  };
41035
41061
  const OVERLAY_TEMP_PREFIX = "react-doctor-lsp-";
41036
41062
  const OVERLAY_CONFIG_FILENAMES = [...new Set([...STAGED_FILES_PROJECT_CONFIG_FILENAMES, ...ADOPTABLE_LINT_CONFIG_FILENAMES])];
41037
- const toProjectRelative$1 = (projectDirectory, filePath) => {
41038
- const relative = path.relative(projectDirectory, filePath).replace(/\\/g, "/");
41039
- if (relative.length === 0 || relative.startsWith("../") || path.isAbsolute(relative)) return null;
41040
- return relative;
41041
- };
41042
41063
  /**
41043
41064
  * Writes the live (possibly unsaved) content of the target files into a
41044
41065
  * throwaway temp tree that mirrors the project, alongside the well-known
@@ -41052,7 +41073,7 @@ const materializeOverlay = (input) => {
41052
41073
  const relativePaths = [];
41053
41074
  try {
41054
41075
  for (const filePath of input.files) {
41055
- const relative = toProjectRelative$1(input.projectDirectory, filePath);
41076
+ const relative = toProjectRelative(input.projectDirectory, filePath);
41056
41077
  if (relative === null) continue;
41057
41078
  const content = input.readText(filePath);
41058
41079
  if (content === null) continue;
@@ -41100,11 +41121,6 @@ const materializeOverlay = (input) => {
41100
41121
  throw error;
41101
41122
  }
41102
41123
  };
41103
- const toProjectRelative = (projectDirectory, filePath) => {
41104
- const relative = path.relative(projectDirectory, filePath).replace(/\\/g, "/");
41105
- if (relative.length === 0 || relative.startsWith("../") || path.isAbsolute(relative)) return null;
41106
- return relative;
41107
- };
41108
41124
  /**
41109
41125
  * Resolves a diagnostic's (possibly relative, possibly overlay-temp)
41110
41126
  * file path back to the canonical absolute path inside the real project.
@@ -41326,7 +41342,7 @@ const createScheduler = (options) => {
41326
41342
  if (outcome && !token.isCancelled) options.onResult(outcome);
41327
41343
  }).catch((error) => {
41328
41344
  if (options.onError) options.onError(error, request);
41329
- else logger.error(`Scan failed: ${error instanceof Error ? error.message : String(error)}`);
41345
+ else logger.error(`Scan failed: ${messageFromUnknown(error)}`);
41330
41346
  }).finally(() => {
41331
41347
  running -= 1;
41332
41348
  if (isBackground) runningBackground -= 1;
@@ -41713,7 +41729,7 @@ const createServer = (connection, options = {}) => {
41713
41729
  maybeWarnLintUnavailable(outcome);
41714
41730
  if (outcome.request.priority === "background") scanTelemetry.accumulate(outcome);
41715
41731
  },
41716
- onError: (error, request) => logger.error(`Scan of ${request.projectDirectory} threw: ${error instanceof Error ? error.message : String(error)}`),
41732
+ onError: (error, request) => logger.error(`Scan of ${request.projectDirectory} threw: ${messageFromUnknown(error)}`),
41717
41733
  onIdleChange: (idle) => {
41718
41734
  setBusy(!idle);
41719
41735
  if (idle) scanTelemetry.finish();
@@ -42361,5 +42377,5 @@ const startLanguageServer = () => {
42361
42377
  };
42362
42378
  //#endregion
42363
42379
  export { startLanguageServer };
42364
- !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]="9421149c-0ed9-5e00-81da-4f7dd8faf332")}catch(e){}}();
42365
- //# debugId=9421149c-0ed9-5e00-81da-4f7dd8faf332
42380
+ !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]="7b102472-d01e-5d6a-9975-91795fe8abc5")}catch(e){}}();
42381
+ //# debugId=7b102472-d01e-5d6a-9975-91795fe8abc5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.6-dev.5b742fa",
3
+ "version": "0.5.6-dev.5d1347e",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -64,7 +64,7 @@
64
64
  "vscode-languageserver": "^9.0.1",
65
65
  "vscode-languageserver-textdocument": "^1.0.12",
66
66
  "vscode-uri": "^3.1.0",
67
- "oxlint-plugin-react-doctor": "0.5.6-dev.5b742fa"
67
+ "oxlint-plugin-react-doctor": "0.5.6-dev.5d1347e"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/babel__code-frame": "^7.27.0",