react-doctor 0.5.6-dev.6b8e756 → 0.5.6-dev.740211c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -9794,6 +9794,16 @@ interface JsonReportProjectEntry {
9794
9794
  skippedChecks: string[];
9795
9795
  /** Human-readable explanation per skipped check. See `InspectResult.skippedCheckReasons`. */
9796
9796
  skippedCheckReasons?: Record<string, string>;
9797
+ /**
9798
+ * Number of source files this scan's linter examined. In diff / changed
9799
+ * mode it's the count of changed React-eligible files (`.tsx`/`.jsx` plus
9800
+ * framework entry files); in a full scan it's the whole source tree. `0`
9801
+ * in a diff scan means the changed files held nothing React Doctor lints —
9802
+ * the GitHub Action reads that as "nothing to report" (skips the PR comment;
9803
+ * the commit status says "skipped"). Optional: absent on reports from
9804
+ * constructors that don't track it (e.g. `toJsonReport`).
9805
+ */
9806
+ scannedFileCount?: number;
9797
9807
  elapsedMilliseconds: number;
9798
9808
  }
9799
9809
  interface JsonReportSummary {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b4f6f7e7-99db-553a-aa15-14b646162f39")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="efaa91ed-3659-50a5-b802-51753472b2d6")}catch(e){}}();
3
3
  import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import * as NFS from "node:fs";
@@ -19298,6 +19298,7 @@ var JsonReportProjectEntry = class extends Class("JsonReportProjectEntry")({
19298
19298
  score: Unknown,
19299
19299
  skippedChecks: ArraySchema(String$1),
19300
19300
  skippedCheckReasons: optional(Record$1(String$1, String$1)),
19301
+ scannedFileCount: optional(Number$1),
19301
19302
  elapsedMilliseconds: Number$1
19302
19303
  }) {};
19303
19304
  /**
@@ -32724,6 +32725,7 @@ const isLargeMinifiedFile = (absolutePath) => {
32724
32725
  if (sizeBytes < 2e4) return false;
32725
32726
  return isMinifiedSource(absolutePath);
32726
32727
  };
32728
+ const isErrnoException = (error) => error instanceof Error && "code" in error;
32727
32729
  const IGNORABLE_READDIR_ERROR_CODES = new Set([
32728
32730
  "EACCES",
32729
32731
  "EPERM",
@@ -32733,11 +32735,7 @@ const IGNORABLE_READDIR_ERROR_CODES = new Set([
32733
32735
  "ELOOP",
32734
32736
  "ENAMETOOLONG"
32735
32737
  ]);
32736
- const isIgnorableReaddirError = (error) => {
32737
- if (typeof error !== "object" || error === null) return false;
32738
- const errorCode = error.code;
32739
- return typeof errorCode === "string" && IGNORABLE_READDIR_ERROR_CODES.has(errorCode);
32740
- };
32738
+ const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
32741
32739
  const readDirectoryEntries = (directoryPath) => {
32742
32740
  try {
32743
32741
  return NFS.readdirSync(directoryPath, { withFileTypes: true });
@@ -32787,7 +32785,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
32787
32785
  return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
32788
32786
  } catch (error) {
32789
32787
  if (error instanceof SyntaxError) return {};
32790
- if (error instanceof Error && "code" in error) {
32788
+ if (isErrnoException(error)) {
32791
32789
  const { code } = error;
32792
32790
  if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
32793
32791
  }
@@ -33512,17 +33510,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
33512
33510
  return false;
33513
33511
  };
33514
33512
  const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
33515
- const getExpoDependencySpec = (packageJson) => {
33516
- const spec = packageJson.dependencies?.expo ?? packageJson.devDependencies?.expo ?? packageJson.peerDependencies?.expo ?? packageJson.optionalDependencies?.expo;
33513
+ const getDependencySpec = (packageJson, packageName) => {
33514
+ const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
33517
33515
  return typeof spec === "string" ? spec : null;
33518
33516
  };
33519
- const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getExpoDependencySpec);
33517
+ const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
33520
33518
  const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
33521
- const getShopifyFlashListDependencySpec = (packageJson) => {
33522
- const spec = packageJson.dependencies?.["@shopify/flash-list"] ?? packageJson.devDependencies?.["@shopify/flash-list"] ?? packageJson.peerDependencies?.["@shopify/flash-list"] ?? packageJson.optionalDependencies?.["@shopify/flash-list"];
33523
- return typeof spec === "string" ? spec : null;
33524
- };
33525
- const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getShopifyFlashListDependencySpec);
33519
+ const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
33526
33520
  const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
33527
33521
  if (version === null || !isCatalogReference(version)) return version;
33528
33522
  const catalogName = extractCatalogName(version);
@@ -33534,11 +33528,7 @@ const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson,
33534
33528
  if (!isFile(monorepoPackageJsonPath)) return version;
33535
33529
  return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
33536
33530
  };
33537
- const getNextjsDependencySpec = (packageJson) => {
33538
- const spec = packageJson.dependencies?.next ?? packageJson.devDependencies?.next ?? packageJson.peerDependencies?.next ?? packageJson.optionalDependencies?.next;
33539
- return typeof spec === "string" ? spec : null;
33540
- };
33541
- const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getNextjsDependencySpec);
33531
+ const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
33542
33532
  const getPreactVersion = (packageJson) => {
33543
33533
  return {
33544
33534
  ...packageJson.peerDependencies,
@@ -33620,6 +33610,11 @@ const ES_TARGET_YEAR_BY_NAME = {
33620
33610
  esnext: 9999
33621
33611
  };
33622
33612
  /**
33613
+ * tsconfig filenames probed when resolving a project's TypeScript
33614
+ * compiler options — the root config first, then a monorepo base config.
33615
+ */
33616
+ const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
33617
+ /**
33623
33618
  * Project-config files that `StagedFiles.materialize` copies into
33624
33619
  * the temp directory alongside staged sources so oxlint resolves
33625
33620
  * `tsconfig` / `package.json` / lint configs the same way it would
@@ -34118,6 +34113,7 @@ const isTailwindAtLeast = (detected, required) => {
34118
34113
  if (detected.major !== required.major) return detected.major > required.major;
34119
34114
  return detected.minor >= required.minor;
34120
34115
  };
34116
+ const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
34121
34117
  var InvalidGlobPatternError = class extends Error {
34122
34118
  pattern;
34123
34119
  reason;
@@ -34146,7 +34142,7 @@ const compileGlobPattern = (rawPattern) => {
34146
34142
  try {
34147
34143
  return import_picomatch.default.makeRe(normalizeGlobPattern(rawPattern), PICOMATCH_OPTIONS);
34148
34144
  } catch (caughtError) {
34149
- throw new InvalidGlobPatternError(rawPattern, caughtError instanceof Error ? caughtError.message : String(caughtError));
34145
+ throw new InvalidGlobPatternError(rawPattern, messageFromUnknown(caughtError));
34150
34146
  }
34151
34147
  };
34152
34148
  const compileGlobPatternsLenient = (patterns, onInvalid) => {
@@ -35328,7 +35324,6 @@ const PACKAGE_JSON_FILENAME = "package.json";
35328
35324
  const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
35329
35325
  const LEGACY_CONFIG_FILENAME = "react-doctor.config.json";
35330
35326
  const jiti = createJiti(import.meta.url);
35331
- const formatError = (error) => error instanceof Error ? error.message : String(error);
35332
35327
  const importDefaultExport = async (jitiInstance, filePath) => {
35333
35328
  const imported = await jitiInstance.import(filePath);
35334
35329
  return imported?.default ?? imported;
@@ -35360,7 +35355,7 @@ const loadModuleConfig = async (filePath) => {
35360
35355
  try {
35361
35356
  return await importDefaultExport(aliasJiti, filePath);
35362
35357
  } catch (retryError) {
35363
- throw new Error(`${formatError(error)} (retry with ${SELF_PACKAGE_IMPORT_SPECIFIER} aliased to the running react-doctor package also failed: ${formatError(retryError)})`, { cause: retryError });
35358
+ throw new Error(`${messageFromUnknown(error)} (retry with ${SELF_PACKAGE_IMPORT_SPECIFIER} aliased to the running react-doctor package also failed: ${messageFromUnknown(retryError)})`, { cause: retryError });
35364
35359
  }
35365
35360
  }
35366
35361
  };
@@ -35409,7 +35404,7 @@ const loadLegacyConfig = (directory) => {
35409
35404
  }
35410
35405
  warn(`${LEGACY_CONFIG_FILENAME} must contain an object, ignoring.`);
35411
35406
  } catch (error) {
35412
- warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${formatError(error)}`);
35407
+ warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${messageFromUnknown(error)}`);
35413
35408
  }
35414
35409
  return {
35415
35410
  status: "invalid",
@@ -35436,7 +35431,7 @@ const loadConfigFromDirectory = async (directory) => {
35436
35431
  warn(`${CONFIG_BASENAME}.${extension} must export an object, ignoring.`);
35437
35432
  sawBrokenConfigFile = true;
35438
35433
  } catch (error) {
35439
- warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${formatError(error)}`);
35434
+ warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${messageFromUnknown(error)}`);
35440
35435
  sawBrokenConfigFile = true;
35441
35436
  }
35442
35437
  }
@@ -36472,15 +36467,10 @@ const buildCapabilities = (project) => {
36472
36467
  }
36473
36468
  if (project.tailwindVersion !== null) {
36474
36469
  capabilities.add("tailwind");
36475
- const tailwind = parseTailwindMajorMinor(project.tailwindVersion);
36476
- if (isTailwindAtLeast(tailwind, {
36470
+ if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
36477
36471
  major: 3,
36478
36472
  minor: 4
36479
36473
  })) capabilities.add("tailwind:3.4");
36480
- if (tailwind !== null && isTailwindAtLeast(tailwind, {
36481
- major: 4,
36482
- minor: 0
36483
- })) capabilities.add("tailwind:4");
36484
36474
  }
36485
36475
  if (project.zodVersion !== null) {
36486
36476
  capabilities.add("zod");
@@ -36669,7 +36659,7 @@ const readIgnoreFile = (filePath) => {
36669
36659
  try {
36670
36660
  content = NFS.readFileSync(filePath, "utf-8");
36671
36661
  } catch (error) {
36672
- const errnoCode = error?.code;
36662
+ const errnoCode = isErrnoException(error) ? error.code : void 0;
36673
36663
  if (errnoCode && errnoCode !== "ENOENT") runSync(warn$1(`Could not read ignore file ${filePath}: ${errnoCode}`));
36674
36664
  return [];
36675
36665
  }
@@ -36710,8 +36700,8 @@ const collectIgnorePatterns = (rootDirectory) => {
36710
36700
  cachedPatternsByRoot.set(rootDirectory, patterns);
36711
36701
  return patterns;
36712
36702
  };
36703
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36713
36704
  const KNIP_JSON_FILENAME = "knip.json";
36714
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36715
36705
  const readJsonFileSafe = (filePath) => {
36716
36706
  let rawContents;
36717
36707
  try {
@@ -36727,10 +36717,10 @@ const readJsonFileSafe = (filePath) => {
36727
36717
  };
36728
36718
  const readKnipConfig = (rootDirectory) => {
36729
36719
  const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
36730
- if (isRecord$1(knipJson)) return knipJson;
36720
+ if (isRecord(knipJson)) return knipJson;
36731
36721
  const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
36732
- const packageKnipConfig = isRecord$1(packageJson) ? packageJson.knip : null;
36733
- return isRecord$1(packageKnipConfig) ? packageKnipConfig : null;
36722
+ const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
36723
+ return isRecord(packageKnipConfig) ? packageKnipConfig : null;
36734
36724
  };
36735
36725
  const normalizePatternList = (value) => {
36736
36726
  if (typeof value === "string" && value.length > 0) return [value];
@@ -36742,10 +36732,10 @@ const prefixWorkspacePatterns = (workspacePattern, patterns) => {
36742
36732
  return patterns.map((pattern) => pattern.startsWith("!") ? `!${normalizedWorkspacePattern}/${pattern.slice(1)}` : `${normalizedWorkspacePattern}/${pattern}`);
36743
36733
  };
36744
36734
  const collectKnipWorkspacePatterns = (workspaces, settingName) => {
36745
- if (!isRecord$1(workspaces)) return [];
36735
+ if (!isRecord(workspaces)) return [];
36746
36736
  const patterns = [];
36747
36737
  for (const [workspacePattern, workspaceConfig] of Object.entries(workspaces)) {
36748
- if (!isRecord$1(workspaceConfig)) continue;
36738
+ if (!isRecord(workspaceConfig)) continue;
36749
36739
  patterns.push(...prefixWorkspacePatterns(workspacePattern, normalizePatternList(workspaceConfig[settingName])));
36750
36740
  }
36751
36741
  return patterns;
@@ -36790,8 +36780,6 @@ const toCanonicalPath = (filePath) => {
36790
36780
  };
36791
36781
  const DEAD_CODE_PLUGIN = "deslop";
36792
36782
  const DEAD_CODE_CATEGORY = "Maintainability";
36793
- const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
36794
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36795
36783
  const DEAD_CODE_WORKER_SCRIPT = `
36796
36784
  const inputChunks = [];
36797
36785
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -36849,7 +36837,7 @@ process.stdin.on("end", () => {
36849
36837
  });
36850
36838
  `;
36851
36839
  const resolveTsConfigPath = (rootDirectory) => {
36852
- for (const filename of TSCONFIG_FILENAMES$1) {
36840
+ for (const filename of TSCONFIG_FILENAMES) {
36853
36841
  const candidate = Path.join(rootDirectory, filename);
36854
36842
  if (NFS.existsSync(candidate)) return candidate;
36855
36843
  }
@@ -37230,15 +37218,13 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
37230
37218
  })()) }));
37231
37219
  static layerOf = (diagnostics) => succeed$3(DeadCode, DeadCode.of({ run: () => fromIterable$1(diagnostics) }));
37232
37220
  };
37233
- const createNodeReadFileLinesSync = (rootDirectory) => {
37234
- return (filePath) => {
37235
- const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
37236
- try {
37237
- return NFS.readFileSync(absolutePath, "utf-8").split("\n");
37238
- } catch {
37239
- return null;
37240
- }
37241
- };
37221
+ const createNodeReadFileLinesSync = (rootDirectory) => (filePath) => {
37222
+ const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
37223
+ try {
37224
+ return NFS.readFileSync(absolutePath, "utf-8").split("\n");
37225
+ } catch {
37226
+ return null;
37227
+ }
37242
37228
  };
37243
37229
  var Files = class Files extends Service()("react-doctor/Files") {
37244
37230
  static layerNode = succeed$3(Files, Files.of({
@@ -37449,7 +37435,10 @@ var Git = class Git extends Service()("react-doctor/Git") {
37449
37435
  directory: input.directory,
37450
37436
  cause
37451
37437
  }) });
37452
- }));
37438
+ }), withSpan("git.exec", { attributes: {
37439
+ "git.command": input.command,
37440
+ "git.subcommand": input.args[0] ?? ""
37441
+ } }));
37453
37442
  const runGit = (directory, args) => runCommand({
37454
37443
  command: "git",
37455
37444
  args,
@@ -37477,7 +37466,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37477
37466
  ]);
37478
37467
  if (candidates.status !== 0) return null;
37479
37468
  return trimOrNull(candidates.stdout.split("\n")[0] ?? "");
37480
- });
37469
+ }).pipe(withSpan("Git.defaultBranch"));
37481
37470
  const branchExists = (directory, branch) => runGit(directory, [
37482
37471
  "rev-parse",
37483
37472
  "--verify",
@@ -37524,7 +37513,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37524
37513
  const result = resultOption.value;
37525
37514
  if (result.status !== 0) return null;
37526
37515
  return parseGithubViewerPermission(result.stdout);
37527
- }).pipe(catch_$1(() => succeed$2(null)));
37516
+ }).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.githubViewerPermission"));
37528
37517
  /**
37529
37518
  * Resolves a `--diff A..B` / `A...B` commit range into a changed-file
37530
37519
  * selection. Each endpoint is validated with `isSafeGitRevision`
@@ -37638,7 +37627,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37638
37627
  changedFiles: splitNullSeparated(diff.stdout),
37639
37628
  isCurrentChanges: false
37640
37629
  };
37641
- }),
37630
+ }).pipe(withSpan("Git.diffSelection")),
37642
37631
  stagedFilePaths: (directory) => runGit(directory, [
37643
37632
  "diff",
37644
37633
  "--cached",
@@ -37680,7 +37669,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37680
37669
  status: result.status,
37681
37670
  stdout: result.stdout
37682
37671
  };
37683
- }),
37672
+ }).pipe(withSpan("Git.grep")),
37684
37673
  changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
37685
37674
  if (files.length === 0) return [];
37686
37675
  if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
@@ -37696,7 +37685,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37696
37685
  ]);
37697
37686
  if (result.status !== 0) return null;
37698
37687
  return parseChangedLineRanges(result.stdout);
37699
- })
37688
+ }).pipe(withSpan("Git.changedLineRanges"))
37700
37689
  });
37701
37690
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
37702
37691
  /**
@@ -37911,7 +37900,7 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
37911
37900
  for (const [absolutePath, originalContent] of originalContents) try {
37912
37901
  NFS.writeFileSync(absolutePath, originalContent);
37913
37902
  } catch (error) {
37914
- 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`);
37903
+ process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
37915
37904
  }
37916
37905
  };
37917
37906
  const onExit = () => restore();
@@ -38017,7 +38006,7 @@ const resolveUserPlugin = (spec, configSourceDirectory) => {
38017
38006
  try {
38018
38007
  resolvedSpecifier = isRelative ? Path.resolve(configSourceDirectory, spec) : candidateRequire.resolve(spec);
38019
38008
  } catch (error) {
38020
- warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${error instanceof Error ? error.message : String(error)}`);
38009
+ warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${messageFromUnknown(error)}`);
38021
38010
  return null;
38022
38011
  }
38023
38012
  const { name, ruleNames } = readPluginShape(resolvedSpecifier, (target) => candidateRequire(target));
@@ -38089,8 +38078,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38089
38078
  }
38090
38079
  return enabled;
38091
38080
  };
38092
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
38093
- const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38081
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
38082
+ const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38094
38083
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38095
38084
  const jsPlugins = [];
38096
38085
  if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
@@ -38150,7 +38139,6 @@ const resolveOxlintBinary = () => {
38150
38139
  return Path.join(oxlintPackageDirectory, "bin", "oxlint");
38151
38140
  };
38152
38141
  const resolvePluginPath = () => esmRequire.resolve("oxlint-plugin-react-doctor");
38153
- const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
38154
38142
  const resolveTsConfigRelativePath = (rootDirectory) => {
38155
38143
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
38156
38144
  return null;
@@ -38522,7 +38510,7 @@ const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
38522
38510
  const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
38523
38511
  let currentNode = identifier.parent;
38524
38512
  while (currentNode) {
38525
- if (isScopeNode(currentNode)) {
38513
+ if (isScopeBoundary(currentNode)) {
38526
38514
  if (scopeContainsNonImportBinding(currentNode, currentNode, identifier.text)) return true;
38527
38515
  }
38528
38516
  if (currentNode === sourceFile) return false;
@@ -38613,11 +38601,10 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
38613
38601
  });
38614
38602
  return resolution;
38615
38603
  };
38616
- const isScopeNode = isScopeBoundary;
38617
38604
  const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, visitedDeclarations = /* @__PURE__ */ new Set()) => {
38618
38605
  let currentNode = identifier.parent;
38619
38606
  while (currentNode) {
38620
- if (isScopeNode(currentNode)) {
38607
+ if (isScopeBoundary(currentNode)) {
38621
38608
  const resolution = findResolutionInScope(currentNode, identifier.text, reactImportBindings, sourceFile, visitedDeclarations);
38622
38609
  if (resolution) return resolution;
38623
38610
  }
@@ -38787,9 +38774,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
38787
38774
  try {
38788
38775
  parsed = JSON.parse(sanitizedStdout);
38789
38776
  } catch {
38790
- throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38777
+ throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38791
38778
  }
38792
- if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38779
+ if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38793
38780
  const minifiedFileCache = /* @__PURE__ */ new Map();
38794
38781
  const isMinifiedDiagnosticFile = (filename) => {
38795
38782
  const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
@@ -38865,7 +38852,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
38865
38852
  child.kill("SIGKILL");
38866
38853
  reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
38867
38854
  kind: "timeout",
38868
- detail: `${spawnTimeoutMs / 1e3}s budget exceeded`
38855
+ detail: `${spawnTimeoutMs / MILLISECONDS_PER_SECOND}s budget exceeded`
38869
38856
  }) }));
38870
38857
  }, spawnTimeoutMs);
38871
38858
  timeoutHandle.unref?.();
@@ -39080,6 +39067,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
39080
39067
  NFS.closeSync(fileHandle);
39081
39068
  }
39082
39069
  };
39070
+ const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
39071
+ /**
39072
+ * Detects an oxlint config-load crash caused by the optional
39073
+ * `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
39074
+ * builds the partial-failure note for it; returns `null` when the failure
39075
+ * was anything else.
39076
+ *
39077
+ * oxlint prints a framed error to stdout (not stderr) and exits non-zero
39078
+ * when a `jsPlugins` entry can't be imported; that non-JSON stdout
39079
+ * surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
39080
+ * config load on it, leaving the plugin in would drop every curated
39081
+ * react-doctor diagnostic too — so the caller retries with the plugin
39082
+ * stripped (issue #833). Both markers sit at the start of oxlint's
39083
+ * message, so they survive the `preview` slice even for deep pnpm paths.
39084
+ */
39085
+ const reactHooksJsPluginDropNote = (error) => {
39086
+ if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
39087
+ const { preview } = error.reason;
39088
+ if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
39089
+ const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
39090
+ return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
39091
+ };
39083
39092
  /**
39084
39093
  * The oxlint runner. Composed of three pieces in `runners/oxlint/`:
39085
39094
  *
@@ -39107,15 +39116,16 @@ const runOxlint = async (options) => {
39107
39116
  const pluginPath = resolvePluginPath();
39108
39117
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
39109
39118
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
39110
- const buildConfig = (extendsForThisAttempt) => createOxlintConfig({
39119
+ const buildConfig = (overrides) => createOxlintConfig({
39111
39120
  pluginPath,
39112
39121
  project,
39113
39122
  customRulesOnly,
39114
- extendsPaths: extendsForThisAttempt,
39123
+ extendsPaths: overrides.extendsPaths,
39115
39124
  ignoredTags,
39116
39125
  serverAuthFunctionNames,
39117
39126
  severityControls,
39118
- userPlugins
39127
+ userPlugins,
39128
+ disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
39119
39129
  });
39120
39130
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
39121
39131
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -39151,12 +39161,22 @@ const runOxlint = async (options) => {
39151
39161
  outputMaxBytes,
39152
39162
  concurrency: options.concurrency
39153
39163
  });
39154
- writeOxlintConfig(configPath, buildConfig(extendsPaths));
39164
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
39155
39165
  try {
39156
39166
  return await runBatches();
39157
39167
  } catch (error) {
39168
+ const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
39169
+ if (reactHooksJsDropNote !== null) {
39170
+ writeOxlintConfig(configPath, buildConfig({
39171
+ extendsPaths,
39172
+ disableReactHooksJsPlugin: true
39173
+ }));
39174
+ const diagnostics = await runBatches();
39175
+ onPartialFailure?.(reactHooksJsDropNote);
39176
+ return diagnostics;
39177
+ }
39158
39178
  if (extendsPaths.length === 0) throw error;
39159
- writeOxlintConfig(configPath, buildConfig([]));
39179
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
39160
39180
  return await runBatches();
39161
39181
  }
39162
39182
  } finally {
@@ -39954,7 +39974,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
39954
39974
  }))))))));
39955
39975
  const deadCodeFailureState = yield* get$2(deadCodeFailure);
39956
39976
  const scanElapsedMilliseconds = Date.now() - scanStartTime;
39957
- const scanElapsedSeconds = (scanElapsedMilliseconds / 1e3).toFixed(1);
39977
+ const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
39958
39978
  if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
39959
39979
  else if (input.suppressScanSummary) yield* scanProgress.stop();
39960
39980
  else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
@@ -40168,7 +40188,7 @@ const materializeSourceTree = (input) => gen(function* () {
40168
40188
  static layerNode = effect(StagedFiles, gen(function* () {
40169
40189
  const git = yield* Git;
40170
40190
  return StagedFiles.of({
40171
- discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile))),
40191
+ discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile)), withSpan("StagedFiles.discoverSourceFiles")),
40172
40192
  materialize: ({ directory, stagedFiles, tempDirectory }) => materializeSourceTree({
40173
40193
  directory,
40174
40194
  files: stagedFiles,
@@ -40178,7 +40198,7 @@ const materializeSourceTree = (input) => gen(function* () {
40178
40198
  tempDirectory: tree.tempDirectory,
40179
40199
  stagedFiles: tree.materializedFiles,
40180
40200
  cleanup: tree.cleanup
40181
- })))
40201
+ })), withSpan("StagedFiles.materialize"))
40182
40202
  });
40183
40203
  }));
40184
40204
  /**
@@ -40310,6 +40330,7 @@ const buildJsonReport = (input) => {
40310
40330
  score: result.score,
40311
40331
  skippedChecks: result.skippedChecks,
40312
40332
  ...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
40333
+ ...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
40313
40334
  elapsedMilliseconds: result.elapsedMilliseconds
40314
40335
  }));
40315
40336
  const flattenedDiagnostics = projects.flatMap((entry) => entry.diagnostics);
@@ -40576,4 +40597,4 @@ const toJsonReport = (result, options) => buildJsonReport({
40576
40597
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
40577
40598
 
40578
40599
  //# sourceMappingURL=index.js.map
40579
- //# debugId=b4f6f7e7-99db-553a-aa15-14b646162f39
40600
+ //# debugId=efaa91ed-3659-50a5-b802-51753472b2d6