react-doctor 0.5.8-dev.ba2af1b → 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 +174 -102
- package/dist/index.d.ts +9 -0
- package/dist/index.js +24 -6
- package/dist/lsp.js +26 -7
- package/package.json +3 -3
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]="
|
|
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
|
|
42567
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
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
|
}
|
|
@@ -45059,7 +45077,8 @@ const METRIC = {
|
|
|
45059
45077
|
lspScanCompleted: "lsp.scan.completed",
|
|
45060
45078
|
lspScanDuration: "lsp.scan.duration",
|
|
45061
45079
|
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
45062
|
-
aiTrainingWarningShown: "ai.training.warning_shown"
|
|
45080
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
45081
|
+
jsonOutUsed: "json.out_used"
|
|
45063
45082
|
};
|
|
45064
45083
|
//#endregion
|
|
45065
45084
|
//#region src/cli/utils/noop-console.ts
|
|
@@ -45102,7 +45121,7 @@ const makeNoopConsole = () => ({
|
|
|
45102
45121
|
});
|
|
45103
45122
|
//#endregion
|
|
45104
45123
|
//#region src/cli/utils/version.ts
|
|
45105
|
-
const VERSION = "0.5.8-dev.
|
|
45124
|
+
const VERSION = "0.5.8-dev.c16e8ea";
|
|
45106
45125
|
//#endregion
|
|
45107
45126
|
//#region src/cli/utils/json-mode.ts
|
|
45108
45127
|
let context = null;
|
|
@@ -45140,12 +45159,13 @@ const installSilentConsole = () => {
|
|
|
45140
45159
|
"trace"
|
|
45141
45160
|
]) target[key] = source[key];
|
|
45142
45161
|
};
|
|
45143
|
-
const enableJsonMode = ({ compact, directory }) => {
|
|
45162
|
+
const enableJsonMode = ({ compact, directory, outputFile }) => {
|
|
45144
45163
|
context = {
|
|
45145
45164
|
compact,
|
|
45146
45165
|
directory,
|
|
45147
45166
|
startTime: performance$1.now(),
|
|
45148
|
-
mode: "full"
|
|
45167
|
+
mode: "full",
|
|
45168
|
+
outputFile: outputFile ?? null
|
|
45149
45169
|
};
|
|
45150
45170
|
installSilentConsole();
|
|
45151
45171
|
};
|
|
@@ -45158,7 +45178,11 @@ const setJsonReportMode = (mode) => {
|
|
|
45158
45178
|
};
|
|
45159
45179
|
const writeJsonReport = (report) => {
|
|
45160
45180
|
const serialized = context?.compact ? JSON.stringify(report) : JSON.stringify(report, null, 2);
|
|
45161
|
-
|
|
45181
|
+
if (context?.outputFile) {
|
|
45182
|
+
const resolvedPath = Path.resolve(context.outputFile);
|
|
45183
|
+
NFS.mkdirSync(Path.dirname(resolvedPath), { recursive: true });
|
|
45184
|
+
NFS.writeFileSync(resolvedPath, `${serialized}\n`);
|
|
45185
|
+
} else process.stdout.write(`${serialized}\n`);
|
|
45162
45186
|
};
|
|
45163
45187
|
const writeJsonErrorReport = (error, sentryEventId) => {
|
|
45164
45188
|
if (!context) return;
|
|
@@ -45466,13 +45490,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
45466
45490
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
45467
45491
|
* standard `SENTRY_RELEASE` override.
|
|
45468
45492
|
*/
|
|
45469
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.
|
|
45493
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.c16e8ea`;
|
|
45470
45494
|
/**
|
|
45471
45495
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
45472
45496
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
45473
45497
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
45474
45498
|
*/
|
|
45475
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.
|
|
45499
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.c16e8ea") ? "development" : "production");
|
|
45476
45500
|
/**
|
|
45477
45501
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
45478
45502
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -50556,6 +50580,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
|
50556
50580
|
`if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
|
|
50557
50581
|
"rm -f \"$react_doctor_output\";",
|
|
50558
50582
|
"else",
|
|
50583
|
+
"cat \"$react_doctor_output\" >&2;",
|
|
50559
50584
|
"rm -f \"$react_doctor_output\";",
|
|
50560
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;`,
|
|
50561
50586
|
"fi"
|
|
@@ -52253,10 +52278,11 @@ const rememberInstallAgents = (agents, options = {}) => writePreference(INSTALL_
|
|
|
52253
52278
|
const CLAUDE_AGENT = "claude-code";
|
|
52254
52279
|
const CURSOR_AGENT = "cursor";
|
|
52255
52280
|
const CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
|
|
52256
|
-
const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.
|
|
52257
|
-
const CLAUDE_HOOK_COMMAND = "
|
|
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\"";
|
|
52258
52283
|
const CURSOR_HOOKS_RELATIVE_PATH = ".cursor/hooks.json";
|
|
52259
|
-
const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.
|
|
52284
|
+
const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.mjs";
|
|
52285
|
+
const CURSOR_HOOK_COMMAND = "node .cursor/hooks/react-doctor.mjs";
|
|
52260
52286
|
const CURSOR_HOOK_MATCHER = "Write|Edit|MultiEdit|ApplyPatch";
|
|
52261
52287
|
const CURSOR_HOOKS_SCHEMA_VERSION = 1;
|
|
52262
52288
|
const isSupportedAgent = (agent) => agent === CLAUDE_AGENT || agent === CURSOR_AGENT;
|
|
@@ -52288,7 +52314,6 @@ const writeJsonFileWithDirectoryCheck = (filePath, value) => {
|
|
|
52288
52314
|
const writeHookScript = (filePath) => {
|
|
52289
52315
|
ensureDirectoryExists(Path.dirname(filePath));
|
|
52290
52316
|
NFS.writeFileSync(filePath, buildAgentHookScript());
|
|
52291
|
-
NFS.chmodSync(filePath, 493);
|
|
52292
52317
|
};
|
|
52293
52318
|
const hasClaudeHookCommand = (groups) => groups.some((group) => (group.hooks ?? []).some((hook) => hook.command === CLAUDE_HOOK_COMMAND));
|
|
52294
52319
|
const installClaudeHook = (projectRoot) => {
|
|
@@ -52309,7 +52334,7 @@ const installClaudeHook = (projectRoot) => {
|
|
|
52309
52334
|
writeHookScript(hookPath);
|
|
52310
52335
|
return [settingsPath, hookPath];
|
|
52311
52336
|
};
|
|
52312
|
-
const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command ===
|
|
52337
|
+
const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command === CURSOR_HOOK_COMMAND);
|
|
52313
52338
|
const installCursorHook = (projectRoot) => {
|
|
52314
52339
|
const configPath = Path.join(projectRoot, CURSOR_HOOKS_RELATIVE_PATH);
|
|
52315
52340
|
const hookPath = Path.join(projectRoot, CURSOR_HOOK_RELATIVE_PATH);
|
|
@@ -52317,7 +52342,7 @@ const installCursorHook = (projectRoot) => {
|
|
|
52317
52342
|
const hooks = { ...config.hooks ?? {} };
|
|
52318
52343
|
const postToolUseHooks = [...hooks.postToolUse ?? []];
|
|
52319
52344
|
if (!hasCursorHookCommand(postToolUseHooks)) postToolUseHooks.push({
|
|
52320
|
-
command:
|
|
52345
|
+
command: CURSOR_HOOK_COMMAND,
|
|
52321
52346
|
matcher: CURSOR_HOOK_MATCHER,
|
|
52322
52347
|
timeout: 120
|
|
52323
52348
|
});
|
|
@@ -52331,102 +52356,111 @@ const installCursorHook = (projectRoot) => {
|
|
|
52331
52356
|
return [configPath, hookPath];
|
|
52332
52357
|
};
|
|
52333
52358
|
const buildAgentHookScript = () => [
|
|
52334
|
-
"
|
|
52335
|
-
"
|
|
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';",
|
|
52336
52364
|
"",
|
|
52337
|
-
"
|
|
52338
|
-
"
|
|
52339
|
-
"trap 'rm -f \"$input_file\" \"$output_file\"' EXIT",
|
|
52340
|
-
"cat > \"$input_file\"",
|
|
52365
|
+
"const __filename = fileURLToPath(import.meta.url);",
|
|
52366
|
+
"const __dirname = dirname(__filename);",
|
|
52341
52367
|
"",
|
|
52342
|
-
"
|
|
52343
|
-
"project_root=${CLAUDE_PROJECT_DIR:-}",
|
|
52344
|
-
"if [ -z \"$project_root\" ]; then",
|
|
52345
|
-
" project_root=$(CDPATH= cd \"$script_dir/../..\" && pwd)",
|
|
52346
|
-
"fi",
|
|
52347
|
-
"if ! cd \"$project_root\"; then",
|
|
52348
|
-
" exit 0",
|
|
52349
|
-
"fi",
|
|
52368
|
+
"const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
|
|
52350
52369
|
"",
|
|
52351
|
-
"
|
|
52352
|
-
"
|
|
52353
|
-
" return 0",
|
|
52354
|
-
"
|
|
52370
|
+
"const readStdin = () => {",
|
|
52371
|
+
" try {",
|
|
52372
|
+
" return readFileSync(0, 'utf8');",
|
|
52373
|
+
" } catch {",
|
|
52374
|
+
" return '';",
|
|
52375
|
+
" }",
|
|
52376
|
+
"};",
|
|
52355
52377
|
"",
|
|
52356
|
-
"
|
|
52357
|
-
"const fs = require('node:fs');",
|
|
52358
|
-
"const inputPath = process.argv[2];",
|
|
52359
|
-
"const editToolNames = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
|
|
52360
|
-
"try {",
|
|
52361
|
-
" const input = JSON.parse(fs.readFileSync(inputPath, 'utf8') || '{}');",
|
|
52378
|
+
"const shouldScan = (input) => {",
|
|
52362
52379
|
" const eventName = input.hook_event_name || input.eventName || input.event_name;",
|
|
52363
52380
|
" if (eventName === 'PostToolBatch') {",
|
|
52364
52381
|
" const toolCalls = Array.isArray(input.tool_calls) ? input.tool_calls : [];",
|
|
52365
|
-
"
|
|
52382
|
+
" return toolCalls.some((toolCall) => EDIT_TOOL_NAMES.has(toolCall.tool_name));",
|
|
52366
52383
|
" }",
|
|
52367
52384
|
" const toolName = input.tool_name || input.toolName || input.tool;",
|
|
52368
|
-
"
|
|
52369
|
-
"}
|
|
52370
|
-
" process.exit(0);",
|
|
52371
|
-
"}",
|
|
52372
|
-
"NODE",
|
|
52373
|
-
"}",
|
|
52385
|
+
" return !toolName || EDIT_TOOL_NAMES.has(toolName);",
|
|
52386
|
+
"};",
|
|
52374
52387
|
"",
|
|
52375
|
-
"
|
|
52376
|
-
"
|
|
52377
|
-
"
|
|
52378
|
-
"
|
|
52379
|
-
"
|
|
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
|
+
" ];",
|
|
52380
52400
|
"",
|
|
52381
|
-
"
|
|
52382
|
-
"
|
|
52383
|
-
"
|
|
52384
|
-
"
|
|
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
|
+
" }",
|
|
52385
52409
|
"",
|
|
52386
|
-
"
|
|
52387
|
-
"
|
|
52388
|
-
" return",
|
|
52389
|
-
" fi",
|
|
52410
|
+
" return 0;",
|
|
52411
|
+
"};",
|
|
52390
52412
|
"",
|
|
52391
|
-
"
|
|
52392
|
-
"
|
|
52393
|
-
"
|
|
52394
|
-
"
|
|
52413
|
+
"const cleanup = (...paths) => {",
|
|
52414
|
+
" for (const path of paths) {",
|
|
52415
|
+
" try { unlinkSync(path); } catch {}",
|
|
52416
|
+
" }",
|
|
52417
|
+
"};",
|
|
52395
52418
|
"",
|
|
52396
|
-
"
|
|
52397
|
-
"
|
|
52398
|
-
"
|
|
52419
|
+
"const main = () => {",
|
|
52420
|
+
" let input;",
|
|
52421
|
+
" try {",
|
|
52422
|
+
" const stdinContent = readStdin();",
|
|
52423
|
+
" input = JSON.parse(stdinContent || '{}');",
|
|
52424
|
+
" } catch {",
|
|
52425
|
+
" input = {};",
|
|
52426
|
+
" }",
|
|
52399
52427
|
"",
|
|
52400
|
-
"if !
|
|
52401
|
-
"
|
|
52402
|
-
"
|
|
52428
|
+
" if (!shouldScan(input)) {",
|
|
52429
|
+
" process.exit(0);",
|
|
52430
|
+
" }",
|
|
52403
52431
|
"",
|
|
52404
|
-
"
|
|
52405
|
-
"
|
|
52406
|
-
"fi",
|
|
52432
|
+
" const projectRoot = process.env.CLAUDE_PROJECT_DIR || join(__dirname, '../..');",
|
|
52433
|
+
" const outputPath = join(tmpdir(), `react-doctor-agent-hook-output-${process.pid}.txt`);",
|
|
52407
52434
|
"",
|
|
52408
|
-
"node - \"$input_file\" \"$output_file\" <<'NODE'",
|
|
52409
|
-
"const fs = require('node:fs');",
|
|
52410
|
-
"const inputPath = process.argv[2];",
|
|
52411
|
-
"const outputPath = process.argv[3];",
|
|
52412
|
-
"const readInput = () => {",
|
|
52413
52435
|
" try {",
|
|
52414
|
-
"
|
|
52436
|
+
" process.chdir(projectRoot);",
|
|
52415
52437
|
" } catch {",
|
|
52416
|
-
"
|
|
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 }));",
|
|
52417
52460
|
" }",
|
|
52418
52461
|
"};",
|
|
52419
|
-
"
|
|
52420
|
-
"
|
|
52421
|
-
"if (!scanOutput) process.exit(0);",
|
|
52422
|
-
"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}`;",
|
|
52423
|
-
"if (input.hook_event_name === 'PostToolBatch') {",
|
|
52424
|
-
" console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
|
|
52425
|
-
"} else {",
|
|
52426
|
-
" console.log(JSON.stringify({ additional_context: message }));",
|
|
52427
|
-
"}",
|
|
52428
|
-
"NODE",
|
|
52429
|
-
""
|
|
52462
|
+
"",
|
|
52463
|
+
"main();"
|
|
52430
52464
|
].join("\n");
|
|
52431
52465
|
const installReactDoctorAgentHooks = (options) => {
|
|
52432
52466
|
const installedAgents = [];
|
|
@@ -52487,6 +52521,7 @@ const buildReactDoctorHookBlock = () => [
|
|
|
52487
52521
|
"if react_doctor_scan_staged_files > \"$react_doctor_output\" 2>&1; then",
|
|
52488
52522
|
" rm -f \"$react_doctor_output\"",
|
|
52489
52523
|
"else",
|
|
52524
|
+
" cat \"$react_doctor_output\" >&2",
|
|
52490
52525
|
" rm -f \"$react_doctor_output\"",
|
|
52491
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`,
|
|
52492
52527
|
"fi",
|
|
@@ -53252,6 +53287,26 @@ const CLI_AGENT_AUTO_FLAGS = {
|
|
|
53252
53287
|
codex: ["--yolo"],
|
|
53253
53288
|
cursor: ["--force"]
|
|
53254
53289
|
};
|
|
53290
|
+
const resolveCursorBundledNode = () => {
|
|
53291
|
+
if (!isWindows) return null;
|
|
53292
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
53293
|
+
if (!localAppData) return null;
|
|
53294
|
+
const cursorAgentRoot = Path.join(localAppData, "cursor-agent", "versions");
|
|
53295
|
+
try {
|
|
53296
|
+
const versions = NFS.readdirSync(cursorAgentRoot);
|
|
53297
|
+
if (versions.length === 0) return null;
|
|
53298
|
+
const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
|
|
53299
|
+
for (const version of sortedVersions) try {
|
|
53300
|
+
const bundledNodePath = Path.join(cursorAgentRoot, version, "node.exe");
|
|
53301
|
+
const entryScriptPath = Path.join(cursorAgentRoot, version, "index.js");
|
|
53302
|
+
if (NFS.statSync(bundledNodePath).isFile() && NFS.statSync(entryScriptPath).isFile()) return {
|
|
53303
|
+
command: bundledNodePath,
|
|
53304
|
+
args: [entryScriptPath]
|
|
53305
|
+
};
|
|
53306
|
+
} catch {}
|
|
53307
|
+
} catch {}
|
|
53308
|
+
return null;
|
|
53309
|
+
};
|
|
53255
53310
|
const resolveWindowsCmdEntryScript = (command) => {
|
|
53256
53311
|
if (!isWindows) return null;
|
|
53257
53312
|
const pathDirectories = (process.env.PATH ?? "").split(Path.delimiter).filter(Boolean);
|
|
@@ -53271,11 +53326,10 @@ const resolveWindowsCmdEntryScript = (command) => {
|
|
|
53271
53326
|
}
|
|
53272
53327
|
return null;
|
|
53273
53328
|
};
|
|
53274
|
-
const spawnAgent = (command, args, cwd
|
|
53329
|
+
const spawnAgent = (command, args, cwd) => new Promise((resolve, reject) => {
|
|
53275
53330
|
const child = spawn(command, [...args], {
|
|
53276
53331
|
cwd,
|
|
53277
|
-
stdio: "inherit"
|
|
53278
|
-
shell
|
|
53332
|
+
stdio: "inherit"
|
|
53279
53333
|
});
|
|
53280
53334
|
child.on("error", reject);
|
|
53281
53335
|
child.on("close", (code) => resolve(code ?? 0));
|
|
@@ -53284,6 +53338,10 @@ const launchCliAgent = async (agentId, prompt, cwd) => {
|
|
|
53284
53338
|
const binary = CLI_AGENT_BINARIES[agentId];
|
|
53285
53339
|
const agentArgs = [...CLI_AGENT_AUTO_FLAGS[agentId], prompt];
|
|
53286
53340
|
if (isWindows) {
|
|
53341
|
+
if (agentId === "cursor") {
|
|
53342
|
+
const cursorResolution = resolveCursorBundledNode();
|
|
53343
|
+
if (cursorResolution) return spawnAgent(cursorResolution.command, [...cursorResolution.args, ...agentArgs], cwd);
|
|
53344
|
+
}
|
|
53287
53345
|
const entryScript = resolveWindowsCmdEntryScript(binary);
|
|
53288
53346
|
if (entryScript) return spawnAgent(process.execPath, [entryScript, ...agentArgs], cwd);
|
|
53289
53347
|
}
|
|
@@ -54396,10 +54454,14 @@ const inspectAction = async (directory, flags) => {
|
|
|
54396
54454
|
const isQuiet = isScoreOnly || isJsonMode;
|
|
54397
54455
|
const requestedDirectory = Path.resolve(directory);
|
|
54398
54456
|
const startTime = performance$1.now();
|
|
54399
|
-
if (isJsonMode)
|
|
54400
|
-
|
|
54401
|
-
|
|
54402
|
-
|
|
54457
|
+
if (isJsonMode) {
|
|
54458
|
+
enableJsonMode({
|
|
54459
|
+
compact: Boolean(flags.jsonCompact),
|
|
54460
|
+
directory: requestedDirectory,
|
|
54461
|
+
outputFile: flags.jsonOut
|
|
54462
|
+
});
|
|
54463
|
+
if (flags.jsonOut) recordCount(METRIC.jsonOutUsed, 1);
|
|
54464
|
+
}
|
|
54403
54465
|
recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
|
|
54404
54466
|
try {
|
|
54405
54467
|
validateModeFlags(flags);
|
|
@@ -55337,6 +55399,14 @@ const applyColorPreference = (argv, env = process.env) => {
|
|
|
55337
55399
|
setColorEnabled(enabled);
|
|
55338
55400
|
};
|
|
55339
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
|
|
55340
55410
|
//#region src/cli/utils/exit-gracefully.ts
|
|
55341
55411
|
const exitGracefully = () => {
|
|
55342
55412
|
try {
|
|
@@ -55479,6 +55549,7 @@ const ROOT_FLAG_SPEC = {
|
|
|
55479
55549
|
"--category",
|
|
55480
55550
|
"--changed-files-from",
|
|
55481
55551
|
"--blocking",
|
|
55552
|
+
"--json-out",
|
|
55482
55553
|
"--output-dir",
|
|
55483
55554
|
"--fail-on",
|
|
55484
55555
|
"--project",
|
|
@@ -55669,7 +55740,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
55669
55740
|
${highlighter.info(CANONICAL_GITHUB_URL)}
|
|
55670
55741
|
`;
|
|
55671
55742
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
55672
|
-
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
55743
|
+
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
55673
55744
|
program.action(inspectAction);
|
|
55674
55745
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
55675
55746
|
program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
|
|
@@ -55694,6 +55765,7 @@ if (process.argv.includes("-V") && !strippedArgv.includes("-V")) {
|
|
|
55694
55765
|
process.exit(0);
|
|
55695
55766
|
}
|
|
55696
55767
|
applyColorPreference(strippedArgv);
|
|
55768
|
+
ensureWindowsUtf8Console();
|
|
55697
55769
|
const argv = normalizeHelpInvocation(strippedArgv, knownCommands);
|
|
55698
55770
|
Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => program.parseAsync(argv)).then(() => flushSentry()).catch(async (error) => {
|
|
55699
55771
|
const isUserError = isExpectedUserError(error);
|
|
@@ -55712,4 +55784,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
55712
55784
|
export {};
|
|
55713
55785
|
|
|
55714
55786
|
//# sourceMappingURL=cli.js.map
|
|
55715
|
-
//# debugId=
|
|
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]="
|
|
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
|
|
39335
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
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=
|
|
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
|
|
39320
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
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
|
}
|
|
@@ -43047,7 +43065,8 @@ const METRIC = {
|
|
|
43047
43065
|
lspScanCompleted: "lsp.scan.completed",
|
|
43048
43066
|
lspScanDuration: "lsp.scan.duration",
|
|
43049
43067
|
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
43050
|
-
aiTrainingWarningShown: "ai.training.warning_shown"
|
|
43068
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
43069
|
+
jsonOutUsed: "json.out_used"
|
|
43051
43070
|
};
|
|
43052
43071
|
//#endregion
|
|
43053
43072
|
//#region src/cli/utils/to-category-key.ts
|
|
@@ -43364,5 +43383,5 @@ const startLanguageServer = () => {
|
|
|
43364
43383
|
};
|
|
43365
43384
|
//#endregion
|
|
43366
43385
|
export { startLanguageServer };
|
|
43367
|
-
!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]="
|
|
43368
|
-
//# debugId=
|
|
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.
|
|
3
|
+
"version": "0.5.8-dev.c16e8ea",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"magicast": "^0.5.3",
|
|
61
61
|
"oxlint": ">=1.66.0 <1.67.0",
|
|
62
62
|
"prompts": "^2.4.2",
|
|
63
|
-
"typescript": ">=5.0.4 <
|
|
63
|
+
"typescript": ">=5.0.4 <6",
|
|
64
64
|
"vscode-languageserver": "^9.0.1",
|
|
65
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"deslop-js": "0.5.8",
|
|
68
|
-
"oxlint-plugin-react-doctor": "0.5.8-dev.
|
|
68
|
+
"oxlint-plugin-react-doctor": "0.5.8-dev.c16e8ea"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/babel__code-frame": "^7.27.0",
|