react-doctor 0.5.8-dev.bd0f465 → 0.5.8-dev.c16e8ea

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]="fabe3e34-b40f-514e-a5af-fa68a36365ae")}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]="810b7c16-b0df-5f5f-a918-99f6ed04dd67")}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";
@@ -38187,6 +38187,7 @@ const collectStringSet = (values) => {
38187
38187
  if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
38188
38188
  return new Set(values.filter((value) => typeof value === "string"));
38189
38189
  };
38190
+ const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
38190
38191
  /**
38191
38192
  * Pre-compiles every stateful filter and returns a single
38192
38193
  * `apply(diagnostic)` closure that runs (in order):
@@ -38220,8 +38221,10 @@ const buildDiagnosticPipeline = (input) => {
38220
38221
  const compiledOverrides = compileIgnoreOverrides(userConfig);
38221
38222
  const textComponentNames = collectStringSet(userConfig?.textComponents);
38222
38223
  const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
38224
+ const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
38223
38225
  const hasTextComponents = textComponentNames.size > 0;
38224
38226
  const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
38227
+ const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
38225
38228
  const fileLinesCache = /* @__PURE__ */ new Map();
38226
38229
  const fileContextCache = /* @__PURE__ */ new Map();
38227
38230
  const libraryFileCache = /* @__PURE__ */ new Map();
@@ -38273,6 +38276,12 @@ const buildDiagnosticPipeline = (input) => {
38273
38276
  if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
38274
38277
  return false;
38275
38278
  };
38279
+ const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
38280
+ if (!hasRuntimeGlobals) return false;
38281
+ if (diagnostic.rule !== "jsx-no-undef") return false;
38282
+ const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
38283
+ return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
38284
+ };
38276
38285
  return { apply: (diagnostic) => {
38277
38286
  if (shouldAutoSuppress(diagnostic)) return null;
38278
38287
  let current = diagnostic;
@@ -38297,6 +38306,7 @@ const buildDiagnosticPipeline = (input) => {
38297
38306
  if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
38298
38307
  if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
38299
38308
  if (isRnRawTextSuppressedByConfig(current)) return null;
38309
+ if (isJsxNoUndefSuppressedByConfig(current)) return null;
38300
38310
  }
38301
38311
  if (respectInlineDisables && current.line > 0) {
38302
38312
  const lines = getFileLines(current.filePath);
@@ -38774,6 +38784,7 @@ const STRING_ARRAY_FIELD_NAMES = [
38774
38784
  "projects",
38775
38785
  "textComponents",
38776
38786
  "rawTextWrapperComponents",
38787
+ "runtimeGlobals",
38777
38788
  "serverAuthFunctionNames"
38778
38789
  ];
38779
38790
  const SURFACE_CONTROL_FIELD_NAMES = [
@@ -40522,6 +40533,11 @@ const toCanonicalPath = (filePath) => {
40522
40533
  };
40523
40534
  const DEAD_CODE_PLUGIN = "deslop";
40524
40535
  const DEAD_CODE_CATEGORY = "Maintainability";
40536
+ const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
40537
+ "react-doctor",
40538
+ "eslint-plugin-react-doctor",
40539
+ "oxlint-plugin-react-doctor"
40540
+ ]);
40525
40541
  const DEAD_CODE_WORKER_SCRIPT = `
40526
40542
  const inputChunks = [];
40527
40543
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -40827,6 +40843,7 @@ const checkDeadCode = async (options) => {
40827
40843
  });
40828
40844
  }
40829
40845
  for (const unusedDependency of result.unusedDependencies) {
40846
+ if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
40830
40847
  const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
40831
40848
  diagnostics.push({
40832
40849
  filePath: "package.json",
@@ -42560,11 +42577,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
42560
42577
  const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
42561
42578
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
42562
42579
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
42580
+ const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
42563
42581
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
42564
- const buildReactCompilerMessage = (reasonSummary) => {
42582
+ const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
42565
42583
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
42566
- if (!normalizedSummary) return REACT_COMPILER_GENERIC_MESSAGE;
42567
- return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${REACT_COMPILER_ACTION}`;
42584
+ if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
42585
+ return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
42568
42586
  };
42569
42587
  const PLUGIN_CATEGORY_MAP = {
42570
42588
  react: "Bugs",
@@ -42613,7 +42631,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
42613
42631
  const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
42614
42632
  const reasonDetail = reasonDetailLines.join("\n").trim();
42615
42633
  return {
42616
- message: buildReactCompilerMessage(reasonSummary.trim()),
42634
+ message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
42617
42635
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
42618
42636
  };
42619
42637
  }
@@ -45103,7 +45121,7 @@ const makeNoopConsole = () => ({
45103
45121
  });
45104
45122
  //#endregion
45105
45123
  //#region src/cli/utils/version.ts
45106
- const VERSION = "0.5.8-dev.bd0f465";
45124
+ const VERSION = "0.5.8-dev.c16e8ea";
45107
45125
  //#endregion
45108
45126
  //#region src/cli/utils/json-mode.ts
45109
45127
  let context = null;
@@ -45472,13 +45490,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
45472
45490
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
45473
45491
  * standard `SENTRY_RELEASE` override.
45474
45492
  */
45475
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.bd0f465`;
45493
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.c16e8ea`;
45476
45494
  /**
45477
45495
  * Deployment environment shown in Sentry's environment filter. Defaults to
45478
45496
  * `production` for tagged releases and `development` for dev/unbuilt versions,
45479
45497
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
45480
45498
  */
45481
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.bd0f465") ? "development" : "production");
45499
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.c16e8ea") ? "development" : "production");
45482
45500
  /**
45483
45501
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
45484
45502
  * (set to `0` to disable tracing) and falls back to
@@ -50562,6 +50580,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
50562
50580
  `if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
50563
50581
  "rm -f \"$react_doctor_output\";",
50564
50582
  "else",
50583
+ "cat \"$react_doctor_output\" >&2;",
50565
50584
  "rm -f \"$react_doctor_output\";",
50566
50585
  `printf "%s\\n" "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2;`,
50567
50586
  "fi"
@@ -52259,10 +52278,11 @@ const rememberInstallAgents = (agents, options = {}) => writePreference(INSTALL_
52259
52278
  const CLAUDE_AGENT = "claude-code";
52260
52279
  const CURSOR_AGENT = "cursor";
52261
52280
  const CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
52262
- const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.sh";
52263
- const CLAUDE_HOOK_COMMAND = "sh \"$CLAUDE_PROJECT_DIR/.claude/hooks/react-doctor.sh\"";
52281
+ const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.mjs";
52282
+ const CLAUDE_HOOK_COMMAND = "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/react-doctor.mjs\"";
52264
52283
  const CURSOR_HOOKS_RELATIVE_PATH = ".cursor/hooks.json";
52265
- const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.sh";
52284
+ const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.mjs";
52285
+ const CURSOR_HOOK_COMMAND = "node .cursor/hooks/react-doctor.mjs";
52266
52286
  const CURSOR_HOOK_MATCHER = "Write|Edit|MultiEdit|ApplyPatch";
52267
52287
  const CURSOR_HOOKS_SCHEMA_VERSION = 1;
52268
52288
  const isSupportedAgent = (agent) => agent === CLAUDE_AGENT || agent === CURSOR_AGENT;
@@ -52294,7 +52314,6 @@ const writeJsonFileWithDirectoryCheck = (filePath, value) => {
52294
52314
  const writeHookScript = (filePath) => {
52295
52315
  ensureDirectoryExists(Path.dirname(filePath));
52296
52316
  NFS.writeFileSync(filePath, buildAgentHookScript());
52297
- NFS.chmodSync(filePath, 493);
52298
52317
  };
52299
52318
  const hasClaudeHookCommand = (groups) => groups.some((group) => (group.hooks ?? []).some((hook) => hook.command === CLAUDE_HOOK_COMMAND));
52300
52319
  const installClaudeHook = (projectRoot) => {
@@ -52315,7 +52334,7 @@ const installClaudeHook = (projectRoot) => {
52315
52334
  writeHookScript(hookPath);
52316
52335
  return [settingsPath, hookPath];
52317
52336
  };
52318
- const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command === CURSOR_HOOK_RELATIVE_PATH);
52337
+ const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command === CURSOR_HOOK_COMMAND);
52319
52338
  const installCursorHook = (projectRoot) => {
52320
52339
  const configPath = Path.join(projectRoot, CURSOR_HOOKS_RELATIVE_PATH);
52321
52340
  const hookPath = Path.join(projectRoot, CURSOR_HOOK_RELATIVE_PATH);
@@ -52323,7 +52342,7 @@ const installCursorHook = (projectRoot) => {
52323
52342
  const hooks = { ...config.hooks ?? {} };
52324
52343
  const postToolUseHooks = [...hooks.postToolUse ?? []];
52325
52344
  if (!hasCursorHookCommand(postToolUseHooks)) postToolUseHooks.push({
52326
- command: CURSOR_HOOK_RELATIVE_PATH,
52345
+ command: CURSOR_HOOK_COMMAND,
52327
52346
  matcher: CURSOR_HOOK_MATCHER,
52328
52347
  timeout: 120
52329
52348
  });
@@ -52337,102 +52356,111 @@ const installCursorHook = (projectRoot) => {
52337
52356
  return [configPath, hookPath];
52338
52357
  };
52339
52358
  const buildAgentHookScript = () => [
52340
- "#!/bin/sh",
52341
- "set -u",
52359
+ "import { readFileSync, writeFileSync, unlinkSync } from 'node:fs';",
52360
+ "import { tmpdir } from 'node:os';",
52361
+ "import { join, dirname } from 'node:path';",
52362
+ "import { fileURLToPath } from 'node:url';",
52363
+ "import { spawnSync } from 'node:child_process';",
52342
52364
  "",
52343
- "input_file=$(mktemp \"${TMPDIR:-/tmp}/react-doctor-agent-hook.XXXXXX\")",
52344
- "output_file=$(mktemp \"${TMPDIR:-/tmp}/react-doctor-agent-hook-output.XXXXXX\")",
52345
- "trap 'rm -f \"$input_file\" \"$output_file\"' EXIT",
52346
- "cat > \"$input_file\"",
52365
+ "const __filename = fileURLToPath(import.meta.url);",
52366
+ "const __dirname = dirname(__filename);",
52347
52367
  "",
52348
- "script_dir=$(CDPATH= cd \"$(dirname \"$0\")\" && pwd)",
52349
- "project_root=${CLAUDE_PROJECT_DIR:-}",
52350
- "if [ -z \"$project_root\" ]; then",
52351
- " project_root=$(CDPATH= cd \"$script_dir/../..\" && pwd)",
52352
- "fi",
52353
- "if ! cd \"$project_root\"; then",
52354
- " exit 0",
52355
- "fi",
52368
+ "const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
52356
52369
  "",
52357
- "should_scan() {",
52358
- " if ! command -v node >/dev/null 2>&1; then",
52359
- " return 0",
52360
- " fi",
52370
+ "const readStdin = () => {",
52371
+ " try {",
52372
+ " return readFileSync(0, 'utf8');",
52373
+ " } catch {",
52374
+ " return '';",
52375
+ " }",
52376
+ "};",
52361
52377
  "",
52362
- " node - \"$input_file\" <<'NODE'",
52363
- "const fs = require('node:fs');",
52364
- "const inputPath = process.argv[2];",
52365
- "const editToolNames = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
52366
- "try {",
52367
- " const input = JSON.parse(fs.readFileSync(inputPath, 'utf8') || '{}');",
52378
+ "const shouldScan = (input) => {",
52368
52379
  " const eventName = input.hook_event_name || input.eventName || input.event_name;",
52369
52380
  " if (eventName === 'PostToolBatch') {",
52370
52381
  " const toolCalls = Array.isArray(input.tool_calls) ? input.tool_calls : [];",
52371
- " process.exit(toolCalls.some((toolCall) => editToolNames.has(toolCall.tool_name)) ? 0 : 10);",
52382
+ " return toolCalls.some((toolCall) => EDIT_TOOL_NAMES.has(toolCall.tool_name));",
52372
52383
  " }",
52373
52384
  " const toolName = input.tool_name || input.toolName || input.tool;",
52374
- " process.exit(!toolName || editToolNames.has(toolName) ? 0 : 10);",
52375
- "} catch {",
52376
- " process.exit(0);",
52377
- "}",
52378
- "NODE",
52379
- "}",
52385
+ " return !toolName || EDIT_TOOL_NAMES.has(toolName);",
52386
+ "};",
52380
52387
  "",
52381
- "run_react_doctor() {",
52382
- " if [ -x ./node_modules/.bin/react-doctor ]; then",
52383
- " ./node_modules/.bin/react-doctor --verbose --scope changed --blocking warning --no-score",
52384
- " return",
52385
- " fi",
52388
+ "const runReactDoctor = (outputPath) => {",
52389
+ " // Each candidate is a single shell command string (not an args array):",
52390
+ " // `shell: true` is required to run the Windows `.cmd` shims, and an args",
52391
+ " // array with `shell: true` trips Node's DEP0190. A missing command exits",
52392
+ " // 127 via the shell (no ENOENT error), so fall through on that. With no",
52393
+ " // runner found, exit 0 silently — stdout is parsed as the hook's JSON.",
52394
+ " const commands = [",
52395
+ " './node_modules/.bin/react-doctor --verbose --scope changed --blocking warning --no-score',",
52396
+ " 'react-doctor --verbose --scope changed --blocking warning --no-score',",
52397
+ " 'pnpm dlx react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
52398
+ " 'npx --yes react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
52399
+ " ];",
52386
52400
  "",
52387
- " if command -v react-doctor >/dev/null 2>&1; then",
52388
- " react-doctor --verbose --scope changed --blocking warning --no-score",
52389
- " return",
52390
- " fi",
52401
+ " for (const command of commands) {",
52402
+ " const result = spawnSync(command, { encoding: 'utf8', shell: true });",
52403
+ " if (result.error?.code === 'ENOENT' || result.status === 127) continue;",
52404
+ " try {",
52405
+ " writeFileSync(outputPath, (result.stdout || '') + (result.stderr || ''));",
52406
+ " } catch {}",
52407
+ " return result.status;",
52408
+ " }",
52391
52409
  "",
52392
- " if command -v pnpm >/dev/null 2>&1; then",
52393
- " pnpm dlx react-doctor@latest --verbose --scope changed --blocking warning --no-score",
52394
- " return",
52395
- " fi",
52410
+ " return 0;",
52411
+ "};",
52396
52412
  "",
52397
- " if command -v npx >/dev/null 2>&1; then",
52398
- " npx --yes react-doctor@latest --verbose --scope changed --blocking warning --no-score",
52399
- " return",
52400
- " fi",
52413
+ "const cleanup = (...paths) => {",
52414
+ " for (const path of paths) {",
52415
+ " try { unlinkSync(path); } catch {}",
52416
+ " }",
52417
+ "};",
52401
52418
  "",
52402
- " printf '%s\\n' 'react-doctor: command not found; skipping agent hook scan.'",
52403
- " return 0",
52404
- "}",
52419
+ "const main = () => {",
52420
+ " let input;",
52421
+ " try {",
52422
+ " const stdinContent = readStdin();",
52423
+ " input = JSON.parse(stdinContent || '{}');",
52424
+ " } catch {",
52425
+ " input = {};",
52426
+ " }",
52405
52427
  "",
52406
- "if ! should_scan; then",
52407
- " exit 0",
52408
- "fi",
52428
+ " if (!shouldScan(input)) {",
52429
+ " process.exit(0);",
52430
+ " }",
52409
52431
  "",
52410
- "if run_react_doctor > \"$output_file\" 2>&1; then",
52411
- " exit 0",
52412
- "fi",
52432
+ " const projectRoot = process.env.CLAUDE_PROJECT_DIR || join(__dirname, '../..');",
52433
+ " const outputPath = join(tmpdir(), `react-doctor-agent-hook-output-${process.pid}.txt`);",
52413
52434
  "",
52414
- "node - \"$input_file\" \"$output_file\" <<'NODE'",
52415
- "const fs = require('node:fs');",
52416
- "const inputPath = process.argv[2];",
52417
- "const outputPath = process.argv[3];",
52418
- "const readInput = () => {",
52419
52435
  " try {",
52420
- " return JSON.parse(fs.readFileSync(inputPath, 'utf8') || '{}');",
52436
+ " process.chdir(projectRoot);",
52421
52437
  " } catch {",
52422
- " return {};",
52438
+ " process.exit(0);",
52439
+ " }",
52440
+ "",
52441
+ " const scanResult = runReactDoctor(outputPath);",
52442
+ " if (scanResult === 0) {",
52443
+ " cleanup(outputPath);",
52444
+ " process.exit(0);",
52445
+ " }",
52446
+ "",
52447
+ " const scanOutput = readFileSync(outputPath, 'utf8').trim();",
52448
+ " cleanup(outputPath);",
52449
+ "",
52450
+ " if (!scanOutput) {",
52451
+ " process.exit(0);",
52452
+ " }",
52453
+ "",
52454
+ " const message = `React Doctor found issues in the changed files. Review this output and fix the regressions before finishing. For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.\\n\\n${scanOutput}`;",
52455
+ "",
52456
+ " if (input.hook_event_name === 'PostToolBatch') {",
52457
+ " console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
52458
+ " } else {",
52459
+ " console.log(JSON.stringify({ additional_context: message }));",
52423
52460
  " }",
52424
52461
  "};",
52425
- "const input = readInput();",
52426
- "const scanOutput = fs.readFileSync(outputPath, 'utf8').trim();",
52427
- "if (!scanOutput) process.exit(0);",
52428
- "const message = `React Doctor found issues in the changed files. Review this output and fix the regressions before finishing. For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.\\n\\n${scanOutput}`;",
52429
- "if (input.hook_event_name === 'PostToolBatch') {",
52430
- " console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
52431
- "} else {",
52432
- " console.log(JSON.stringify({ additional_context: message }));",
52433
- "}",
52434
- "NODE",
52435
- ""
52462
+ "",
52463
+ "main();"
52436
52464
  ].join("\n");
52437
52465
  const installReactDoctorAgentHooks = (options) => {
52438
52466
  const installedAgents = [];
@@ -52493,6 +52521,7 @@ const buildReactDoctorHookBlock = () => [
52493
52521
  "if react_doctor_scan_staged_files > \"$react_doctor_output\" 2>&1; then",
52494
52522
  " rm -f \"$react_doctor_output\"",
52495
52523
  "else",
52524
+ " cat \"$react_doctor_output\" >&2",
52496
52525
  " rm -f \"$react_doctor_output\"",
52497
52526
  ` printf '%s\\n' "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2`,
52498
52527
  "fi",
@@ -55370,6 +55399,14 @@ const applyColorPreference = (argv, env = process.env) => {
55370
55399
  setColorEnabled(enabled);
55371
55400
  };
55372
55401
  //#endregion
55402
+ //#region src/cli/utils/ensure-windows-utf8-console.ts
55403
+ const ensureWindowsUtf8Console = () => {
55404
+ if (process.platform !== "win32" || !process.stdout.isTTY) return;
55405
+ try {
55406
+ execFileSync("chcp.com", ["65001"], { stdio: "ignore" });
55407
+ } catch {}
55408
+ };
55409
+ //#endregion
55373
55410
  //#region src/cli/utils/exit-gracefully.ts
55374
55411
  const exitGracefully = () => {
55375
55412
  try {
@@ -55728,6 +55765,7 @@ if (process.argv.includes("-V") && !strippedArgv.includes("-V")) {
55728
55765
  process.exit(0);
55729
55766
  }
55730
55767
  applyColorPreference(strippedArgv);
55768
+ ensureWindowsUtf8Console();
55731
55769
  const argv = normalizeHelpInvocation(strippedArgv, knownCommands);
55732
55770
  Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => program.parseAsync(argv)).then(() => flushSentry()).catch(async (error) => {
55733
55771
  const isUserError = isExpectedUserError(error);
@@ -55746,4 +55784,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
55746
55784
  export {};
55747
55785
 
55748
55786
  //# sourceMappingURL=cli.js.map
55749
- //# debugId=fabe3e34-b40f-514e-a5af-fa68a36365ae
55787
+ //# debugId=810b7c16-b0df-5f5f-a918-99f6ed04dd67
package/dist/index.d.ts CHANGED
@@ -9271,6 +9271,15 @@ interface ReactDoctorConfig {
9271
9271
  * suppresses regardless of sibling content.
9272
9272
  */
9273
9273
  rawTextWrapperComponents?: string[];
9274
+ /**
9275
+ * Capitalized identifiers that exist at runtime but aren't imported in
9276
+ * the file that uses them — react-live's `<LiveProvider scope={...}>`,
9277
+ * Storybook globals, MDX live blocks, or an ambient `declare global` in a
9278
+ * separate `.d.ts`. The single-file `jsx-no-undef` rule can't see those
9279
+ * bindings and would report `<DatePicker />` as an undefined JSX component.
9280
+ * List the injected names here and `jsx-no-undef` treats them as known.
9281
+ */
9282
+ runtimeGlobals?: string[];
9274
9283
  /**
9275
9284
  * Project-level allowlist of function names that the
9276
9285
  * `server-auth-actions` rule treats as an auth check at the top of
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]="9807b1bf-1e62-5421-ad35-d1f4129c1538")}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]="1e049c6a-27f7-5c62-8be8-57ce0b987f6f")}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";
@@ -34999,6 +34999,7 @@ const collectStringSet = (values) => {
34999
34999
  if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
35000
35000
  return new Set(values.filter((value) => typeof value === "string"));
35001
35001
  };
35002
+ const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
35002
35003
  /**
35003
35004
  * Pre-compiles every stateful filter and returns a single
35004
35005
  * `apply(diagnostic)` closure that runs (in order):
@@ -35032,8 +35033,10 @@ const buildDiagnosticPipeline = (input) => {
35032
35033
  const compiledOverrides = compileIgnoreOverrides(userConfig);
35033
35034
  const textComponentNames = collectStringSet(userConfig?.textComponents);
35034
35035
  const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
35036
+ const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
35035
35037
  const hasTextComponents = textComponentNames.size > 0;
35036
35038
  const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
35039
+ const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
35037
35040
  const fileLinesCache = /* @__PURE__ */ new Map();
35038
35041
  const fileContextCache = /* @__PURE__ */ new Map();
35039
35042
  const libraryFileCache = /* @__PURE__ */ new Map();
@@ -35085,6 +35088,12 @@ const buildDiagnosticPipeline = (input) => {
35085
35088
  if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
35086
35089
  return false;
35087
35090
  };
35091
+ const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
35092
+ if (!hasRuntimeGlobals) return false;
35093
+ if (diagnostic.rule !== "jsx-no-undef") return false;
35094
+ const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
35095
+ return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
35096
+ };
35088
35097
  return { apply: (diagnostic) => {
35089
35098
  if (shouldAutoSuppress(diagnostic)) return null;
35090
35099
  let current = diagnostic;
@@ -35109,6 +35118,7 @@ const buildDiagnosticPipeline = (input) => {
35109
35118
  if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
35110
35119
  if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
35111
35120
  if (isRnRawTextSuppressedByConfig(current)) return null;
35121
+ if (isJsxNoUndefSuppressedByConfig(current)) return null;
35112
35122
  }
35113
35123
  if (respectInlineDisables && current.line > 0) {
35114
35124
  const lines = getFileLines(current.filePath);
@@ -35586,6 +35596,7 @@ const STRING_ARRAY_FIELD_NAMES = [
35586
35596
  "projects",
35587
35597
  "textComponents",
35588
35598
  "rawTextWrapperComponents",
35599
+ "runtimeGlobals",
35589
35600
  "serverAuthFunctionNames"
35590
35601
  ];
35591
35602
  const SURFACE_CONTROL_FIELD_NAMES = [
@@ -37290,6 +37301,11 @@ const toCanonicalPath = (filePath) => {
37290
37301
  };
37291
37302
  const DEAD_CODE_PLUGIN = "deslop";
37292
37303
  const DEAD_CODE_CATEGORY = "Maintainability";
37304
+ const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
37305
+ "react-doctor",
37306
+ "eslint-plugin-react-doctor",
37307
+ "oxlint-plugin-react-doctor"
37308
+ ]);
37293
37309
  const DEAD_CODE_WORKER_SCRIPT = `
37294
37310
  const inputChunks = [];
37295
37311
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -37595,6 +37611,7 @@ const checkDeadCode = async (options) => {
37595
37611
  });
37596
37612
  }
37597
37613
  for (const unusedDependency of result.unusedDependencies) {
37614
+ if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
37598
37615
  const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
37599
37616
  diagnostics.push({
37600
37617
  filePath: "package.json",
@@ -39328,11 +39345,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
39328
39345
  const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
39329
39346
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
39330
39347
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
39348
+ const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
39331
39349
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
39332
- const buildReactCompilerMessage = (reasonSummary) => {
39350
+ const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
39333
39351
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
39334
- if (!normalizedSummary) return REACT_COMPILER_GENERIC_MESSAGE;
39335
- return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${REACT_COMPILER_ACTION}`;
39352
+ if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
39353
+ return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
39336
39354
  };
39337
39355
  const PLUGIN_CATEGORY_MAP = {
39338
39356
  react: "Bugs",
@@ -39381,7 +39399,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
39381
39399
  const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
39382
39400
  const reasonDetail = reasonDetailLines.join("\n").trim();
39383
39401
  return {
39384
- message: buildReactCompilerMessage(reasonSummary.trim()),
39402
+ message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
39385
39403
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
39386
39404
  };
39387
39405
  }
@@ -41645,4 +41663,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41645
41663
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41646
41664
 
41647
41665
  //# sourceMappingURL=index.js.map
41648
- //# debugId=9807b1bf-1e62-5421-ad35-d1f4129c1538
41666
+ //# debugId=1e049c6a-27f7-5c62-8be8-57ce0b987f6f
package/dist/lsp.js CHANGED
@@ -35054,6 +35054,7 @@ const collectStringSet = (values) => {
35054
35054
  if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
35055
35055
  return new Set(values.filter((value) => typeof value === "string"));
35056
35056
  };
35057
+ const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
35057
35058
  /**
35058
35059
  * Pre-compiles every stateful filter and returns a single
35059
35060
  * `apply(diagnostic)` closure that runs (in order):
@@ -35087,8 +35088,10 @@ const buildDiagnosticPipeline = (input) => {
35087
35088
  const compiledOverrides = compileIgnoreOverrides(userConfig);
35088
35089
  const textComponentNames = collectStringSet(userConfig?.textComponents);
35089
35090
  const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
35091
+ const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
35090
35092
  const hasTextComponents = textComponentNames.size > 0;
35091
35093
  const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
35094
+ const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
35092
35095
  const fileLinesCache = /* @__PURE__ */ new Map();
35093
35096
  const fileContextCache = /* @__PURE__ */ new Map();
35094
35097
  const libraryFileCache = /* @__PURE__ */ new Map();
@@ -35140,6 +35143,12 @@ const buildDiagnosticPipeline = (input) => {
35140
35143
  if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
35141
35144
  return false;
35142
35145
  };
35146
+ const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
35147
+ if (!hasRuntimeGlobals) return false;
35148
+ if (diagnostic.rule !== "jsx-no-undef") return false;
35149
+ const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
35150
+ return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
35151
+ };
35143
35152
  return { apply: (diagnostic) => {
35144
35153
  if (shouldAutoSuppress(diagnostic)) return null;
35145
35154
  let current = diagnostic;
@@ -35164,6 +35173,7 @@ const buildDiagnosticPipeline = (input) => {
35164
35173
  if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
35165
35174
  if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
35166
35175
  if (isRnRawTextSuppressedByConfig(current)) return null;
35176
+ if (isJsxNoUndefSuppressedByConfig(current)) return null;
35167
35177
  }
35168
35178
  if (respectInlineDisables && current.line > 0) {
35169
35179
  const lines = getFileLines(current.filePath);
@@ -35619,6 +35629,7 @@ const STRING_ARRAY_FIELD_NAMES = [
35619
35629
  "projects",
35620
35630
  "textComponents",
35621
35631
  "rawTextWrapperComponents",
35632
+ "runtimeGlobals",
35622
35633
  "serverAuthFunctionNames"
35623
35634
  ];
35624
35635
  const SURFACE_CONTROL_FIELD_NAMES = [
@@ -37275,6 +37286,11 @@ const toCanonicalPath = (filePath) => {
37275
37286
  };
37276
37287
  const DEAD_CODE_PLUGIN = "deslop";
37277
37288
  const DEAD_CODE_CATEGORY = "Maintainability";
37289
+ const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
37290
+ "react-doctor",
37291
+ "eslint-plugin-react-doctor",
37292
+ "oxlint-plugin-react-doctor"
37293
+ ]);
37278
37294
  const DEAD_CODE_WORKER_SCRIPT = `
37279
37295
  const inputChunks = [];
37280
37296
  process.stdin.on("data", (chunk) => inputChunks.push(chunk));
@@ -37580,6 +37596,7 @@ const checkDeadCode = async (options) => {
37580
37596
  });
37581
37597
  }
37582
37598
  for (const unusedDependency of result.unusedDependencies) {
37599
+ if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
37583
37600
  const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
37584
37601
  diagnostics.push({
37585
37602
  filePath: "package.json",
@@ -39313,11 +39330,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
39313
39330
  const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
39314
39331
  const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
39315
39332
  const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
39333
+ const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
39316
39334
  const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
39317
- const buildReactCompilerMessage = (reasonSummary) => {
39335
+ const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
39318
39336
  const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
39319
- if (!normalizedSummary) return REACT_COMPILER_GENERIC_MESSAGE;
39320
- return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${REACT_COMPILER_ACTION}`;
39337
+ if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
39338
+ return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
39321
39339
  };
39322
39340
  const PLUGIN_CATEGORY_MAP = {
39323
39341
  react: "Bugs",
@@ -39366,7 +39384,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
39366
39384
  const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
39367
39385
  const reasonDetail = reasonDetailLines.join("\n").trim();
39368
39386
  return {
39369
- message: buildReactCompilerMessage(reasonSummary.trim()),
39387
+ message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
39370
39388
  help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
39371
39389
  };
39372
39390
  }
@@ -43365,5 +43383,5 @@ const startLanguageServer = () => {
43365
43383
  };
43366
43384
  //#endregion
43367
43385
  export { startLanguageServer };
43368
- !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]="c5b6c740-461d-5f65-a993-9e6d35ad9113")}catch(e){}}();
43369
- //# debugId=c5b6c740-461d-5f65-a993-9e6d35ad9113
43386
+ !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]="195b3b8e-1867-5a60-b233-6b100ac6462b")}catch(e){}}();
43387
+ //# debugId=195b3b8e-1867-5a60-b233-6b100ac6462b
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.8-dev.bd0f465",
3
+ "version": "0.5.8-dev.c16e8ea",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -64,8 +64,8 @@
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.8-dev.bd0f465",
68
- "deslop-js": "0.5.8"
67
+ "deslop-js": "0.5.8",
68
+ "oxlint-plugin-react-doctor": "0.5.8-dev.c16e8ea"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/babel__code-frame": "^7.27.0",