react-doctor 0.5.6-dev.93b796d → 0.5.6-dev.a9d2713
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 +341 -181
- package/dist/index.d.ts +19 -0
- package/dist/index.js +257 -166
- package/dist/lsp.js +269 -178
- package/package.json +2 -2
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]="e4d06258-7975-53ee-88e5-490715798dd2")}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";
|
|
@@ -22358,7 +22358,8 @@ var Diagnostic = class extends Class("Diagnostic")({
|
|
|
22358
22358
|
category: String$1,
|
|
22359
22359
|
fileContext: optional(Literals(["test", "story"])),
|
|
22360
22360
|
suppressionHint: optional(String$1),
|
|
22361
|
-
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation))
|
|
22361
|
+
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
|
|
22362
|
+
fixGroupId: optional(String$1)
|
|
22362
22363
|
}) {};
|
|
22363
22364
|
const JsonReportMode = Literals([
|
|
22364
22365
|
"full",
|
|
@@ -22400,6 +22401,7 @@ var JsonReportProjectEntry = class extends Class("JsonReportProjectEntry")({
|
|
|
22400
22401
|
score: Unknown,
|
|
22401
22402
|
skippedChecks: ArraySchema(String$1),
|
|
22402
22403
|
skippedCheckReasons: optional(Record$1(String$1, String$1)),
|
|
22404
|
+
scannedFileCount: optional(Number$1),
|
|
22403
22405
|
elapsedMilliseconds: Number$1
|
|
22404
22406
|
}) {};
|
|
22405
22407
|
/**
|
|
@@ -35893,6 +35895,7 @@ const isLargeMinifiedFile = (absolutePath) => {
|
|
|
35893
35895
|
if (sizeBytes < 2e4) return false;
|
|
35894
35896
|
return isMinifiedSource(absolutePath);
|
|
35895
35897
|
};
|
|
35898
|
+
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
35896
35899
|
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
35897
35900
|
"EACCES",
|
|
35898
35901
|
"EPERM",
|
|
@@ -35902,11 +35905,7 @@ const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
|
35902
35905
|
"ELOOP",
|
|
35903
35906
|
"ENAMETOOLONG"
|
|
35904
35907
|
]);
|
|
35905
|
-
const isIgnorableReaddirError = (error) =>
|
|
35906
|
-
if (typeof error !== "object" || error === null) return false;
|
|
35907
|
-
const errorCode = error.code;
|
|
35908
|
-
return typeof errorCode === "string" && IGNORABLE_READDIR_ERROR_CODES.has(errorCode);
|
|
35909
|
-
};
|
|
35908
|
+
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
35910
35909
|
const readDirectoryEntries = (directoryPath) => {
|
|
35911
35910
|
try {
|
|
35912
35911
|
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
@@ -35953,7 +35952,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
|
|
|
35953
35952
|
return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
35954
35953
|
} catch (error) {
|
|
35955
35954
|
if (error instanceof SyntaxError) return {};
|
|
35956
|
-
if (error
|
|
35955
|
+
if (isErrnoException(error)) {
|
|
35957
35956
|
const { code } = error;
|
|
35958
35957
|
if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
|
|
35959
35958
|
}
|
|
@@ -36678,17 +36677,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
36678
36677
|
return false;
|
|
36679
36678
|
};
|
|
36680
36679
|
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
36681
|
-
const
|
|
36682
|
-
const spec = packageJson.dependencies?.
|
|
36680
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
36681
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
36683
36682
|
return typeof spec === "string" ? spec : null;
|
|
36684
36683
|
};
|
|
36685
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson,
|
|
36684
|
+
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
36686
36685
|
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
36687
|
-
const
|
|
36688
|
-
const spec = packageJson.dependencies?.["@shopify/flash-list"] ?? packageJson.devDependencies?.["@shopify/flash-list"] ?? packageJson.peerDependencies?.["@shopify/flash-list"] ?? packageJson.optionalDependencies?.["@shopify/flash-list"];
|
|
36689
|
-
return typeof spec === "string" ? spec : null;
|
|
36690
|
-
};
|
|
36691
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getShopifyFlashListDependencySpec);
|
|
36686
|
+
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
36692
36687
|
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
36693
36688
|
if (version === null || !isCatalogReference(version)) return version;
|
|
36694
36689
|
const catalogName = extractCatalogName(version);
|
|
@@ -36700,11 +36695,7 @@ const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson,
|
|
|
36700
36695
|
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
36701
36696
|
return resolveCatalogVersion(readPackageJson$1(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
36702
36697
|
};
|
|
36703
|
-
const
|
|
36704
|
-
const spec = packageJson.dependencies?.next ?? packageJson.devDependencies?.next ?? packageJson.peerDependencies?.next ?? packageJson.optionalDependencies?.next;
|
|
36705
|
-
return typeof spec === "string" ? spec : null;
|
|
36706
|
-
};
|
|
36707
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, getNextjsDependencySpec);
|
|
36698
|
+
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
36708
36699
|
const getPreactVersion = (packageJson) => {
|
|
36709
36700
|
return {
|
|
36710
36701
|
...packageJson.peerDependencies,
|
|
@@ -36870,6 +36861,13 @@ const APP_ONLY_RULE_KEYS = new Set([
|
|
|
36870
36861
|
]);
|
|
36871
36862
|
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
36872
36863
|
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
36864
|
+
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
36865
|
+
"react-doctor/no-derived-state",
|
|
36866
|
+
"react-doctor/no-derived-state-effect",
|
|
36867
|
+
"react-doctor/no-derived-useState",
|
|
36868
|
+
"react-doctor/no-adjust-state-on-prop-change",
|
|
36869
|
+
"react-doctor/no-reset-all-state-on-prop-change"
|
|
36870
|
+
]);
|
|
36873
36871
|
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
36874
36872
|
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
36875
36873
|
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
@@ -37319,6 +37317,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
37319
37317
|
if (detected.major !== required.major) return detected.major > required.major;
|
|
37320
37318
|
return detected.minor >= required.minor;
|
|
37321
37319
|
};
|
|
37320
|
+
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
37322
37321
|
var InvalidGlobPatternError = class extends Error {
|
|
37323
37322
|
pattern;
|
|
37324
37323
|
reason;
|
|
@@ -37347,7 +37346,7 @@ const compileGlobPattern = (rawPattern) => {
|
|
|
37347
37346
|
try {
|
|
37348
37347
|
return import_picomatch.default.makeRe(normalizeGlobPattern(rawPattern), PICOMATCH_OPTIONS);
|
|
37349
37348
|
} catch (caughtError) {
|
|
37350
|
-
throw new InvalidGlobPatternError(rawPattern,
|
|
37349
|
+
throw new InvalidGlobPatternError(rawPattern, messageFromUnknown(caughtError));
|
|
37351
37350
|
}
|
|
37352
37351
|
};
|
|
37353
37352
|
const compileGlobPatternsLenient = (patterns, onInvalid) => {
|
|
@@ -37443,115 +37442,6 @@ const buildRuleSeverityControls = (config) => {
|
|
|
37443
37442
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
37444
37443
|
};
|
|
37445
37444
|
};
|
|
37446
|
-
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
37447
|
-
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
37448
|
-
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
37449
|
-
let stringDelimiter = null;
|
|
37450
|
-
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
37451
|
-
const character = line[charIndex];
|
|
37452
|
-
if (stringDelimiter !== null) {
|
|
37453
|
-
if (character === "\\") {
|
|
37454
|
-
charIndex++;
|
|
37455
|
-
continue;
|
|
37456
|
-
}
|
|
37457
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
37458
|
-
continue;
|
|
37459
|
-
}
|
|
37460
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
37461
|
-
stringDelimiter = character;
|
|
37462
|
-
continue;
|
|
37463
|
-
}
|
|
37464
|
-
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
37465
|
-
}
|
|
37466
|
-
return false;
|
|
37467
|
-
};
|
|
37468
|
-
const findOpenerTagOnLine = (line) => {
|
|
37469
|
-
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
37470
|
-
if (match.index === void 0) continue;
|
|
37471
|
-
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
37472
|
-
}
|
|
37473
|
-
return null;
|
|
37474
|
-
};
|
|
37475
|
-
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
37476
|
-
const openerLine = lines[openerLineIndex];
|
|
37477
|
-
if (openerLine === void 0) return null;
|
|
37478
|
-
const opener = findOpenerTagOnLine(openerLine);
|
|
37479
|
-
if (!opener) return null;
|
|
37480
|
-
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
37481
|
-
let braceDepth = 0;
|
|
37482
|
-
let innerAngleDepth = 0;
|
|
37483
|
-
let stringDelimiter = null;
|
|
37484
|
-
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
37485
|
-
const currentLine = lines[lineIndex];
|
|
37486
|
-
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
37487
|
-
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
37488
|
-
const character = currentLine[charIndex];
|
|
37489
|
-
if (stringDelimiter !== null) {
|
|
37490
|
-
if (character === "\\") {
|
|
37491
|
-
charIndex++;
|
|
37492
|
-
continue;
|
|
37493
|
-
}
|
|
37494
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
37495
|
-
continue;
|
|
37496
|
-
}
|
|
37497
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
37498
|
-
stringDelimiter = character;
|
|
37499
|
-
continue;
|
|
37500
|
-
}
|
|
37501
|
-
if (character === "{") {
|
|
37502
|
-
braceDepth++;
|
|
37503
|
-
continue;
|
|
37504
|
-
}
|
|
37505
|
-
if (character === "}") {
|
|
37506
|
-
braceDepth--;
|
|
37507
|
-
continue;
|
|
37508
|
-
}
|
|
37509
|
-
if (braceDepth !== 0) continue;
|
|
37510
|
-
if (character === "<") {
|
|
37511
|
-
const followCharacter = currentLine[charIndex + 1];
|
|
37512
|
-
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
37513
|
-
continue;
|
|
37514
|
-
}
|
|
37515
|
-
if (character !== ">") continue;
|
|
37516
|
-
const previousCharacter = currentLine[charIndex - 1];
|
|
37517
|
-
const nextCharacter = currentLine[charIndex + 1];
|
|
37518
|
-
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
37519
|
-
if (innerAngleDepth > 0) {
|
|
37520
|
-
innerAngleDepth--;
|
|
37521
|
-
continue;
|
|
37522
|
-
}
|
|
37523
|
-
return lineIndex;
|
|
37524
|
-
}
|
|
37525
|
-
}
|
|
37526
|
-
return null;
|
|
37527
|
-
};
|
|
37528
|
-
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
37529
|
-
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
37530
|
-
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
37531
|
-
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
37532
|
-
}
|
|
37533
|
-
return null;
|
|
37534
|
-
};
|
|
37535
|
-
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
37536
|
-
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
37537
|
-
const collected = [];
|
|
37538
|
-
let isStillInChain = true;
|
|
37539
|
-
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
37540
|
-
const candidateLine = lines[candidateIndex];
|
|
37541
|
-
if (candidateLine === void 0) break;
|
|
37542
|
-
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
37543
|
-
if (match) {
|
|
37544
|
-
collected.push({
|
|
37545
|
-
commentLineIndex: candidateIndex,
|
|
37546
|
-
ruleList: match[1],
|
|
37547
|
-
isInChain: isStillInChain
|
|
37548
|
-
});
|
|
37549
|
-
continue;
|
|
37550
|
-
}
|
|
37551
|
-
isStillInChain = false;
|
|
37552
|
-
}
|
|
37553
|
-
return collected;
|
|
37554
|
-
};
|
|
37555
37445
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
37556
37446
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
37557
37447
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -37676,7 +37566,13 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
|
|
|
37676
37566
|
}
|
|
37677
37567
|
const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
|
|
37678
37568
|
const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
|
|
37679
|
-
const
|
|
37569
|
+
const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
|
|
37570
|
+
const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
|
|
37571
|
+
const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
|
|
37572
|
+
const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
|
|
37573
|
+
if (canonicalCandidate === canonicalTarget) return true;
|
|
37574
|
+
return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
|
|
37575
|
+
};
|
|
37680
37576
|
const getEquivalentRuleKeys = (ruleKey) => {
|
|
37681
37577
|
const nativeRuleKey = canonicalizeRuleKey(ruleKey);
|
|
37682
37578
|
return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
|
|
@@ -37686,12 +37582,182 @@ const stripDescriptionTail = (ruleList) => {
|
|
|
37686
37582
|
if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
|
|
37687
37583
|
return ruleList.slice(0, descriptionMatch.index);
|
|
37688
37584
|
};
|
|
37689
|
-
const
|
|
37585
|
+
const tokenizeRuleList = (ruleList) => {
|
|
37690
37586
|
const trimmed = ruleList?.trim();
|
|
37691
|
-
if (!trimmed) return
|
|
37587
|
+
if (!trimmed) return [];
|
|
37692
37588
|
const ruleSection = stripDescriptionTail(trimmed).trim();
|
|
37693
|
-
if (!ruleSection) return
|
|
37694
|
-
return ruleSection.split(/[,\s]+/).
|
|
37589
|
+
if (!ruleSection) return [];
|
|
37590
|
+
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
37591
|
+
};
|
|
37592
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
37593
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
37594
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
37595
|
+
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — change it to \`${ruleId}\`.`;
|
|
37596
|
+
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
37597
|
+
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
37598
|
+
const candidates = [{
|
|
37599
|
+
line: lines[diagnosticLineIndex],
|
|
37600
|
+
requiredScope: "line"
|
|
37601
|
+
}, {
|
|
37602
|
+
line: lines[diagnosticLineIndex - 1],
|
|
37603
|
+
requiredScope: "next-line"
|
|
37604
|
+
}];
|
|
37605
|
+
for (const { line, requiredScope } of candidates) {
|
|
37606
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
37607
|
+
if (!match) continue;
|
|
37608
|
+
const [, tool, scope, ruleList] = match;
|
|
37609
|
+
if (scope !== requiredScope) continue;
|
|
37610
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
37611
|
+
if (tokens.includes(ruleId)) continue;
|
|
37612
|
+
for (const token of tokens) if (tokenMisnamesRule(token, ruleId)) return buildHint(tool, token, ruleId);
|
|
37613
|
+
}
|
|
37614
|
+
return null;
|
|
37615
|
+
};
|
|
37616
|
+
const detectBlockNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
37617
|
+
let openMisname = null;
|
|
37618
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
37619
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
37620
|
+
const line = lines[lineIndex];
|
|
37621
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
37622
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
37623
|
+
if (disableMatch) {
|
|
37624
|
+
const [, tool, ruleList] = disableMatch;
|
|
37625
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
37626
|
+
if (tokens.includes(ruleId)) openMisname = null;
|
|
37627
|
+
else {
|
|
37628
|
+
const misnamed = tokens.find((token) => tokenMisnamesRule(token, ruleId));
|
|
37629
|
+
if (misnamed) openMisname = {
|
|
37630
|
+
tool,
|
|
37631
|
+
token: misnamed
|
|
37632
|
+
};
|
|
37633
|
+
}
|
|
37634
|
+
continue;
|
|
37635
|
+
}
|
|
37636
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
37637
|
+
if (enableMatch) {
|
|
37638
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
37639
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => isSameRuleKey(rule, ruleId))) openMisname = null;
|
|
37640
|
+
}
|
|
37641
|
+
}
|
|
37642
|
+
return openMisname ? buildHint(openMisname.tool, openMisname.token, ruleId) : null;
|
|
37643
|
+
};
|
|
37644
|
+
const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
37645
|
+
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
37646
|
+
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
37647
|
+
};
|
|
37648
|
+
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
37649
|
+
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
37650
|
+
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
37651
|
+
let stringDelimiter = null;
|
|
37652
|
+
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
37653
|
+
const character = line[charIndex];
|
|
37654
|
+
if (stringDelimiter !== null) {
|
|
37655
|
+
if (character === "\\") {
|
|
37656
|
+
charIndex++;
|
|
37657
|
+
continue;
|
|
37658
|
+
}
|
|
37659
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
37660
|
+
continue;
|
|
37661
|
+
}
|
|
37662
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
37663
|
+
stringDelimiter = character;
|
|
37664
|
+
continue;
|
|
37665
|
+
}
|
|
37666
|
+
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
37667
|
+
}
|
|
37668
|
+
return false;
|
|
37669
|
+
};
|
|
37670
|
+
const findOpenerTagOnLine = (line) => {
|
|
37671
|
+
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
37672
|
+
if (match.index === void 0) continue;
|
|
37673
|
+
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
37674
|
+
}
|
|
37675
|
+
return null;
|
|
37676
|
+
};
|
|
37677
|
+
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
37678
|
+
const openerLine = lines[openerLineIndex];
|
|
37679
|
+
if (openerLine === void 0) return null;
|
|
37680
|
+
const opener = findOpenerTagOnLine(openerLine);
|
|
37681
|
+
if (!opener) return null;
|
|
37682
|
+
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
37683
|
+
let braceDepth = 0;
|
|
37684
|
+
let innerAngleDepth = 0;
|
|
37685
|
+
let stringDelimiter = null;
|
|
37686
|
+
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
37687
|
+
const currentLine = lines[lineIndex];
|
|
37688
|
+
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
37689
|
+
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
37690
|
+
const character = currentLine[charIndex];
|
|
37691
|
+
if (stringDelimiter !== null) {
|
|
37692
|
+
if (character === "\\") {
|
|
37693
|
+
charIndex++;
|
|
37694
|
+
continue;
|
|
37695
|
+
}
|
|
37696
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
37697
|
+
continue;
|
|
37698
|
+
}
|
|
37699
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
37700
|
+
stringDelimiter = character;
|
|
37701
|
+
continue;
|
|
37702
|
+
}
|
|
37703
|
+
if (character === "{") {
|
|
37704
|
+
braceDepth++;
|
|
37705
|
+
continue;
|
|
37706
|
+
}
|
|
37707
|
+
if (character === "}") {
|
|
37708
|
+
braceDepth--;
|
|
37709
|
+
continue;
|
|
37710
|
+
}
|
|
37711
|
+
if (braceDepth !== 0) continue;
|
|
37712
|
+
if (character === "<") {
|
|
37713
|
+
const followCharacter = currentLine[charIndex + 1];
|
|
37714
|
+
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
37715
|
+
continue;
|
|
37716
|
+
}
|
|
37717
|
+
if (character !== ">") continue;
|
|
37718
|
+
const previousCharacter = currentLine[charIndex - 1];
|
|
37719
|
+
const nextCharacter = currentLine[charIndex + 1];
|
|
37720
|
+
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
37721
|
+
if (innerAngleDepth > 0) {
|
|
37722
|
+
innerAngleDepth--;
|
|
37723
|
+
continue;
|
|
37724
|
+
}
|
|
37725
|
+
return lineIndex;
|
|
37726
|
+
}
|
|
37727
|
+
}
|
|
37728
|
+
return null;
|
|
37729
|
+
};
|
|
37730
|
+
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
37731
|
+
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
37732
|
+
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
37733
|
+
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
37734
|
+
}
|
|
37735
|
+
return null;
|
|
37736
|
+
};
|
|
37737
|
+
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
37738
|
+
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
37739
|
+
const collected = [];
|
|
37740
|
+
let isStillInChain = true;
|
|
37741
|
+
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
37742
|
+
const candidateLine = lines[candidateIndex];
|
|
37743
|
+
if (candidateLine === void 0) break;
|
|
37744
|
+
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
37745
|
+
if (match) {
|
|
37746
|
+
collected.push({
|
|
37747
|
+
commentLineIndex: candidateIndex,
|
|
37748
|
+
ruleList: match[1],
|
|
37749
|
+
isInChain: isStillInChain
|
|
37750
|
+
});
|
|
37751
|
+
continue;
|
|
37752
|
+
}
|
|
37753
|
+
isStillInChain = false;
|
|
37754
|
+
}
|
|
37755
|
+
return collected;
|
|
37756
|
+
};
|
|
37757
|
+
const isRuleListedInComment = (ruleList, ruleId) => {
|
|
37758
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
37759
|
+
if (tokens.length === 0) return true;
|
|
37760
|
+
return tokens.some((token) => isSameRuleKey(token, ruleId));
|
|
37695
37761
|
};
|
|
37696
37762
|
const DISABLE_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
37697
37763
|
const formatLineGap = (gapLineCount) => `${gapLineCount} line${gapLineCount === 1 ? "" : "s"}`;
|
|
@@ -37735,7 +37801,7 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
37735
37801
|
};
|
|
37736
37802
|
return {
|
|
37737
37803
|
isSuppressed: false,
|
|
37738
|
-
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId)
|
|
37804
|
+
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId) ?? detectForeignDisableNearMiss(lines, diagnosticLineIndex, ruleId)
|
|
37739
37805
|
};
|
|
37740
37806
|
};
|
|
37741
37807
|
/**
|
|
@@ -38525,7 +38591,6 @@ const PACKAGE_JSON_FILENAME = "package.json";
|
|
|
38525
38591
|
const PACKAGE_JSON_CONFIG_KEY$1 = "reactDoctor";
|
|
38526
38592
|
const LEGACY_CONFIG_FILENAME = "react-doctor.config.json";
|
|
38527
38593
|
const jiti = createJiti(import.meta.url);
|
|
38528
|
-
const formatError = (error) => error instanceof Error ? error.message : String(error);
|
|
38529
38594
|
const importDefaultExport = async (jitiInstance, filePath) => {
|
|
38530
38595
|
const imported = await jitiInstance.import(filePath);
|
|
38531
38596
|
return imported?.default ?? imported;
|
|
@@ -38557,7 +38622,7 @@ const loadModuleConfig = async (filePath) => {
|
|
|
38557
38622
|
try {
|
|
38558
38623
|
return await importDefaultExport(aliasJiti, filePath);
|
|
38559
38624
|
} catch (retryError) {
|
|
38560
|
-
throw new Error(`${
|
|
38625
|
+
throw new Error(`${messageFromUnknown(error)} (retry with ${SELF_PACKAGE_IMPORT_SPECIFIER} aliased to the running react-doctor package also failed: ${messageFromUnknown(retryError)})`, { cause: retryError });
|
|
38561
38626
|
}
|
|
38562
38627
|
}
|
|
38563
38628
|
};
|
|
@@ -38606,7 +38671,7 @@ const loadLegacyConfig = (directory) => {
|
|
|
38606
38671
|
}
|
|
38607
38672
|
warn(`${LEGACY_CONFIG_FILENAME} must contain an object, ignoring.`);
|
|
38608
38673
|
} catch (error) {
|
|
38609
|
-
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${
|
|
38674
|
+
warn(`Failed to load ${LEGACY_CONFIG_FILENAME}: ${messageFromUnknown(error)}`);
|
|
38610
38675
|
}
|
|
38611
38676
|
return {
|
|
38612
38677
|
status: "invalid",
|
|
@@ -38633,7 +38698,7 @@ const loadConfigFromDirectory = async (directory) => {
|
|
|
38633
38698
|
warn(`${CONFIG_BASENAME}.${extension} must export an object, ignoring.`);
|
|
38634
38699
|
sawBrokenConfigFile = true;
|
|
38635
38700
|
} catch (error) {
|
|
38636
|
-
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${
|
|
38701
|
+
warn(`Failed to load ${CONFIG_BASENAME}.${extension}: ${messageFromUnknown(error)}`);
|
|
38637
38702
|
sawBrokenConfigFile = true;
|
|
38638
38703
|
}
|
|
38639
38704
|
}
|
|
@@ -38763,6 +38828,29 @@ const resolveScanTarget = async (requestedDirectory, options = {}) => {
|
|
|
38763
38828
|
didRedirectViaRootDir: redirectedDirectory !== null
|
|
38764
38829
|
};
|
|
38765
38830
|
};
|
|
38831
|
+
const buildFixGroupId = (diagnostic) => createHash("sha1").update(JSON.stringify([
|
|
38832
|
+
diagnostic.filePath,
|
|
38833
|
+
`${diagnostic.plugin}/${diagnostic.rule}`,
|
|
38834
|
+
diagnostic.message
|
|
38835
|
+
])).digest("hex").slice(0, 16);
|
|
38836
|
+
const isGroupableRule = (diagnostic) => ROOT_CAUSE_GROUPABLE_RULE_KEYS.has(`${diagnostic.plugin}/${diagnostic.rule}`);
|
|
38837
|
+
const assignFixGroups = (diagnostics) => {
|
|
38838
|
+
const siteCountByGroupId = /* @__PURE__ */ new Map();
|
|
38839
|
+
for (const diagnostic of diagnostics) {
|
|
38840
|
+
if (!isGroupableRule(diagnostic)) continue;
|
|
38841
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
38842
|
+
siteCountByGroupId.set(groupId, (siteCountByGroupId.get(groupId) ?? 0) + 1);
|
|
38843
|
+
}
|
|
38844
|
+
return diagnostics.map((diagnostic) => {
|
|
38845
|
+
if (!isGroupableRule(diagnostic)) return diagnostic;
|
|
38846
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
38847
|
+
if ((siteCountByGroupId.get(groupId) ?? 0) < 2) return diagnostic;
|
|
38848
|
+
return {
|
|
38849
|
+
...diagnostic,
|
|
38850
|
+
fixGroupId: groupId
|
|
38851
|
+
};
|
|
38852
|
+
});
|
|
38853
|
+
};
|
|
38766
38854
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
38767
38855
|
const buildExpoCheckContext = (rootDirectory, expoVersion) => {
|
|
38768
38856
|
const packageJson = readPackageJson$1(Path.join(rootDirectory, "package.json"));
|
|
@@ -39908,7 +39996,7 @@ const readIgnoreFile = (filePath) => {
|
|
|
39908
39996
|
try {
|
|
39909
39997
|
content = NFS.readFileSync(filePath, "utf-8");
|
|
39910
39998
|
} catch (error) {
|
|
39911
|
-
const errnoCode = error
|
|
39999
|
+
const errnoCode = isErrnoException(error) ? error.code : void 0;
|
|
39912
40000
|
if (errnoCode && errnoCode !== "ENOENT") runSync(warn$1(`Could not read ignore file ${filePath}: ${errnoCode}`));
|
|
39913
40001
|
return [];
|
|
39914
40002
|
}
|
|
@@ -40464,15 +40552,13 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
40464
40552
|
})()) }));
|
|
40465
40553
|
static layerOf = (diagnostics) => succeed$3(DeadCode, DeadCode.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40466
40554
|
};
|
|
40467
|
-
const createNodeReadFileLinesSync = (rootDirectory) => {
|
|
40468
|
-
|
|
40469
|
-
|
|
40470
|
-
|
|
40471
|
-
|
|
40472
|
-
|
|
40473
|
-
|
|
40474
|
-
}
|
|
40475
|
-
};
|
|
40555
|
+
const createNodeReadFileLinesSync = (rootDirectory) => (filePath) => {
|
|
40556
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
|
|
40557
|
+
try {
|
|
40558
|
+
return NFS.readFileSync(absolutePath, "utf-8").split("\n");
|
|
40559
|
+
} catch {
|
|
40560
|
+
return null;
|
|
40561
|
+
}
|
|
40476
40562
|
};
|
|
40477
40563
|
var Files = class Files extends Service()("react-doctor/Files") {
|
|
40478
40564
|
static layerNode = succeed$3(Files, Files.of({
|
|
@@ -40683,7 +40769,10 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40683
40769
|
directory: input.directory,
|
|
40684
40770
|
cause
|
|
40685
40771
|
}) });
|
|
40686
|
-
})
|
|
40772
|
+
}), withSpan("git.exec", { attributes: {
|
|
40773
|
+
"git.command": input.command,
|
|
40774
|
+
"git.subcommand": input.args[0] ?? ""
|
|
40775
|
+
} }));
|
|
40687
40776
|
const runGit = (directory, args) => runCommand({
|
|
40688
40777
|
command: "git",
|
|
40689
40778
|
args,
|
|
@@ -40711,7 +40800,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40711
40800
|
]);
|
|
40712
40801
|
if (candidates.status !== 0) return null;
|
|
40713
40802
|
return trimOrNull(candidates.stdout.split("\n")[0] ?? "");
|
|
40714
|
-
});
|
|
40803
|
+
}).pipe(withSpan("Git.defaultBranch"));
|
|
40715
40804
|
const branchExists = (directory, branch) => runGit(directory, [
|
|
40716
40805
|
"rev-parse",
|
|
40717
40806
|
"--verify",
|
|
@@ -40758,7 +40847,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40758
40847
|
const result = resultOption.value;
|
|
40759
40848
|
if (result.status !== 0) return null;
|
|
40760
40849
|
return parseGithubViewerPermission(result.stdout);
|
|
40761
|
-
}).pipe(catch_$1(() => succeed$2(null)));
|
|
40850
|
+
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.githubViewerPermission"));
|
|
40762
40851
|
/**
|
|
40763
40852
|
* Resolves a `--diff A..B` / `A...B` commit range into a changed-file
|
|
40764
40853
|
* selection. Each endpoint is validated with `isSafeGitRevision`
|
|
@@ -40872,7 +40961,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40872
40961
|
changedFiles: splitNullSeparated(diff.stdout),
|
|
40873
40962
|
isCurrentChanges: false
|
|
40874
40963
|
};
|
|
40875
|
-
}),
|
|
40964
|
+
}).pipe(withSpan("Git.diffSelection")),
|
|
40876
40965
|
stagedFilePaths: (directory) => runGit(directory, [
|
|
40877
40966
|
"diff",
|
|
40878
40967
|
"--cached",
|
|
@@ -40914,7 +41003,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40914
41003
|
status: result.status,
|
|
40915
41004
|
stdout: result.stdout
|
|
40916
41005
|
};
|
|
40917
|
-
}),
|
|
41006
|
+
}).pipe(withSpan("Git.grep")),
|
|
40918
41007
|
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
40919
41008
|
if (files.length === 0) return [];
|
|
40920
41009
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
@@ -40930,7 +41019,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
40930
41019
|
]);
|
|
40931
41020
|
if (result.status !== 0) return null;
|
|
40932
41021
|
return parseChangedLineRanges(result.stdout);
|
|
40933
|
-
})
|
|
41022
|
+
}).pipe(withSpan("Git.changedLineRanges"))
|
|
40934
41023
|
});
|
|
40935
41024
|
})).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
|
|
40936
41025
|
/**
|
|
@@ -41145,7 +41234,7 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
|
|
|
41145
41234
|
for (const [absolutePath, originalContent] of originalContents) try {
|
|
41146
41235
|
NFS.writeFileSync(absolutePath, originalContent);
|
|
41147
41236
|
} catch (error) {
|
|
41148
|
-
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${
|
|
41237
|
+
process.stderr.write(`[react-doctor] Failed to restore inline disable directives in ${absolutePath}: ${messageFromUnknown(error)}\n[react-doctor] Run: git checkout -- ${absolutePath}\n`);
|
|
41149
41238
|
}
|
|
41150
41239
|
};
|
|
41151
41240
|
const onExit = () => restore();
|
|
@@ -41251,7 +41340,7 @@ const resolveUserPlugin = (spec, configSourceDirectory) => {
|
|
|
41251
41340
|
try {
|
|
41252
41341
|
resolvedSpecifier = isRelative ? Path.resolve(configSourceDirectory, spec) : candidateRequire.resolve(spec);
|
|
41253
41342
|
} catch (error) {
|
|
41254
|
-
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${
|
|
41343
|
+
warnConfigIssue(`config.plugins entry "${spec}" could not be resolved from ${configSourceDirectory}: ${messageFromUnknown(error)}`);
|
|
41255
41344
|
return null;
|
|
41256
41345
|
}
|
|
41257
41346
|
const { name, ruleNames } = readPluginShape(resolvedSpecifier, (target) => candidateRequire(target));
|
|
@@ -42097,7 +42186,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
42097
42186
|
child.kill("SIGKILL");
|
|
42098
42187
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
42099
42188
|
kind: "timeout",
|
|
42100
|
-
detail: `${spawnTimeoutMs /
|
|
42189
|
+
detail: `${spawnTimeoutMs / MILLISECONDS_PER_SECOND}s budget exceeded`
|
|
42101
42190
|
}) }));
|
|
42102
42191
|
}, spawnTimeoutMs);
|
|
42103
42192
|
timeoutHandle.unref?.();
|
|
@@ -43219,17 +43308,17 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
43219
43308
|
}))))))));
|
|
43220
43309
|
const deadCodeFailureState = yield* get$2(deadCodeFailure);
|
|
43221
43310
|
const scanElapsedMilliseconds = Date.now() - scanStartTime;
|
|
43222
|
-
const scanElapsedSeconds = (scanElapsedMilliseconds /
|
|
43311
|
+
const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
|
|
43223
43312
|
if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
|
|
43224
43313
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
43225
43314
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
43226
43315
|
yield* reporterService.finalize;
|
|
43227
|
-
const finalDiagnostics = [
|
|
43316
|
+
const finalDiagnostics = assignFixGroups([
|
|
43228
43317
|
...envCollected,
|
|
43229
43318
|
...supplyChainCollected,
|
|
43230
43319
|
...lintCollected,
|
|
43231
43320
|
...deadCodeCollected
|
|
43232
|
-
];
|
|
43321
|
+
]);
|
|
43233
43322
|
const githubViewerPermission = yield* join(githubViewerPermissionFiber);
|
|
43234
43323
|
const scoreMetadata = {
|
|
43235
43324
|
...repo !== null ? { repo } : {},
|
|
@@ -43456,7 +43545,7 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
|
|
|
43456
43545
|
static layerNode = effect(StagedFiles, gen(function* () {
|
|
43457
43546
|
const git = yield* Git;
|
|
43458
43547
|
return StagedFiles.of({
|
|
43459
|
-
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile))),
|
|
43548
|
+
discoverSourceFiles: (directory) => git.stagedFilePaths(directory).pipe(map$3((entries) => entries.filter(isLintableSourceFile)), withSpan("StagedFiles.discoverSourceFiles")),
|
|
43460
43549
|
materialize: ({ directory, stagedFiles, tempDirectory }) => materializeSourceTree({
|
|
43461
43550
|
directory,
|
|
43462
43551
|
files: stagedFiles,
|
|
@@ -43466,7 +43555,7 @@ var StagedFiles = class StagedFiles extends Service()("react-doctor/StagedFiles"
|
|
|
43466
43555
|
tempDirectory: tree.tempDirectory,
|
|
43467
43556
|
stagedFiles: tree.materializedFiles,
|
|
43468
43557
|
cleanup: tree.cleanup
|
|
43469
|
-
})))
|
|
43558
|
+
})), withSpan("StagedFiles.materialize"))
|
|
43470
43559
|
});
|
|
43471
43560
|
}));
|
|
43472
43561
|
/**
|
|
@@ -43599,6 +43688,7 @@ const buildJsonReport = (input) => {
|
|
|
43599
43688
|
score: result.score,
|
|
43600
43689
|
skippedChecks: result.skippedChecks,
|
|
43601
43690
|
...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
|
|
43691
|
+
...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
|
|
43602
43692
|
elapsedMilliseconds: result.elapsedMilliseconds
|
|
43603
43693
|
}));
|
|
43604
43694
|
const flattenedDiagnostics = projects.flatMap((entry) => entry.diagnostics);
|
|
@@ -43934,6 +44024,17 @@ const detectTerminalKind = (env = process.env) => {
|
|
|
43934
44024
|
return "unknown";
|
|
43935
44025
|
};
|
|
43936
44026
|
//#endregion
|
|
44027
|
+
//#region src/cli/utils/is-debug-flag.ts
|
|
44028
|
+
/**
|
|
44029
|
+
* Whether the user passed `--debug` (surface the run's Sentry trace id, and
|
|
44030
|
+
* force performance tracing on so there's a trace to surface). Read straight
|
|
44031
|
+
* from argv rather than Commander's parsed flags because `initializeSentry()`
|
|
44032
|
+
* runs before Commander parses — the same reason `shouldEnableSentry()` reads
|
|
44033
|
+
* `--no-score` from argv. Sharing this one reader keeps the init-time sampling
|
|
44034
|
+
* override and the end-of-run print in agreement.
|
|
44035
|
+
*/
|
|
44036
|
+
const isDebugFlagEnabled = (argv = process.argv) => argv.includes("--debug");
|
|
44037
|
+
//#endregion
|
|
43937
44038
|
//#region src/cli/utils/is-git-hook-environment.ts
|
|
43938
44039
|
const isGitHookEnvironment = () => Boolean(process.env.GIT_DIR);
|
|
43939
44040
|
//#endregion
|
|
@@ -44041,7 +44142,7 @@ const makeNoopConsole = () => ({
|
|
|
44041
44142
|
});
|
|
44042
44143
|
//#endregion
|
|
44043
44144
|
//#region src/cli/utils/version.ts
|
|
44044
|
-
const VERSION = "0.5.6-dev.
|
|
44145
|
+
const VERSION = "0.5.6-dev.a9d2713";
|
|
44045
44146
|
//#endregion
|
|
44046
44147
|
//#region src/cli/utils/json-mode.ts
|
|
44047
44148
|
let context = null;
|
|
@@ -44193,6 +44294,7 @@ const buildRunContext = () => {
|
|
|
44193
44294
|
interactive: !isNonInteractiveEnvironment(),
|
|
44194
44295
|
terminalKind: detectTerminalKind(),
|
|
44195
44296
|
jsonMode: isJsonModeActive(),
|
|
44297
|
+
debug: isDebugFlagEnabled(),
|
|
44196
44298
|
invokedVia: detectInvokedVia()
|
|
44197
44299
|
};
|
|
44198
44300
|
};
|
|
@@ -44264,6 +44366,7 @@ const buildSentryScope = (runContext = buildRunContext()) => {
|
|
|
44264
44366
|
interactive: runContext.interactive,
|
|
44265
44367
|
terminalKind: runContext.terminalKind,
|
|
44266
44368
|
jsonMode: runContext.jsonMode,
|
|
44369
|
+
debug: runContext.debug,
|
|
44267
44370
|
invokedVia: runContext.invokedVia,
|
|
44268
44371
|
nodeMajor: runContext.nodeMajor
|
|
44269
44372
|
};
|
|
@@ -44401,13 +44504,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
44401
44504
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
44402
44505
|
* standard `SENTRY_RELEASE` override.
|
|
44403
44506
|
*/
|
|
44404
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.
|
|
44507
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.a9d2713`;
|
|
44405
44508
|
/**
|
|
44406
44509
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
44407
44510
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
44408
44511
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
44409
44512
|
*/
|
|
44410
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.
|
|
44513
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.a9d2713") ? "development" : "production");
|
|
44411
44514
|
/**
|
|
44412
44515
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
44413
44516
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -44471,7 +44574,7 @@ const flushSentry = async () => {
|
|
|
44471
44574
|
const initializeSentry = () => {
|
|
44472
44575
|
if (isInitialized || !shouldEnableSentry()) return;
|
|
44473
44576
|
isInitialized = true;
|
|
44474
|
-
resolvedTracesSampleRate = resolveTracesSampleRate();
|
|
44577
|
+
resolvedTracesSampleRate = isDebugFlagEnabled() ? 1 : resolveTracesSampleRate();
|
|
44475
44578
|
const { tags, contexts } = buildSentryScope();
|
|
44476
44579
|
Sentry.init({
|
|
44477
44580
|
dsn: process.env.SENTRY_DSN || "https://f253d570240a59b8dbd77b7a548ef133@o4510226365743104.ingest.us.sentry.io/4511487817809920",
|
|
@@ -47687,6 +47790,11 @@ const setActiveRunTrace = (trace) => {
|
|
|
47687
47790
|
activeRunTrace = trace;
|
|
47688
47791
|
};
|
|
47689
47792
|
const getActiveRunTrace = () => activeRunTrace;
|
|
47793
|
+
let lastRunTraceId = null;
|
|
47794
|
+
const recordRunTraceId = (traceId) => {
|
|
47795
|
+
lastRunTraceId = traceId;
|
|
47796
|
+
};
|
|
47797
|
+
const getLastRunTraceId = () => lastRunTraceId;
|
|
47690
47798
|
//#endregion
|
|
47691
47799
|
//#region src/cli/utils/to-span-attributes.ts
|
|
47692
47800
|
/**
|
|
@@ -47749,14 +47857,13 @@ const withSentryRunSpan = (run, options = {}) => {
|
|
|
47749
47857
|
op: "cli.inspect",
|
|
47750
47858
|
attributes: toSpanAttributes(tags)
|
|
47751
47859
|
}, (rootSpan) => {
|
|
47752
|
-
|
|
47753
|
-
|
|
47754
|
-
|
|
47755
|
-
|
|
47756
|
-
|
|
47757
|
-
|
|
47758
|
-
|
|
47759
|
-
}
|
|
47860
|
+
const spanContext = rootSpan.spanContext();
|
|
47861
|
+
recordRunTraceId(spanContext.traceId);
|
|
47862
|
+
if (options.concurrentScan !== true) setActiveRunTrace({
|
|
47863
|
+
traceId: spanContext.traceId,
|
|
47864
|
+
spanId: spanContext.spanId,
|
|
47865
|
+
sampled: (spanContext.traceFlags & 1) === 1
|
|
47866
|
+
});
|
|
47760
47867
|
return run(rootSpan);
|
|
47761
47868
|
});
|
|
47762
47869
|
};
|
|
@@ -48014,10 +48121,14 @@ const buildOutcomeAttributes = (input) => {
|
|
|
48014
48121
|
}
|
|
48015
48122
|
let diagnosticsInTestFiles = 0;
|
|
48016
48123
|
let diagnosticsInStoryFiles = 0;
|
|
48124
|
+
const findingsPerFixGroup = /* @__PURE__ */ new Map();
|
|
48017
48125
|
for (const diagnostic of result.diagnostics) {
|
|
48018
48126
|
if (diagnostic.fileContext === "test") diagnosticsInTestFiles += 1;
|
|
48019
48127
|
if (diagnostic.fileContext === "story") diagnosticsInStoryFiles += 1;
|
|
48128
|
+
if (diagnostic.fixGroupId) findingsPerFixGroup.set(diagnostic.fixGroupId, (findingsPerFixGroup.get(diagnostic.fixGroupId) ?? 0) + 1);
|
|
48020
48129
|
}
|
|
48130
|
+
let fixGroupedFindings = 0;
|
|
48131
|
+
for (const count of findingsPerFixGroup.values()) fixGroupedFindings += count;
|
|
48021
48132
|
const attributes = {
|
|
48022
48133
|
outcome,
|
|
48023
48134
|
exitCode: wouldBlock ? 1 : 0,
|
|
@@ -48031,6 +48142,8 @@ const buildOutcomeAttributes = (input) => {
|
|
|
48031
48142
|
diagnosticsInTestFiles,
|
|
48032
48143
|
diagnosticsInStoryFiles,
|
|
48033
48144
|
distinctRulesFired: countByRule.size,
|
|
48145
|
+
"diag.fixGroups": findingsPerFixGroup.size,
|
|
48146
|
+
"diag.fixGroupedFindings": fixGroupedFindings,
|
|
48034
48147
|
topRule,
|
|
48035
48148
|
scannedFileCount: result.scannedFileCount ?? null,
|
|
48036
48149
|
elapsedMs: result.elapsedMilliseconds,
|
|
@@ -48220,6 +48333,12 @@ const compareByRulePriority = (ruleKeyA, ruleKeyB, rulePriority) => {
|
|
|
48220
48333
|
};
|
|
48221
48334
|
const sortRuleGroupsByImportance = (diagnosticGroups, rulePriority) => diagnosticGroups.toSorted(([ruleKeyA], [ruleKeyB]) => compareByRulePriority(ruleKeyA, ruleKeyB, rulePriority));
|
|
48222
48335
|
const buildSortedRuleGroups = (diagnostics, rulePriority) => sortRuleGroupsByImportance([...groupBy([...diagnostics], (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`)], rulePriority);
|
|
48336
|
+
const getSharedFixSiteCount = (diagnostics) => {
|
|
48337
|
+
if (diagnostics.length < 2) return 0;
|
|
48338
|
+
const firstFixGroupId = diagnostics[0]?.fixGroupId;
|
|
48339
|
+
if (!firstFixGroupId) return 0;
|
|
48340
|
+
return diagnostics.every((diagnostic) => diagnostic.fixGroupId === firstFixGroupId) ? diagnostics.length : 0;
|
|
48341
|
+
};
|
|
48223
48342
|
const FIX_RECIPE_DIRECTIVE_LABEL = "Curl with no cache & follow the canonical fix and false positive check recipe before fixing";
|
|
48224
48343
|
const formatFixRecipeLine = (diagnostic) => hasPublishedFixRecipe(diagnostic) ? `${FIX_RECIPE_DIRECTIVE_LABEL}: ${buildRuleDocsUrl(diagnostic.plugin, diagnostic.rule)}` : null;
|
|
48225
48344
|
const formatLearnMoreLine = (diagnostic) => hasPublishedFixRecipe(diagnostic) ? `Learn more: ${buildRuleDocsUrl(diagnostic.plugin, diagnostic.rule)}` : null;
|
|
@@ -48535,6 +48654,8 @@ const buildRuleDetailBlock = (ruleKey, ruleDiagnostics, resolveSourceRoot, rende
|
|
|
48535
48654
|
const impactMessages = isCollapsedWarningGroup ? [...new Set(ruleDiagnostics.map((diagnostic) => diagnostic.message))] : [representative.message];
|
|
48536
48655
|
for (const impactMessage of impactMessages) for (const explanationLine of wrapTextToWidth(impactMessage, resolveMeasureWidth(4), { breakLongWords: false })) lines.push(`${TOP_ERROR_DETAIL_INDENT}${explanationLine}`);
|
|
48537
48656
|
if (representative.help) for (const fixLine of wrapTextToWidth(`→ ${representative.help}`, resolveMeasureWidth(4), { breakLongWords: false })) lines.push(highlighter.dim(`${TOP_ERROR_DETAIL_INDENT}${fixLine}`));
|
|
48657
|
+
const sharedFixSiteCount = getSharedFixSiteCount(ruleDiagnostics);
|
|
48658
|
+
if (sharedFixSiteCount > 0) lines.push(highlighter.dim(`${TOP_ERROR_DETAIL_INDENT}↳ One fix clears all ${sharedFixSiteCount} findings.`));
|
|
48538
48659
|
if (renderEverySite && isAgentEnvironment) {
|
|
48539
48660
|
const fixRecipeLine = formatFixRecipeLine(representative);
|
|
48540
48661
|
if (fixRecipeLine) lines.push(highlighter.gray(`${TOP_ERROR_DETAIL_INDENT}${fixRecipeLine}`));
|
|
@@ -50093,7 +50214,9 @@ const buildHandoffPayload = (input) => {
|
|
|
50093
50214
|
topGroups.forEach(([ruleKey, ruleDiagnostics], index) => {
|
|
50094
50215
|
const representative = ruleDiagnostics[0];
|
|
50095
50216
|
const severityLabel = representative.severity === "error" ? "ERROR" : "WARN";
|
|
50096
|
-
|
|
50217
|
+
const sharedFixSiteCount = getSharedFixSiteCount(ruleDiagnostics);
|
|
50218
|
+
const countBadge = sharedFixSiteCount > 0 ? `one fix · ${sharedFixSiteCount} sites` : `×${ruleDiagnostics.length}`;
|
|
50219
|
+
lines.push(`${index + 1}. ${severityLabel} ${representative.category}: ${representative.title ?? ruleKey} (${countBadge})`, ` ${representative.message}`);
|
|
50097
50220
|
const fixRecipeLine = formatFixRecipeLine(representative);
|
|
50098
50221
|
if (fixRecipeLine) lines.push(` ${fixRecipeLine}`);
|
|
50099
50222
|
const uniqueFiles = [...new Set(ruleDiagnostics.map((diagnostic) => diagnostic.filePath))];
|
|
@@ -50106,7 +50229,7 @@ const buildHandoffPayload = (input) => {
|
|
|
50106
50229
|
});
|
|
50107
50230
|
lines.push("");
|
|
50108
50231
|
if (outputDirectory) lines.push(`Full results for all ${input.diagnostics.length} issues (diagnostics.json + a .txt per rule): ${outputDirectory}`, "");
|
|
50109
|
-
lines.push("Read each file and fix the root cause — don't suppress or silence the rule.", "", "Verify against the real thing, don't assume: confirm each change matches the canonical fix recipe you fetched for that rule, then re-run `npx react-doctor@latest --verbose` and check the issue is actually gone against the real tool before moving on.", "", "Teach me as you go: for every issue you touch, explain it in plain language (no jargon) — what the problem is, why it's a problem, and how serious it is in human terms. Describe the real-world impact and severity concretely (e.g. \"this crashes the page for users on Safari\" vs. \"this is a minor cleanup with no user impact\") so I understand why it matters, not just what changed.", "", "Then work through the rest from the full results above.");
|
|
50232
|
+
lines.push("Read each file and fix the root cause — don't suppress or silence the rule.", "", "Findings that share a `fixGroupId` (in diagnostics.json) are one root cause — a single fix clears all of them, so treat each `fixGroupId` as ONE task, not one per site.", "", "Verify against the real thing, don't assume: confirm each change matches the canonical fix recipe you fetched for that rule, then re-run `npx react-doctor@latest --verbose` and check the issue is actually gone against the real tool before moving on.", "", "Teach me as you go: for every issue you touch, explain it in plain language (no jargon) — what the problem is, why it's a problem, and how serious it is in human terms. Describe the real-world impact and severity concretely (e.g. \"this crashes the page for users on Safari\" vs. \"this is a minor cleanup with no user impact\") so I understand why it matters, not just what changed.", "", "Then work through the rest from the full results above.");
|
|
50110
50233
|
return lines.join("\n");
|
|
50111
50234
|
};
|
|
50112
50235
|
//#endregion
|
|
@@ -52141,6 +52264,7 @@ const reportErrorToSentry = async (error) => {
|
|
|
52141
52264
|
sampled: runTrace.sampled,
|
|
52142
52265
|
sampleRand: Math.random()
|
|
52143
52266
|
});
|
|
52267
|
+
recordRunTraceId(scope.getPropagationContext().traceId);
|
|
52144
52268
|
return Sentry.captureException(error);
|
|
52145
52269
|
});
|
|
52146
52270
|
await Sentry.flush(SENTRY_FLUSH_TIMEOUT_MS);
|
|
@@ -52755,6 +52879,10 @@ const validateModeFlags = (flags) => {
|
|
|
52755
52879
|
if (flags.staged && (flags.scope === "full" || flags.scope === "changed")) throw new CliInputError(`Cannot combine --staged with --scope ${flags.scope}; use --scope files or --scope lines, or drop --scope.`);
|
|
52756
52880
|
if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
|
|
52757
52881
|
if (flags.score && flags.telemetry === false) throw new CliInputError("Cannot combine --score with --no-telemetry; --score prints the score that --no-telemetry disables.");
|
|
52882
|
+
if (flags.debug && (flags.score === false || flags.telemetry === false)) {
|
|
52883
|
+
const disablingFlag = flags.score === false ? "--no-score" : "--no-telemetry";
|
|
52884
|
+
throw new CliInputError(`Cannot combine --debug with ${disablingFlag}; ${disablingFlag} disables the Sentry reporting --debug needs to capture a trace.`);
|
|
52885
|
+
}
|
|
52758
52886
|
};
|
|
52759
52887
|
//#endregion
|
|
52760
52888
|
//#region src/cli/commands/inspect.ts
|
|
@@ -53108,6 +53236,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
53108
53236
|
} catch (error) {
|
|
53109
53237
|
const isUserError = isExpectedUserError(error);
|
|
53110
53238
|
const sentryEventId = isUserError ? void 0 : await reportErrorToSentry(error);
|
|
53239
|
+
if (isDebugFlagEnabled()) await flushSentry();
|
|
53111
53240
|
if (isJsonMode) {
|
|
53112
53241
|
writeJsonErrorReport(error, sentryEventId);
|
|
53113
53242
|
process.exitCode = 1;
|
|
@@ -53830,6 +53959,33 @@ const normalizeHelpInvocation = (argv, knownCommands) => {
|
|
|
53830
53959
|
return [...nodeArguments, "--help"];
|
|
53831
53960
|
};
|
|
53832
53961
|
//#endregion
|
|
53962
|
+
//#region src/cli/utils/print-debug-trace.ts
|
|
53963
|
+
/**
|
|
53964
|
+
* The `--debug` end-of-run line, pure so it's testable without the Sentry SDK.
|
|
53965
|
+
* Mirrors the crash-reference phrasing in `handle-error.ts` ("mention this when
|
|
53966
|
+
* reporting") so users learn one habit for both paths. A `null` trace says why,
|
|
53967
|
+
* so `--debug` never silently does nothing.
|
|
53968
|
+
*/
|
|
53969
|
+
const buildDebugTraceMessage = (traceId) => traceId === null ? "Sentry trace unavailable for this run (no trace was recorded)." : `Sentry trace (mention this when reporting): ${traceId}`;
|
|
53970
|
+
/**
|
|
53971
|
+
* Prints the run's Sentry trace id to stderr at the end of a `--debug` run, so
|
|
53972
|
+
* maintainers can pull the full trace from a pasted id. Runs from the process
|
|
53973
|
+
* `exit` handler, so it's the last line on both the success path and the error
|
|
53974
|
+
* funnels (which `process.exit()` before the promise chain could resume).
|
|
53975
|
+
*
|
|
53976
|
+
* Writes straight to `process.stderr` (not `Console`) for three reasons: the
|
|
53977
|
+
* exit handler is synchronous, JSON mode patches the global console to no-ops —
|
|
53978
|
+
* a diagnostic the user explicitly asked for must survive that — and stderr
|
|
53979
|
+
* keeps `--json` / `--score` stdout machine-clean. The write is wrapped because
|
|
53980
|
+
* a diagnostic must never throw out of an exit handler.
|
|
53981
|
+
*/
|
|
53982
|
+
const printDebugTrace = () => {
|
|
53983
|
+
if (!Sentry.isInitialized()) return;
|
|
53984
|
+
try {
|
|
53985
|
+
process.stderr.write(`${highlighter.dim(buildDebugTraceMessage(getLastRunTraceId()))}\n`);
|
|
53986
|
+
} catch {}
|
|
53987
|
+
};
|
|
53988
|
+
//#endregion
|
|
53833
53989
|
//#region src/cli/utils/removed-cli-flags.ts
|
|
53834
53990
|
const REMOVED_FLAGS = new Map([
|
|
53835
53991
|
["--full", "use `--diff false` to force a full scan"],
|
|
@@ -53856,6 +54012,7 @@ const ROOT_FLAG_SPEC = {
|
|
|
53856
54012
|
longOptionsWithoutValues: new Set([
|
|
53857
54013
|
"--color",
|
|
53858
54014
|
"--dead-code",
|
|
54015
|
+
"--debug",
|
|
53859
54016
|
"--help",
|
|
53860
54017
|
"--json",
|
|
53861
54018
|
"--json-compact",
|
|
@@ -54023,6 +54180,9 @@ const stripUnknownCliFlags = (argv) => {
|
|
|
54023
54180
|
initializeSentry();
|
|
54024
54181
|
process.on("SIGINT", exitGracefully);
|
|
54025
54182
|
process.on("SIGTERM", exitGracefully);
|
|
54183
|
+
process.on("exit", () => {
|
|
54184
|
+
if (isDebugFlagEnabled()) printDebugTrace();
|
|
54185
|
+
});
|
|
54026
54186
|
unrefStdin();
|
|
54027
54187
|
guardStdin();
|
|
54028
54188
|
const formatExampleLines = (examples) => {
|
|
@@ -54067,7 +54227,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
54067
54227
|
${highlighter.info(CANONICAL_GITHUB_URL)}
|
|
54068
54228
|
`;
|
|
54069
54229
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
54070
|
-
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("--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);
|
|
54230
|
+
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);
|
|
54071
54231
|
program.action(inspectAction);
|
|
54072
54232
|
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));
|
|
54073
54233
|
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);
|
|
@@ -54110,4 +54270,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
54110
54270
|
export {};
|
|
54111
54271
|
|
|
54112
54272
|
//# sourceMappingURL=cli.js.map
|
|
54113
|
-
//# debugId=
|
|
54273
|
+
//# debugId=e4d06258-7975-53ee-88e5-490715798dd2
|