react-doctor 0.5.6-dev.8908f98 → 0.5.6-dev.93b796d

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
@@ -9280,9 +9280,10 @@ interface ReactDoctorConfig {
9280
9280
  * list, user-provided names are treated as distinctive and never
9281
9281
  * subject to receiver-object disambiguation.
9282
9282
  *
9283
- * Use this to teach react-doctor about custom auth guards in
9284
- * codebases that wrap their auth library — e.g. a project-local
9285
- * `requireWorkspaceMember` or `ensureSignedIn`.
9283
+ * Common guard conventions are already recognized automatically
9284
+ * (`requireAdmin`, `ensureSignedIn`, `getCurrentUser`, `hasRole`, …),
9285
+ * so this is only needed for domain-specific guards whose names carry
9286
+ * no auth noun — e.g. a project-local `requireWorkspaceMember`.
9286
9287
  */
9287
9288
  serverAuthFunctionNames?: string[];
9288
9289
  /**
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]="f55d58c3-c6f3-598b-bd99-d0abe85fd35e")}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]="36898527-7b70-58ed-8b5b-19ca48903764")}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";
@@ -33620,6 +33620,11 @@ const ES_TARGET_YEAR_BY_NAME = {
33620
33620
  esnext: 9999
33621
33621
  };
33622
33622
  /**
33623
+ * tsconfig filenames probed when resolving a project's TypeScript
33624
+ * compiler options — the root config first, then a monorepo base config.
33625
+ */
33626
+ const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
33627
+ /**
33623
33628
  * Project-config files that `StagedFiles.materialize` copies into
33624
33629
  * the temp directory alongside staged sources so oxlint resolves
33625
33630
  * `tsconfig` / `package.json` / lint configs the same way it would
@@ -36705,8 +36710,8 @@ const collectIgnorePatterns = (rootDirectory) => {
36705
36710
  cachedPatternsByRoot.set(rootDirectory, patterns);
36706
36711
  return patterns;
36707
36712
  };
36713
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36708
36714
  const KNIP_JSON_FILENAME = "knip.json";
36709
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36710
36715
  const readJsonFileSafe = (filePath) => {
36711
36716
  let rawContents;
36712
36717
  try {
@@ -36722,10 +36727,10 @@ const readJsonFileSafe = (filePath) => {
36722
36727
  };
36723
36728
  const readKnipConfig = (rootDirectory) => {
36724
36729
  const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
36725
- if (isRecord$1(knipJson)) return knipJson;
36730
+ if (isRecord(knipJson)) return knipJson;
36726
36731
  const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
36727
- const packageKnipConfig = isRecord$1(packageJson) ? packageJson.knip : null;
36728
- return isRecord$1(packageKnipConfig) ? packageKnipConfig : null;
36732
+ const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
36733
+ return isRecord(packageKnipConfig) ? packageKnipConfig : null;
36729
36734
  };
36730
36735
  const normalizePatternList = (value) => {
36731
36736
  if (typeof value === "string" && value.length > 0) return [value];
@@ -36737,10 +36742,10 @@ const prefixWorkspacePatterns = (workspacePattern, patterns) => {
36737
36742
  return patterns.map((pattern) => pattern.startsWith("!") ? `!${normalizedWorkspacePattern}/${pattern.slice(1)}` : `${normalizedWorkspacePattern}/${pattern}`);
36738
36743
  };
36739
36744
  const collectKnipWorkspacePatterns = (workspaces, settingName) => {
36740
- if (!isRecord$1(workspaces)) return [];
36745
+ if (!isRecord(workspaces)) return [];
36741
36746
  const patterns = [];
36742
36747
  for (const [workspacePattern, workspaceConfig] of Object.entries(workspaces)) {
36743
- if (!isRecord$1(workspaceConfig)) continue;
36748
+ if (!isRecord(workspaceConfig)) continue;
36744
36749
  patterns.push(...prefixWorkspacePatterns(workspacePattern, normalizePatternList(workspaceConfig[settingName])));
36745
36750
  }
36746
36751
  return patterns;
@@ -36750,12 +36755,11 @@ const collectKnipPatterns = (rootDirectory, settingName) => {
36750
36755
  if (!config) return [];
36751
36756
  return [...normalizePatternList(config[settingName]), ...collectKnipWorkspacePatterns(config.workspaces, settingName)];
36752
36757
  };
36753
- const collectDeadCodeIgnorePatterns = (rootDirectory, userConfig) => {
36758
+ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
36754
36759
  const seen = /* @__PURE__ */ new Set();
36755
36760
  const sources = [
36756
36761
  readIgnoreFile(path.join(rootDirectory, ".gitignore")),
36757
36762
  collectIgnorePatterns(rootDirectory),
36758
- userConfig?.ignore?.files ?? [],
36759
36763
  collectKnipPatterns(rootDirectory, "ignore")
36760
36764
  ];
36761
36765
  for (const source of sources) for (const pattern of source) seen.add(pattern);
@@ -36786,8 +36790,6 @@ const toCanonicalPath = (filePath) => {
36786
36790
  };
36787
36791
  const DEAD_CODE_PLUGIN = "deslop";
36788
36792
  const DEAD_CODE_CATEGORY = "Maintainability";
36789
- const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
36790
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36791
36793
  const DEAD_CODE_WORKER_SCRIPT = `
36792
36794
  const inputChunks = [];
36793
36795
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -36845,7 +36847,7 @@ process.stdin.on("end", () => {
36845
36847
  });
36846
36848
  `;
36847
36849
  const resolveTsConfigPath = (rootDirectory) => {
36848
- for (const filename of TSCONFIG_FILENAMES$1) {
36850
+ for (const filename of TSCONFIG_FILENAMES) {
36849
36851
  const candidate = Path.join(rootDirectory, filename);
36850
36852
  if (NFS.existsSync(candidate)) return candidate;
36851
36853
  }
@@ -37033,11 +37035,10 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve
37033
37035
  });
37034
37036
  });
37035
37037
  const checkDeadCode = async (options) => {
37036
- const { userConfig } = options;
37037
37038
  const rootDirectory = toCanonicalPath(options.rootDirectory);
37038
37039
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37039
37040
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37040
- const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
37041
+ const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37041
37042
  const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37042
37043
  rootDirectory,
37043
37044
  entryPatterns,
@@ -38086,8 +38087,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38086
38087
  }
38087
38088
  return enabled;
38088
38089
  };
38089
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
38090
- const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38090
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
38091
+ const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38091
38092
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38092
38093
  const jsPlugins = [];
38093
38094
  if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
@@ -38147,7 +38148,6 @@ const resolveOxlintBinary = () => {
38147
38148
  return Path.join(oxlintPackageDirectory, "bin", "oxlint");
38148
38149
  };
38149
38150
  const resolvePluginPath = () => esmRequire.resolve("oxlint-plugin-react-doctor");
38150
- const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
38151
38151
  const resolveTsConfigRelativePath = (rootDirectory) => {
38152
38152
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
38153
38153
  return null;
@@ -38519,7 +38519,7 @@ const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
38519
38519
  const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
38520
38520
  let currentNode = identifier.parent;
38521
38521
  while (currentNode) {
38522
- if (isScopeNode(currentNode)) {
38522
+ if (isScopeBoundary(currentNode)) {
38523
38523
  if (scopeContainsNonImportBinding(currentNode, currentNode, identifier.text)) return true;
38524
38524
  }
38525
38525
  if (currentNode === sourceFile) return false;
@@ -38610,11 +38610,10 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
38610
38610
  });
38611
38611
  return resolution;
38612
38612
  };
38613
- const isScopeNode = isScopeBoundary;
38614
38613
  const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, visitedDeclarations = /* @__PURE__ */ new Set()) => {
38615
38614
  let currentNode = identifier.parent;
38616
38615
  while (currentNode) {
38617
- if (isScopeNode(currentNode)) {
38616
+ if (isScopeBoundary(currentNode)) {
38618
38617
  const resolution = findResolutionInScope(currentNode, identifier.text, reactImportBindings, sourceFile, visitedDeclarations);
38619
38618
  if (resolution) return resolution;
38620
38619
  }
@@ -38784,9 +38783,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
38784
38783
  try {
38785
38784
  parsed = JSON.parse(sanitizedStdout);
38786
38785
  } catch {
38787
- throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38786
+ throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38788
38787
  }
38789
- if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38788
+ if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38790
38789
  const minifiedFileCache = /* @__PURE__ */ new Map();
38791
38790
  const isMinifiedDiagnosticFile = (filename) => {
38792
38791
  const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
@@ -39077,6 +39076,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
39077
39076
  NFS.closeSync(fileHandle);
39078
39077
  }
39079
39078
  };
39079
+ const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
39080
+ /**
39081
+ * Detects an oxlint config-load crash caused by the optional
39082
+ * `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
39083
+ * builds the partial-failure note for it; returns `null` when the failure
39084
+ * was anything else.
39085
+ *
39086
+ * oxlint prints a framed error to stdout (not stderr) and exits non-zero
39087
+ * when a `jsPlugins` entry can't be imported; that non-JSON stdout
39088
+ * surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
39089
+ * config load on it, leaving the plugin in would drop every curated
39090
+ * react-doctor diagnostic too — so the caller retries with the plugin
39091
+ * stripped (issue #833). Both markers sit at the start of oxlint's
39092
+ * message, so they survive the `preview` slice even for deep pnpm paths.
39093
+ */
39094
+ const reactHooksJsPluginDropNote = (error) => {
39095
+ if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
39096
+ const { preview } = error.reason;
39097
+ if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
39098
+ const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
39099
+ return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
39100
+ };
39080
39101
  /**
39081
39102
  * The oxlint runner. Composed of three pieces in `runners/oxlint/`:
39082
39103
  *
@@ -39104,15 +39125,16 @@ const runOxlint = async (options) => {
39104
39125
  const pluginPath = resolvePluginPath();
39105
39126
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
39106
39127
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
39107
- const buildConfig = (extendsForThisAttempt) => createOxlintConfig({
39128
+ const buildConfig = (overrides) => createOxlintConfig({
39108
39129
  pluginPath,
39109
39130
  project,
39110
39131
  customRulesOnly,
39111
- extendsPaths: extendsForThisAttempt,
39132
+ extendsPaths: overrides.extendsPaths,
39112
39133
  ignoredTags,
39113
39134
  serverAuthFunctionNames,
39114
39135
  severityControls,
39115
- userPlugins
39136
+ userPlugins,
39137
+ disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
39116
39138
  });
39117
39139
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
39118
39140
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -39148,12 +39170,22 @@ const runOxlint = async (options) => {
39148
39170
  outputMaxBytes,
39149
39171
  concurrency: options.concurrency
39150
39172
  });
39151
- writeOxlintConfig(configPath, buildConfig(extendsPaths));
39173
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
39152
39174
  try {
39153
39175
  return await runBatches();
39154
39176
  } catch (error) {
39177
+ const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
39178
+ if (reactHooksJsDropNote !== null) {
39179
+ writeOxlintConfig(configPath, buildConfig({
39180
+ extendsPaths,
39181
+ disableReactHooksJsPlugin: true
39182
+ }));
39183
+ const diagnostics = await runBatches();
39184
+ onPartialFailure?.(reactHooksJsDropNote);
39185
+ return diagnostics;
39186
+ }
39155
39187
  if (extendsPaths.length === 0) throw error;
39156
- writeOxlintConfig(configPath, buildConfig([]));
39188
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
39157
39189
  return await runBatches();
39158
39190
  }
39159
39191
  } finally {
@@ -40573,4 +40605,4 @@ const toJsonReport = (result, options) => buildJsonReport({
40573
40605
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
40574
40606
 
40575
40607
  //# sourceMappingURL=index.js.map
40576
- //# debugId=f55d58c3-c6f3-598b-bd99-d0abe85fd35e
40608
+ //# debugId=36898527-7b70-58ed-8b5b-19ca48903764
package/dist/lsp.js CHANGED
@@ -33657,6 +33657,11 @@ const ES_TARGET_YEAR_BY_NAME = {
33657
33657
  esnext: 9999
33658
33658
  };
33659
33659
  /**
33660
+ * tsconfig filenames probed when resolving a project's TypeScript
33661
+ * compiler options — the root config first, then a monorepo base config.
33662
+ */
33663
+ const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
33664
+ /**
33660
33665
  * Project-config files that `StagedFiles.materialize` copies into
33661
33666
  * the temp directory alongside staged sources so oxlint resolves
33662
33667
  * `tsconfig` / `package.json` / lint configs the same way it would
@@ -36691,8 +36696,8 @@ const collectIgnorePatterns = (rootDirectory) => {
36691
36696
  cachedPatternsByRoot.set(rootDirectory, patterns);
36692
36697
  return patterns;
36693
36698
  };
36699
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36694
36700
  const KNIP_JSON_FILENAME = "knip.json";
36695
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36696
36701
  const readJsonFileSafe = (filePath) => {
36697
36702
  let rawContents;
36698
36703
  try {
@@ -36708,10 +36713,10 @@ const readJsonFileSafe = (filePath) => {
36708
36713
  };
36709
36714
  const readKnipConfig = (rootDirectory) => {
36710
36715
  const knipJson = readJsonFileSafe(path.join(rootDirectory, KNIP_JSON_FILENAME));
36711
- if (isRecord$1(knipJson)) return knipJson;
36716
+ if (isRecord(knipJson)) return knipJson;
36712
36717
  const packageJson = readJsonFileSafe(path.join(rootDirectory, "package.json"));
36713
- const packageKnipConfig = isRecord$1(packageJson) ? packageJson.knip : null;
36714
- return isRecord$1(packageKnipConfig) ? packageKnipConfig : null;
36718
+ const packageKnipConfig = isRecord(packageJson) ? packageJson.knip : null;
36719
+ return isRecord(packageKnipConfig) ? packageKnipConfig : null;
36715
36720
  };
36716
36721
  const normalizePatternList = (value) => {
36717
36722
  if (typeof value === "string" && value.length > 0) return [value];
@@ -36723,10 +36728,10 @@ const prefixWorkspacePatterns = (workspacePattern, patterns) => {
36723
36728
  return patterns.map((pattern) => pattern.startsWith("!") ? `!${normalizedWorkspacePattern}/${pattern.slice(1)}` : `${normalizedWorkspacePattern}/${pattern}`);
36724
36729
  };
36725
36730
  const collectKnipWorkspacePatterns = (workspaces, settingName) => {
36726
- if (!isRecord$1(workspaces)) return [];
36731
+ if (!isRecord(workspaces)) return [];
36727
36732
  const patterns = [];
36728
36733
  for (const [workspacePattern, workspaceConfig] of Object.entries(workspaces)) {
36729
- if (!isRecord$1(workspaceConfig)) continue;
36734
+ if (!isRecord(workspaceConfig)) continue;
36730
36735
  patterns.push(...prefixWorkspacePatterns(workspacePattern, normalizePatternList(workspaceConfig[settingName])));
36731
36736
  }
36732
36737
  return patterns;
@@ -36736,12 +36741,11 @@ const collectKnipPatterns = (rootDirectory, settingName) => {
36736
36741
  if (!config) return [];
36737
36742
  return [...normalizePatternList(config[settingName]), ...collectKnipWorkspacePatterns(config.workspaces, settingName)];
36738
36743
  };
36739
- const collectDeadCodeIgnorePatterns = (rootDirectory, userConfig) => {
36744
+ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
36740
36745
  const seen = /* @__PURE__ */ new Set();
36741
36746
  const sources = [
36742
36747
  readIgnoreFile(path.join(rootDirectory, ".gitignore")),
36743
36748
  collectIgnorePatterns(rootDirectory),
36744
- userConfig?.ignore?.files ?? [],
36745
36749
  collectKnipPatterns(rootDirectory, "ignore")
36746
36750
  ];
36747
36751
  for (const source of sources) for (const pattern of source) seen.add(pattern);
@@ -36772,8 +36776,6 @@ const toCanonicalPath = (filePath) => {
36772
36776
  };
36773
36777
  const DEAD_CODE_PLUGIN = "deslop";
36774
36778
  const DEAD_CODE_CATEGORY = "Maintainability";
36775
- const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
36776
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36777
36779
  const DEAD_CODE_WORKER_SCRIPT = `
36778
36780
  const inputChunks = [];
36779
36781
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -36831,7 +36833,7 @@ process.stdin.on("end", () => {
36831
36833
  });
36832
36834
  `;
36833
36835
  const resolveTsConfigPath = (rootDirectory) => {
36834
- for (const filename of TSCONFIG_FILENAMES$1) {
36836
+ for (const filename of TSCONFIG_FILENAMES) {
36835
36837
  const candidate = Path.join(rootDirectory, filename);
36836
36838
  if (NFS.existsSync(candidate)) return candidate;
36837
36839
  }
@@ -37019,11 +37021,10 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve
37019
37021
  });
37020
37022
  });
37021
37023
  const checkDeadCode = async (options) => {
37022
- const { userConfig } = options;
37023
37024
  const rootDirectory = toCanonicalPath(options.rootDirectory);
37024
37025
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37025
37026
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37026
- const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
37027
+ const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37027
37028
  const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37028
37029
  rootDirectory,
37029
37030
  entryPatterns,
@@ -38072,8 +38073,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38072
38073
  }
38073
38074
  return enabled;
38074
38075
  };
38075
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
38076
- const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38076
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
38077
+ const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38077
38078
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38078
38079
  const jsPlugins = [];
38079
38080
  if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
@@ -38133,7 +38134,6 @@ const resolveOxlintBinary = () => {
38133
38134
  return Path.join(oxlintPackageDirectory, "bin", "oxlint");
38134
38135
  };
38135
38136
  const resolvePluginPath = () => esmRequire.resolve("oxlint-plugin-react-doctor");
38136
- const TSCONFIG_FILENAMES = ["tsconfig.json", "tsconfig.base.json"];
38137
38137
  const resolveTsConfigRelativePath = (rootDirectory) => {
38138
38138
  for (const filename of TSCONFIG_FILENAMES) if (NFS.existsSync(Path.join(rootDirectory, filename))) return `./${filename}`;
38139
38139
  return null;
@@ -38505,7 +38505,7 @@ const scopeContainsNonImportBinding = (node, scopeNode, identifierName) => {
38505
38505
  const isIdentifierShadowedByLocalBinding = (identifier, sourceFile) => {
38506
38506
  let currentNode = identifier.parent;
38507
38507
  while (currentNode) {
38508
- if (isScopeNode(currentNode)) {
38508
+ if (isScopeBoundary(currentNode)) {
38509
38509
  if (scopeContainsNonImportBinding(currentNode, currentNode, identifier.text)) return true;
38510
38510
  }
38511
38511
  if (currentNode === sourceFile) return false;
@@ -38596,11 +38596,10 @@ const findResolutionInScope = (scopeNode, identifierName, reactImportBindings, s
38596
38596
  });
38597
38597
  return resolution;
38598
38598
  };
38599
- const isScopeNode = isScopeBoundary;
38600
38599
  const resolveIdentifierBinding = (identifier, reactImportBindings, sourceFile, visitedDeclarations = /* @__PURE__ */ new Set()) => {
38601
38600
  let currentNode = identifier.parent;
38602
38601
  while (currentNode) {
38603
- if (isScopeNode(currentNode)) {
38602
+ if (isScopeBoundary(currentNode)) {
38604
38603
  const resolution = findResolutionInScope(currentNode, identifier.text, reactImportBindings, sourceFile, visitedDeclarations);
38605
38604
  if (resolution) return resolution;
38606
38605
  }
@@ -38770,9 +38769,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
38770
38769
  try {
38771
38770
  parsed = JSON.parse(sanitizedStdout);
38772
38771
  } catch {
38773
- throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38772
+ throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38774
38773
  }
38775
- if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38774
+ if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38776
38775
  const minifiedFileCache = /* @__PURE__ */ new Map();
38777
38776
  const isMinifiedDiagnosticFile = (filename) => {
38778
38777
  const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
@@ -39063,6 +39062,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
39063
39062
  NFS.closeSync(fileHandle);
39064
39063
  }
39065
39064
  };
39065
+ const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
39066
+ /**
39067
+ * Detects an oxlint config-load crash caused by the optional
39068
+ * `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
39069
+ * builds the partial-failure note for it; returns `null` when the failure
39070
+ * was anything else.
39071
+ *
39072
+ * oxlint prints a framed error to stdout (not stderr) and exits non-zero
39073
+ * when a `jsPlugins` entry can't be imported; that non-JSON stdout
39074
+ * surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
39075
+ * config load on it, leaving the plugin in would drop every curated
39076
+ * react-doctor diagnostic too — so the caller retries with the plugin
39077
+ * stripped (issue #833). Both markers sit at the start of oxlint's
39078
+ * message, so they survive the `preview` slice even for deep pnpm paths.
39079
+ */
39080
+ const reactHooksJsPluginDropNote = (error) => {
39081
+ if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
39082
+ const { preview } = error.reason;
39083
+ if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
39084
+ const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
39085
+ return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
39086
+ };
39066
39087
  /**
39067
39088
  * The oxlint runner. Composed of three pieces in `runners/oxlint/`:
39068
39089
  *
@@ -39090,15 +39111,16 @@ const runOxlint = async (options) => {
39090
39111
  const pluginPath = resolvePluginPath();
39091
39112
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
39092
39113
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
39093
- const buildConfig = (extendsForThisAttempt) => createOxlintConfig({
39114
+ const buildConfig = (overrides) => createOxlintConfig({
39094
39115
  pluginPath,
39095
39116
  project,
39096
39117
  customRulesOnly,
39097
- extendsPaths: extendsForThisAttempt,
39118
+ extendsPaths: overrides.extendsPaths,
39098
39119
  ignoredTags,
39099
39120
  serverAuthFunctionNames,
39100
39121
  severityControls,
39101
- userPlugins
39122
+ userPlugins,
39123
+ disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
39102
39124
  });
39103
39125
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
39104
39126
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -39134,12 +39156,22 @@ const runOxlint = async (options) => {
39134
39156
  outputMaxBytes,
39135
39157
  concurrency: options.concurrency
39136
39158
  });
39137
- writeOxlintConfig(configPath, buildConfig(extendsPaths));
39159
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
39138
39160
  try {
39139
39161
  return await runBatches();
39140
39162
  } catch (error) {
39163
+ const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
39164
+ if (reactHooksJsDropNote !== null) {
39165
+ writeOxlintConfig(configPath, buildConfig({
39166
+ extendsPaths,
39167
+ disableReactHooksJsPlugin: true
39168
+ }));
39169
+ const diagnostics = await runBatches();
39170
+ onPartialFailure?.(reactHooksJsDropNote);
39171
+ return diagnostics;
39172
+ }
39141
39173
  if (extendsPaths.length === 0) throw error;
39142
- writeOxlintConfig(configPath, buildConfig([]));
39174
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
39143
39175
  return await runBatches();
39144
39176
  }
39145
39177
  } finally {
@@ -40971,6 +41003,11 @@ const createProjectGraph = (options) => {
40971
41003
  }
40972
41004
  };
40973
41005
  };
41006
+ const toProjectRelative = (projectDirectory, filePath) => {
41007
+ const relative = Path.relative(projectDirectory, filePath).replace(/\\/g, "/");
41008
+ if (relative.length === 0 || relative.startsWith("../") || Path.isAbsolute(relative)) return null;
41009
+ return relative;
41010
+ };
40974
41011
  const resolveCacheFilePath = (projectDirectory) => {
40975
41012
  const nodeModules = path.join(projectDirectory, "node_modules");
40976
41013
  if (fs.existsSync(nodeModules)) return path.join(nodeModules, ".cache", "react-doctor", "lint-cache.json");
@@ -41031,11 +41068,6 @@ const createLintCache = (input) => {
41031
41068
  };
41032
41069
  const OVERLAY_TEMP_PREFIX = "react-doctor-lsp-";
41033
41070
  const OVERLAY_CONFIG_FILENAMES = [...new Set([...STAGED_FILES_PROJECT_CONFIG_FILENAMES, ...ADOPTABLE_LINT_CONFIG_FILENAMES])];
41034
- const toProjectRelative$1 = (projectDirectory, filePath) => {
41035
- const relative = path.relative(projectDirectory, filePath).replace(/\\/g, "/");
41036
- if (relative.length === 0 || relative.startsWith("../") || path.isAbsolute(relative)) return null;
41037
- return relative;
41038
- };
41039
41071
  /**
41040
41072
  * Writes the live (possibly unsaved) content of the target files into a
41041
41073
  * throwaway temp tree that mirrors the project, alongside the well-known
@@ -41049,7 +41081,7 @@ const materializeOverlay = (input) => {
41049
41081
  const relativePaths = [];
41050
41082
  try {
41051
41083
  for (const filePath of input.files) {
41052
- const relative = toProjectRelative$1(input.projectDirectory, filePath);
41084
+ const relative = toProjectRelative(input.projectDirectory, filePath);
41053
41085
  if (relative === null) continue;
41054
41086
  const content = input.readText(filePath);
41055
41087
  if (content === null) continue;
@@ -41097,11 +41129,6 @@ const materializeOverlay = (input) => {
41097
41129
  throw error;
41098
41130
  }
41099
41131
  };
41100
- const toProjectRelative = (projectDirectory, filePath) => {
41101
- const relative = path.relative(projectDirectory, filePath).replace(/\\/g, "/");
41102
- if (relative.length === 0 || relative.startsWith("../") || path.isAbsolute(relative)) return null;
41103
- return relative;
41104
- };
41105
41132
  /**
41106
41133
  * Resolves a diagnostic's (possibly relative, possibly overlay-temp)
41107
41134
  * file path back to the canonical absolute path inside the real project.
@@ -42358,5 +42385,5 @@ const startLanguageServer = () => {
42358
42385
  };
42359
42386
  //#endregion
42360
42387
  export { startLanguageServer };
42361
- !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]="8882c2be-9063-5954-a6a3-fc287e8c715f")}catch(e){}}();
42362
- //# debugId=8882c2be-9063-5954-a6a3-fc287e8c715f
42388
+ !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]="42683a4c-8da1-5155-bf9c-b9a69b9718e6")}catch(e){}}();
42389
+ //# debugId=42683a4c-8da1-5155-bf9c-b9a69b9718e6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.6-dev.8908f98",
3
+ "version": "0.5.6-dev.93b796d",
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.8908f98"
67
+ "oxlint-plugin-react-doctor": "0.5.6-dev.93b796d"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/babel__code-frame": "^7.27.0",
@@ -72,9 +72,9 @@
72
72
  "@xterm/headless": "^6.0.0",
73
73
  "commander": "^14.0.3",
74
74
  "ora": "^9.4.0",
75
- "@react-doctor/api": "0.5.6",
75
+ "@react-doctor/core": "0.5.6",
76
76
  "@react-doctor/language-server": "0.5.6",
77
- "@react-doctor/core": "0.5.6"
77
+ "@react-doctor/api": "0.5.6"
78
78
  },
79
79
  "engines": {
80
80
  "node": "^20.19.0 || >=22.13.0"