react-doctor 0.5.6-dev.15238de → 0.5.6-dev.424d8f9

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/cli.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]="fd7e91f8-8458-50a2-99d9-a1ea8f8bfaff")}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]="fc691f8f-4aa6-5f96-8491-8e4e53439952")}catch(e){}}();
3
3
  import { createRequire } from "node:module";
4
4
  import * as NodeChildProcess from "node:child_process";
5
5
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
@@ -39692,10 +39692,15 @@ const buildCapabilities = (project) => {
39692
39692
  }
39693
39693
  if (project.tailwindVersion !== null) {
39694
39694
  capabilities.add("tailwind");
39695
- if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
39695
+ const tailwind = parseTailwindMajorMinor(project.tailwindVersion);
39696
+ if (isTailwindAtLeast(tailwind, {
39696
39697
  major: 3,
39697
39698
  minor: 4
39698
39699
  })) capabilities.add("tailwind:3.4");
39700
+ if (tailwind !== null && isTailwindAtLeast(tailwind, {
39701
+ major: 4,
39702
+ minor: 0
39703
+ })) capabilities.add("tailwind:4");
39699
39704
  }
39700
39705
  if (project.zodVersion !== null) {
39701
39706
  capabilities.add("zod");
@@ -39986,12 +39991,11 @@ const collectKnipPatterns = (rootDirectory, settingName) => {
39986
39991
  if (!config) return [];
39987
39992
  return [...normalizePatternList(config[settingName]), ...collectKnipWorkspacePatterns(config.workspaces, settingName)];
39988
39993
  };
39989
- const collectDeadCodeIgnorePatterns = (rootDirectory, userConfig) => {
39994
+ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
39990
39995
  const seen = /* @__PURE__ */ new Set();
39991
39996
  const sources = [
39992
39997
  readIgnoreFile(path.join(rootDirectory, ".gitignore")),
39993
39998
  collectIgnorePatterns(rootDirectory),
39994
- userConfig?.ignore?.files ?? [],
39995
39999
  collectKnipPatterns(rootDirectory, "ignore")
39996
40000
  ];
39997
40001
  for (const source of sources) for (const pattern of source) seen.add(pattern);
@@ -40269,11 +40273,10 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve
40269
40273
  });
40270
40274
  });
40271
40275
  const checkDeadCode = async (options) => {
40272
- const { userConfig } = options;
40273
40276
  const rootDirectory = toCanonicalPath(options.rootDirectory);
40274
40277
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
40275
40278
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
40276
- const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
40279
+ const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
40277
40280
  const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
40278
40281
  rootDirectory,
40279
40282
  entryPatterns,
@@ -41322,8 +41325,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
41322
41325
  }
41323
41326
  return enabled;
41324
41327
  };
41325
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
41326
- const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
41328
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
41329
+ const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
41327
41330
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
41328
41331
  const jsPlugins = [];
41329
41332
  if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
@@ -42020,9 +42023,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
42020
42023
  try {
42021
42024
  parsed = JSON.parse(sanitizedStdout);
42022
42025
  } catch {
42023
- throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
42026
+ throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
42024
42027
  }
42025
- if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
42028
+ if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
42026
42029
  const minifiedFileCache = /* @__PURE__ */ new Map();
42027
42030
  const isMinifiedDiagnosticFile = (filename) => {
42028
42031
  const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
@@ -42313,6 +42316,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
42313
42316
  NFS.closeSync(fileHandle);
42314
42317
  }
42315
42318
  };
42319
+ const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
42320
+ /**
42321
+ * Detects an oxlint config-load crash caused by the optional
42322
+ * `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
42323
+ * builds the partial-failure note for it; returns `null` when the failure
42324
+ * was anything else.
42325
+ *
42326
+ * oxlint prints a framed error to stdout (not stderr) and exits non-zero
42327
+ * when a `jsPlugins` entry can't be imported; that non-JSON stdout
42328
+ * surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
42329
+ * config load on it, leaving the plugin in would drop every curated
42330
+ * react-doctor diagnostic too — so the caller retries with the plugin
42331
+ * stripped (issue #833). Both markers sit at the start of oxlint's
42332
+ * message, so they survive the `preview` slice even for deep pnpm paths.
42333
+ */
42334
+ const reactHooksJsPluginDropNote = (error) => {
42335
+ if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
42336
+ const { preview } = error.reason;
42337
+ if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
42338
+ const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
42339
+ return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
42340
+ };
42316
42341
  /**
42317
42342
  * The oxlint runner. Composed of three pieces in `runners/oxlint/`:
42318
42343
  *
@@ -42340,15 +42365,16 @@ const runOxlint = async (options) => {
42340
42365
  const pluginPath = resolvePluginPath();
42341
42366
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
42342
42367
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
42343
- const buildConfig = (extendsForThisAttempt) => createOxlintConfig({
42368
+ const buildConfig = (overrides) => createOxlintConfig({
42344
42369
  pluginPath,
42345
42370
  project,
42346
42371
  customRulesOnly,
42347
- extendsPaths: extendsForThisAttempt,
42372
+ extendsPaths: overrides.extendsPaths,
42348
42373
  ignoredTags,
42349
42374
  serverAuthFunctionNames,
42350
42375
  severityControls,
42351
- userPlugins
42376
+ userPlugins,
42377
+ disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
42352
42378
  });
42353
42379
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
42354
42380
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -42384,12 +42410,22 @@ const runOxlint = async (options) => {
42384
42410
  outputMaxBytes,
42385
42411
  concurrency: options.concurrency
42386
42412
  });
42387
- writeOxlintConfig(configPath, buildConfig(extendsPaths));
42413
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
42388
42414
  try {
42389
42415
  return await runBatches();
42390
42416
  } catch (error) {
42417
+ const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
42418
+ if (reactHooksJsDropNote !== null) {
42419
+ writeOxlintConfig(configPath, buildConfig({
42420
+ extendsPaths,
42421
+ disableReactHooksJsPlugin: true
42422
+ }));
42423
+ const diagnostics = await runBatches();
42424
+ onPartialFailure?.(reactHooksJsDropNote);
42425
+ return diagnostics;
42426
+ }
42391
42427
  if (extendsPaths.length === 0) throw error;
42392
- writeOxlintConfig(configPath, buildConfig([]));
42428
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
42393
42429
  return await runBatches();
42394
42430
  }
42395
42431
  } finally {
@@ -43972,7 +44008,7 @@ const makeNoopConsole = () => ({
43972
44008
  });
43973
44009
  //#endregion
43974
44010
  //#region src/cli/utils/version.ts
43975
- const VERSION = "0.5.6-dev.15238de";
44011
+ const VERSION = "0.5.6-dev.424d8f9";
43976
44012
  //#endregion
43977
44013
  //#region src/cli/utils/json-mode.ts
43978
44014
  let context = null;
@@ -44330,13 +44366,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
44330
44366
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
44331
44367
  * standard `SENTRY_RELEASE` override.
44332
44368
  */
44333
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.15238de`;
44369
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.424d8f9`;
44334
44370
  /**
44335
44371
  * Deployment environment shown in Sentry's environment filter. Defaults to
44336
44372
  * `production` for tagged releases and `development` for dev/unbuilt versions,
44337
44373
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
44338
44374
  */
44339
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.15238de") ? "development" : "production");
44375
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.424d8f9") ? "development" : "production");
44340
44376
  /**
44341
44377
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
44342
44378
  * (set to `0` to disable tracing) and falls back to
@@ -48118,7 +48154,7 @@ const AGENT_GUIDANCE_LINES = [
48118
48154
  "Investigate deeply where relevant: race conditions, security-sensitive flows, state propagation, multi-file refactors, and downstream dependency chains.",
48119
48155
  "Ignore pure style preferences, theoretical issues without real impact, missing features, and unrelated pre-existing code.",
48120
48156
  "Start with high-confidence fixes that preserve behavior. Leave low-confidence or product-dependent changes as notes.",
48121
- "Run `npx react-doctor@latest --verbose --diff` before and after changes, plus relevant tests after each focused batch.",
48157
+ "Run `npx react-doctor@latest --verbose --scope changed` before and after changes, plus relevant tests after each focused batch.",
48122
48158
  "When available, spawn subagents or isolated worktrees for independent rule families, then review and merge only the best safe fixes.",
48123
48159
  "Split unrelated, broad, or behavior-changing work into separate PRs/branches instead of one large cleanup.",
48124
48160
  "For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.",
@@ -50760,22 +50796,22 @@ const buildAgentHookScript = () => [
50760
50796
  "",
50761
50797
  "run_react_doctor() {",
50762
50798
  " if [ -x ./node_modules/.bin/react-doctor ]; then",
50763
- " ./node_modules/.bin/react-doctor --verbose --diff --blocking warning --no-score",
50799
+ " ./node_modules/.bin/react-doctor --verbose --scope changed --blocking warning --no-score",
50764
50800
  " return",
50765
50801
  " fi",
50766
50802
  "",
50767
50803
  " if command -v react-doctor >/dev/null 2>&1; then",
50768
- " react-doctor --verbose --diff --blocking warning --no-score",
50804
+ " react-doctor --verbose --scope changed --blocking warning --no-score",
50769
50805
  " return",
50770
50806
  " fi",
50771
50807
  "",
50772
50808
  " if command -v pnpm >/dev/null 2>&1; then",
50773
- " pnpm dlx react-doctor@latest --verbose --diff --blocking warning --no-score",
50809
+ " pnpm dlx react-doctor@latest --verbose --scope changed --blocking warning --no-score",
50774
50810
  " return",
50775
50811
  " fi",
50776
50812
  "",
50777
50813
  " if command -v npx >/dev/null 2>&1; then",
50778
- " npx --yes react-doctor@latest --verbose --diff --blocking warning --no-score",
50814
+ " npx --yes react-doctor@latest --verbose --scope changed --blocking warning --no-score",
50779
50815
  " return",
50780
50816
  " fi",
50781
50817
  "",
@@ -53872,7 +53908,7 @@ ${highlighter.dim("Examples:")}
53872
53908
  ${formatExampleLines([
53873
53909
  ["react-doctor", "scan the current project"],
53874
53910
  ["react-doctor ./apps/web", "scan a specific directory"],
53875
- ["react-doctor --diff main", "scan only files changed vs. main"],
53911
+ ["react-doctor --scope changed --base main", "scan only new issues vs. main"],
53876
53912
  ["react-doctor --project modules/a,modules/b", "score each module separately (names or paths)"],
53877
53913
  ["react-doctor --staged", "scan staged files (pre-commit hook)"],
53878
53914
  ["react-doctor --category Security", "show only one diagnostic category"],
@@ -53948,4 +53984,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
53948
53984
  export {};
53949
53985
 
53950
53986
  //# sourceMappingURL=cli.js.map
53951
- //# debugId=fd7e91f8-8458-50a2-99d9-a1ea8f8bfaff
53987
+ //# debugId=fc691f8f-4aa6-5f96-8491-8e4e53439952
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]="3b9fafb6-a936-52a2-96df-a26760fdafc1")}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";
@@ -36472,10 +36472,15 @@ const buildCapabilities = (project) => {
36472
36472
  }
36473
36473
  if (project.tailwindVersion !== null) {
36474
36474
  capabilities.add("tailwind");
36475
- if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
36475
+ const tailwind = parseTailwindMajorMinor(project.tailwindVersion);
36476
+ if (isTailwindAtLeast(tailwind, {
36476
36477
  major: 3,
36477
36478
  minor: 4
36478
36479
  })) capabilities.add("tailwind:3.4");
36480
+ if (tailwind !== null && isTailwindAtLeast(tailwind, {
36481
+ major: 4,
36482
+ minor: 0
36483
+ })) capabilities.add("tailwind:4");
36479
36484
  }
36480
36485
  if (project.zodVersion !== null) {
36481
36486
  capabilities.add("zod");
@@ -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);
@@ -37033,11 +37037,10 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve
37033
37037
  });
37034
37038
  });
37035
37039
  const checkDeadCode = async (options) => {
37036
- const { userConfig } = options;
37037
37040
  const rootDirectory = toCanonicalPath(options.rootDirectory);
37038
37041
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37039
37042
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37040
- const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
37043
+ const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37041
37044
  const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37042
37045
  rootDirectory,
37043
37046
  entryPatterns,
@@ -38086,8 +38089,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38086
38089
  }
38087
38090
  return enabled;
38088
38091
  };
38089
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
38090
- const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38092
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
38093
+ const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38091
38094
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38092
38095
  const jsPlugins = [];
38093
38096
  if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
@@ -38784,9 +38787,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
38784
38787
  try {
38785
38788
  parsed = JSON.parse(sanitizedStdout);
38786
38789
  } catch {
38787
- throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38790
+ throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38788
38791
  }
38789
- if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38792
+ if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38790
38793
  const minifiedFileCache = /* @__PURE__ */ new Map();
38791
38794
  const isMinifiedDiagnosticFile = (filename) => {
38792
38795
  const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
@@ -39077,6 +39080,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
39077
39080
  NFS.closeSync(fileHandle);
39078
39081
  }
39079
39082
  };
39083
+ const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
39084
+ /**
39085
+ * Detects an oxlint config-load crash caused by the optional
39086
+ * `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
39087
+ * builds the partial-failure note for it; returns `null` when the failure
39088
+ * was anything else.
39089
+ *
39090
+ * oxlint prints a framed error to stdout (not stderr) and exits non-zero
39091
+ * when a `jsPlugins` entry can't be imported; that non-JSON stdout
39092
+ * surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
39093
+ * config load on it, leaving the plugin in would drop every curated
39094
+ * react-doctor diagnostic too — so the caller retries with the plugin
39095
+ * stripped (issue #833). Both markers sit at the start of oxlint's
39096
+ * message, so they survive the `preview` slice even for deep pnpm paths.
39097
+ */
39098
+ const reactHooksJsPluginDropNote = (error) => {
39099
+ if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
39100
+ const { preview } = error.reason;
39101
+ if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
39102
+ const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
39103
+ return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
39104
+ };
39080
39105
  /**
39081
39106
  * The oxlint runner. Composed of three pieces in `runners/oxlint/`:
39082
39107
  *
@@ -39104,15 +39129,16 @@ const runOxlint = async (options) => {
39104
39129
  const pluginPath = resolvePluginPath();
39105
39130
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
39106
39131
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
39107
- const buildConfig = (extendsForThisAttempt) => createOxlintConfig({
39132
+ const buildConfig = (overrides) => createOxlintConfig({
39108
39133
  pluginPath,
39109
39134
  project,
39110
39135
  customRulesOnly,
39111
- extendsPaths: extendsForThisAttempt,
39136
+ extendsPaths: overrides.extendsPaths,
39112
39137
  ignoredTags,
39113
39138
  serverAuthFunctionNames,
39114
39139
  severityControls,
39115
- userPlugins
39140
+ userPlugins,
39141
+ disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
39116
39142
  });
39117
39143
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
39118
39144
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -39148,12 +39174,22 @@ const runOxlint = async (options) => {
39148
39174
  outputMaxBytes,
39149
39175
  concurrency: options.concurrency
39150
39176
  });
39151
- writeOxlintConfig(configPath, buildConfig(extendsPaths));
39177
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
39152
39178
  try {
39153
39179
  return await runBatches();
39154
39180
  } catch (error) {
39181
+ const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
39182
+ if (reactHooksJsDropNote !== null) {
39183
+ writeOxlintConfig(configPath, buildConfig({
39184
+ extendsPaths,
39185
+ disableReactHooksJsPlugin: true
39186
+ }));
39187
+ const diagnostics = await runBatches();
39188
+ onPartialFailure?.(reactHooksJsDropNote);
39189
+ return diagnostics;
39190
+ }
39155
39191
  if (extendsPaths.length === 0) throw error;
39156
- writeOxlintConfig(configPath, buildConfig([]));
39192
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
39157
39193
  return await runBatches();
39158
39194
  }
39159
39195
  } finally {
@@ -40573,4 +40609,4 @@ const toJsonReport = (result, options) => buildJsonReport({
40573
40609
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
40574
40610
 
40575
40611
  //# sourceMappingURL=index.js.map
40576
- //# debugId=f55d58c3-c6f3-598b-bd99-d0abe85fd35e
40612
+ //# debugId=3b9fafb6-a936-52a2-96df-a26760fdafc1
package/dist/lsp.js CHANGED
@@ -36458,10 +36458,15 @@ const buildCapabilities = (project) => {
36458
36458
  }
36459
36459
  if (project.tailwindVersion !== null) {
36460
36460
  capabilities.add("tailwind");
36461
- if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
36461
+ const tailwind = parseTailwindMajorMinor(project.tailwindVersion);
36462
+ if (isTailwindAtLeast(tailwind, {
36462
36463
  major: 3,
36463
36464
  minor: 4
36464
36465
  })) capabilities.add("tailwind:3.4");
36466
+ if (tailwind !== null && isTailwindAtLeast(tailwind, {
36467
+ major: 4,
36468
+ minor: 0
36469
+ })) capabilities.add("tailwind:4");
36465
36470
  }
36466
36471
  if (project.zodVersion !== null) {
36467
36472
  capabilities.add("zod");
@@ -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);
@@ -37019,11 +37023,10 @@ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve
37019
37023
  });
37020
37024
  });
37021
37025
  const checkDeadCode = async (options) => {
37022
- const { userConfig } = options;
37023
37026
  const rootDirectory = toCanonicalPath(options.rootDirectory);
37024
37027
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37025
37028
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37026
- const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
37029
+ const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37027
37030
  const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37028
37031
  rootDirectory,
37029
37032
  entryPatterns,
@@ -38072,8 +38075,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
38072
38075
  }
38073
38076
  return enabled;
38074
38077
  };
38075
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [] }) => {
38076
- const reactHooksJsPlugin = resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38078
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
38079
+ const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
38077
38080
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
38078
38081
  const jsPlugins = [];
38079
38082
  if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
@@ -38770,9 +38773,9 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
38770
38773
  try {
38771
38774
  parsed = JSON.parse(sanitizedStdout);
38772
38775
  } catch {
38773
- throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38776
+ throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38774
38777
  }
38775
- if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 200) }) });
38778
+ if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
38776
38779
  const minifiedFileCache = /* @__PURE__ */ new Map();
38777
38780
  const isMinifiedDiagnosticFile = (filename) => {
38778
38781
  const absolutePath = Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
@@ -39063,6 +39066,28 @@ const writeOxlintConfig = (configPath, configToWrite) => {
39063
39066
  NFS.closeSync(fileHandle);
39064
39067
  }
39065
39068
  };
39069
+ const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
39070
+ /**
39071
+ * Detects an oxlint config-load crash caused by the optional
39072
+ * `react-hooks-js` (eslint-plugin-react-hooks) React Compiler plugin and
39073
+ * builds the partial-failure note for it; returns `null` when the failure
39074
+ * was anything else.
39075
+ *
39076
+ * oxlint prints a framed error to stdout (not stderr) and exits non-zero
39077
+ * when a `jsPlugins` entry can't be imported; that non-JSON stdout
39078
+ * surfaces as `OxlintOutputUnparseable`. Because oxlint fails the WHOLE
39079
+ * config load on it, leaving the plugin in would drop every curated
39080
+ * react-doctor diagnostic too — so the caller retries with the plugin
39081
+ * stripped (issue #833). Both markers sit at the start of oxlint's
39082
+ * message, so they survive the `preview` slice even for deep pnpm paths.
39083
+ */
39084
+ const reactHooksJsPluginDropNote = (error) => {
39085
+ if (!(error instanceof ReactDoctorError) || error.reason._tag !== "OxlintOutputUnparseable") return null;
39086
+ const { preview } = error.reason;
39087
+ if (!preview.includes("Failed to load JS plugin") || !preview.includes("eslint-plugin-react-hooks")) return null;
39088
+ const underlyingReason = preview.match(/Error:[^\n]*/)?.[0]?.trim();
39089
+ return `${REACT_HOOKS_JS_DROP_PREFIX}${underlyingReason ? `: ${underlyingReason}` : ""}. Other rules ran normally.`;
39090
+ };
39066
39091
  /**
39067
39092
  * The oxlint runner. Composed of three pieces in `runners/oxlint/`:
39068
39093
  *
@@ -39090,15 +39115,16 @@ const runOxlint = async (options) => {
39090
39115
  const pluginPath = resolvePluginPath();
39091
39116
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
39092
39117
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
39093
- const buildConfig = (extendsForThisAttempt) => createOxlintConfig({
39118
+ const buildConfig = (overrides) => createOxlintConfig({
39094
39119
  pluginPath,
39095
39120
  project,
39096
39121
  customRulesOnly,
39097
- extendsPaths: extendsForThisAttempt,
39122
+ extendsPaths: overrides.extendsPaths,
39098
39123
  ignoredTags,
39099
39124
  serverAuthFunctionNames,
39100
39125
  severityControls,
39101
- userPlugins
39126
+ userPlugins,
39127
+ disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
39102
39128
  });
39103
39129
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
39104
39130
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -39134,12 +39160,22 @@ const runOxlint = async (options) => {
39134
39160
  outputMaxBytes,
39135
39161
  concurrency: options.concurrency
39136
39162
  });
39137
- writeOxlintConfig(configPath, buildConfig(extendsPaths));
39163
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
39138
39164
  try {
39139
39165
  return await runBatches();
39140
39166
  } catch (error) {
39167
+ const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
39168
+ if (reactHooksJsDropNote !== null) {
39169
+ writeOxlintConfig(configPath, buildConfig({
39170
+ extendsPaths,
39171
+ disableReactHooksJsPlugin: true
39172
+ }));
39173
+ const diagnostics = await runBatches();
39174
+ onPartialFailure?.(reactHooksJsDropNote);
39175
+ return diagnostics;
39176
+ }
39141
39177
  if (extendsPaths.length === 0) throw error;
39142
- writeOxlintConfig(configPath, buildConfig([]));
39178
+ writeOxlintConfig(configPath, buildConfig({ extendsPaths: [] }));
39143
39179
  return await runBatches();
39144
39180
  }
39145
39181
  } finally {
@@ -42358,5 +42394,5 @@ const startLanguageServer = () => {
42358
42394
  };
42359
42395
  //#endregion
42360
42396
  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
42397
+ !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]="90262051-3b30-5292-8128-962466deb4ac")}catch(e){}}();
42398
+ //# debugId=90262051-3b30-5292-8128-962466deb4ac
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.6-dev.15238de",
3
+ "version": "0.5.6-dev.424d8f9",
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.15238de"
67
+ "oxlint-plugin-react-doctor": "0.5.6-dev.424d8f9"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/babel__code-frame": "^7.27.0",