react-doctor 0.5.6-dev.5d1347e → 0.5.6-dev.66133f8
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 +364 -159
- package/dist/index.d.ts +19 -0
- package/dist/index.js +221 -120
- package/dist/lsp.js +220 -121
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -9489,6 +9489,15 @@ interface Diagnostic {
|
|
|
9489
9489
|
suppressionHint?: string;
|
|
9490
9490
|
/** Secondary source locations (oxlint's non-primary labels). */
|
|
9491
9491
|
relatedLocations?: DiagnosticRelatedLocation[];
|
|
9492
|
+
/**
|
|
9493
|
+
* Stable id shared by every finding that a single fix resolves together —
|
|
9494
|
+
* e.g. four `useEffect`s that reset state on one prop change all clear with
|
|
9495
|
+
* one `key` prop. Set only when ≥2 findings share a root cause; absent for
|
|
9496
|
+
* standalone findings. A consumer that turns findings into work items should
|
|
9497
|
+
* group by it so one fix reads as one task, not N. Presentation-only and
|
|
9498
|
+
* score-neutral — the score never reads it.
|
|
9499
|
+
*/
|
|
9500
|
+
fixGroupId?: string;
|
|
9492
9501
|
}
|
|
9493
9502
|
//#endregion
|
|
9494
9503
|
//#region src/types/project-info.d.ts
|
|
@@ -9794,6 +9803,16 @@ interface JsonReportProjectEntry {
|
|
|
9794
9803
|
skippedChecks: string[];
|
|
9795
9804
|
/** Human-readable explanation per skipped check. See `InspectResult.skippedCheckReasons`. */
|
|
9796
9805
|
skippedCheckReasons?: Record<string, string>;
|
|
9806
|
+
/**
|
|
9807
|
+
* Number of source files this scan's linter examined. In diff / changed
|
|
9808
|
+
* mode it's the count of changed React-eligible files (`.tsx`/`.jsx` plus
|
|
9809
|
+
* framework entry files); in a full scan it's the whole source tree. `0`
|
|
9810
|
+
* in a diff scan means the changed files held nothing React Doctor lints —
|
|
9811
|
+
* the GitHub Action reads that as "nothing to report" (skips the PR comment;
|
|
9812
|
+
* the commit status says "skipped"). Optional: absent on reports from
|
|
9813
|
+
* constructors that don't track it (e.g. `toJsonReport`).
|
|
9814
|
+
*/
|
|
9815
|
+
scannedFileCount?: number;
|
|
9797
9816
|
elapsedMilliseconds: number;
|
|
9798
9817
|
}
|
|
9799
9818
|
interface JsonReportSummary {
|
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]="03972752-ed17-5a0b-a633-71b652d2f457")}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";
|
|
@@ -17,6 +17,7 @@ import * as NodeUrl from "node:url";
|
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { createJiti } from "jiti";
|
|
19
19
|
import * as Crypto from "node:crypto";
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
20
21
|
import { gzipSync } from "node:zlib";
|
|
21
22
|
//#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
|
|
22
23
|
/**
|
|
@@ -19256,7 +19257,8 @@ var Diagnostic = class extends Class("Diagnostic")({
|
|
|
19256
19257
|
category: String$1,
|
|
19257
19258
|
fileContext: optional(Literals(["test", "story"])),
|
|
19258
19259
|
suppressionHint: optional(String$1),
|
|
19259
|
-
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation))
|
|
19260
|
+
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
|
|
19261
|
+
fixGroupId: optional(String$1)
|
|
19260
19262
|
}) {};
|
|
19261
19263
|
const JsonReportMode = Literals([
|
|
19262
19264
|
"full",
|
|
@@ -19298,6 +19300,7 @@ var JsonReportProjectEntry = class extends Class("JsonReportProjectEntry")({
|
|
|
19298
19300
|
score: Unknown,
|
|
19299
19301
|
skippedChecks: ArraySchema(String$1),
|
|
19300
19302
|
skippedCheckReasons: optional(Record$1(String$1, String$1)),
|
|
19303
|
+
scannedFileCount: optional(Number$1),
|
|
19301
19304
|
elapsedMilliseconds: Number$1
|
|
19302
19305
|
}) {};
|
|
19303
19306
|
/**
|
|
@@ -33660,6 +33663,13 @@ const APP_ONLY_RULE_KEYS = new Set([
|
|
|
33660
33663
|
]);
|
|
33661
33664
|
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
33662
33665
|
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
33666
|
+
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
33667
|
+
"react-doctor/no-derived-state",
|
|
33668
|
+
"react-doctor/no-derived-state-effect",
|
|
33669
|
+
"react-doctor/no-derived-useState",
|
|
33670
|
+
"react-doctor/no-adjust-state-on-prop-change",
|
|
33671
|
+
"react-doctor/no-reset-all-state-on-prop-change"
|
|
33672
|
+
]);
|
|
33663
33673
|
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
33664
33674
|
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
33665
33675
|
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
@@ -34237,115 +34247,6 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34237
34247
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34238
34248
|
};
|
|
34239
34249
|
};
|
|
34240
|
-
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34241
|
-
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34242
|
-
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
34243
|
-
let stringDelimiter = null;
|
|
34244
|
-
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
34245
|
-
const character = line[charIndex];
|
|
34246
|
-
if (stringDelimiter !== null) {
|
|
34247
|
-
if (character === "\\") {
|
|
34248
|
-
charIndex++;
|
|
34249
|
-
continue;
|
|
34250
|
-
}
|
|
34251
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
34252
|
-
continue;
|
|
34253
|
-
}
|
|
34254
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
34255
|
-
stringDelimiter = character;
|
|
34256
|
-
continue;
|
|
34257
|
-
}
|
|
34258
|
-
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
34259
|
-
}
|
|
34260
|
-
return false;
|
|
34261
|
-
};
|
|
34262
|
-
const findOpenerTagOnLine = (line) => {
|
|
34263
|
-
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
34264
|
-
if (match.index === void 0) continue;
|
|
34265
|
-
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
34266
|
-
}
|
|
34267
|
-
return null;
|
|
34268
|
-
};
|
|
34269
|
-
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
34270
|
-
const openerLine = lines[openerLineIndex];
|
|
34271
|
-
if (openerLine === void 0) return null;
|
|
34272
|
-
const opener = findOpenerTagOnLine(openerLine);
|
|
34273
|
-
if (!opener) return null;
|
|
34274
|
-
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
34275
|
-
let braceDepth = 0;
|
|
34276
|
-
let innerAngleDepth = 0;
|
|
34277
|
-
let stringDelimiter = null;
|
|
34278
|
-
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
34279
|
-
const currentLine = lines[lineIndex];
|
|
34280
|
-
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
34281
|
-
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
34282
|
-
const character = currentLine[charIndex];
|
|
34283
|
-
if (stringDelimiter !== null) {
|
|
34284
|
-
if (character === "\\") {
|
|
34285
|
-
charIndex++;
|
|
34286
|
-
continue;
|
|
34287
|
-
}
|
|
34288
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
34289
|
-
continue;
|
|
34290
|
-
}
|
|
34291
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
34292
|
-
stringDelimiter = character;
|
|
34293
|
-
continue;
|
|
34294
|
-
}
|
|
34295
|
-
if (character === "{") {
|
|
34296
|
-
braceDepth++;
|
|
34297
|
-
continue;
|
|
34298
|
-
}
|
|
34299
|
-
if (character === "}") {
|
|
34300
|
-
braceDepth--;
|
|
34301
|
-
continue;
|
|
34302
|
-
}
|
|
34303
|
-
if (braceDepth !== 0) continue;
|
|
34304
|
-
if (character === "<") {
|
|
34305
|
-
const followCharacter = currentLine[charIndex + 1];
|
|
34306
|
-
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
34307
|
-
continue;
|
|
34308
|
-
}
|
|
34309
|
-
if (character !== ">") continue;
|
|
34310
|
-
const previousCharacter = currentLine[charIndex - 1];
|
|
34311
|
-
const nextCharacter = currentLine[charIndex + 1];
|
|
34312
|
-
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
34313
|
-
if (innerAngleDepth > 0) {
|
|
34314
|
-
innerAngleDepth--;
|
|
34315
|
-
continue;
|
|
34316
|
-
}
|
|
34317
|
-
return lineIndex;
|
|
34318
|
-
}
|
|
34319
|
-
}
|
|
34320
|
-
return null;
|
|
34321
|
-
};
|
|
34322
|
-
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
34323
|
-
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
34324
|
-
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
34325
|
-
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
34326
|
-
}
|
|
34327
|
-
return null;
|
|
34328
|
-
};
|
|
34329
|
-
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34330
|
-
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
34331
|
-
const collected = [];
|
|
34332
|
-
let isStillInChain = true;
|
|
34333
|
-
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
34334
|
-
const candidateLine = lines[candidateIndex];
|
|
34335
|
-
if (candidateLine === void 0) break;
|
|
34336
|
-
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
34337
|
-
if (match) {
|
|
34338
|
-
collected.push({
|
|
34339
|
-
commentLineIndex: candidateIndex,
|
|
34340
|
-
ruleList: match[1],
|
|
34341
|
-
isInChain: isStillInChain
|
|
34342
|
-
});
|
|
34343
|
-
continue;
|
|
34344
|
-
}
|
|
34345
|
-
isStillInChain = false;
|
|
34346
|
-
}
|
|
34347
|
-
return collected;
|
|
34348
|
-
};
|
|
34349
34250
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34350
34251
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34351
34252
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34470,7 +34371,13 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
|
|
|
34470
34371
|
}
|
|
34471
34372
|
const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
|
|
34472
34373
|
const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
|
|
34473
|
-
const
|
|
34374
|
+
const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
|
|
34375
|
+
const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
|
|
34376
|
+
const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
|
|
34377
|
+
const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
|
|
34378
|
+
if (canonicalCandidate === canonicalTarget) return true;
|
|
34379
|
+
return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
|
|
34380
|
+
};
|
|
34474
34381
|
const getEquivalentRuleKeys = (ruleKey) => {
|
|
34475
34382
|
const nativeRuleKey = canonicalizeRuleKey(ruleKey);
|
|
34476
34383
|
return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
|
|
@@ -34480,12 +34387,182 @@ const stripDescriptionTail = (ruleList) => {
|
|
|
34480
34387
|
if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
|
|
34481
34388
|
return ruleList.slice(0, descriptionMatch.index);
|
|
34482
34389
|
};
|
|
34483
|
-
const
|
|
34390
|
+
const tokenizeRuleList = (ruleList) => {
|
|
34484
34391
|
const trimmed = ruleList?.trim();
|
|
34485
|
-
if (!trimmed) return
|
|
34392
|
+
if (!trimmed) return [];
|
|
34486
34393
|
const ruleSection = stripDescriptionTail(trimmed).trim();
|
|
34487
|
-
if (!ruleSection) return
|
|
34488
|
-
return ruleSection.split(/[,\s]+/).
|
|
34394
|
+
if (!ruleSection) return [];
|
|
34395
|
+
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34396
|
+
};
|
|
34397
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34398
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34399
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34400
|
+
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}\`.`;
|
|
34401
|
+
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34402
|
+
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34403
|
+
const candidates = [{
|
|
34404
|
+
line: lines[diagnosticLineIndex],
|
|
34405
|
+
requiredScope: "line"
|
|
34406
|
+
}, {
|
|
34407
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34408
|
+
requiredScope: "next-line"
|
|
34409
|
+
}];
|
|
34410
|
+
for (const { line, requiredScope } of candidates) {
|
|
34411
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34412
|
+
if (!match) continue;
|
|
34413
|
+
const [, tool, scope, ruleList] = match;
|
|
34414
|
+
if (scope !== requiredScope) continue;
|
|
34415
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34416
|
+
if (tokens.includes(ruleId)) continue;
|
|
34417
|
+
for (const token of tokens) if (tokenMisnamesRule(token, ruleId)) return buildHint(tool, token, ruleId);
|
|
34418
|
+
}
|
|
34419
|
+
return null;
|
|
34420
|
+
};
|
|
34421
|
+
const detectBlockNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34422
|
+
let openMisname = null;
|
|
34423
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34424
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34425
|
+
const line = lines[lineIndex];
|
|
34426
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34427
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34428
|
+
if (disableMatch) {
|
|
34429
|
+
const [, tool, ruleList] = disableMatch;
|
|
34430
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34431
|
+
if (tokens.includes(ruleId)) openMisname = null;
|
|
34432
|
+
else {
|
|
34433
|
+
const misnamed = tokens.find((token) => tokenMisnamesRule(token, ruleId));
|
|
34434
|
+
if (misnamed) openMisname = {
|
|
34435
|
+
tool,
|
|
34436
|
+
token: misnamed
|
|
34437
|
+
};
|
|
34438
|
+
}
|
|
34439
|
+
continue;
|
|
34440
|
+
}
|
|
34441
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34442
|
+
if (enableMatch) {
|
|
34443
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34444
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => isSameRuleKey(rule, ruleId))) openMisname = null;
|
|
34445
|
+
}
|
|
34446
|
+
}
|
|
34447
|
+
return openMisname ? buildHint(openMisname.tool, openMisname.token, ruleId) : null;
|
|
34448
|
+
};
|
|
34449
|
+
const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34450
|
+
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34451
|
+
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34452
|
+
};
|
|
34453
|
+
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34454
|
+
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34455
|
+
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
34456
|
+
let stringDelimiter = null;
|
|
34457
|
+
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
34458
|
+
const character = line[charIndex];
|
|
34459
|
+
if (stringDelimiter !== null) {
|
|
34460
|
+
if (character === "\\") {
|
|
34461
|
+
charIndex++;
|
|
34462
|
+
continue;
|
|
34463
|
+
}
|
|
34464
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
34465
|
+
continue;
|
|
34466
|
+
}
|
|
34467
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
34468
|
+
stringDelimiter = character;
|
|
34469
|
+
continue;
|
|
34470
|
+
}
|
|
34471
|
+
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
34472
|
+
}
|
|
34473
|
+
return false;
|
|
34474
|
+
};
|
|
34475
|
+
const findOpenerTagOnLine = (line) => {
|
|
34476
|
+
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
34477
|
+
if (match.index === void 0) continue;
|
|
34478
|
+
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
34479
|
+
}
|
|
34480
|
+
return null;
|
|
34481
|
+
};
|
|
34482
|
+
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
34483
|
+
const openerLine = lines[openerLineIndex];
|
|
34484
|
+
if (openerLine === void 0) return null;
|
|
34485
|
+
const opener = findOpenerTagOnLine(openerLine);
|
|
34486
|
+
if (!opener) return null;
|
|
34487
|
+
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
34488
|
+
let braceDepth = 0;
|
|
34489
|
+
let innerAngleDepth = 0;
|
|
34490
|
+
let stringDelimiter = null;
|
|
34491
|
+
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
34492
|
+
const currentLine = lines[lineIndex];
|
|
34493
|
+
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
34494
|
+
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
34495
|
+
const character = currentLine[charIndex];
|
|
34496
|
+
if (stringDelimiter !== null) {
|
|
34497
|
+
if (character === "\\") {
|
|
34498
|
+
charIndex++;
|
|
34499
|
+
continue;
|
|
34500
|
+
}
|
|
34501
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
34502
|
+
continue;
|
|
34503
|
+
}
|
|
34504
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
34505
|
+
stringDelimiter = character;
|
|
34506
|
+
continue;
|
|
34507
|
+
}
|
|
34508
|
+
if (character === "{") {
|
|
34509
|
+
braceDepth++;
|
|
34510
|
+
continue;
|
|
34511
|
+
}
|
|
34512
|
+
if (character === "}") {
|
|
34513
|
+
braceDepth--;
|
|
34514
|
+
continue;
|
|
34515
|
+
}
|
|
34516
|
+
if (braceDepth !== 0) continue;
|
|
34517
|
+
if (character === "<") {
|
|
34518
|
+
const followCharacter = currentLine[charIndex + 1];
|
|
34519
|
+
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
34520
|
+
continue;
|
|
34521
|
+
}
|
|
34522
|
+
if (character !== ">") continue;
|
|
34523
|
+
const previousCharacter = currentLine[charIndex - 1];
|
|
34524
|
+
const nextCharacter = currentLine[charIndex + 1];
|
|
34525
|
+
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
34526
|
+
if (innerAngleDepth > 0) {
|
|
34527
|
+
innerAngleDepth--;
|
|
34528
|
+
continue;
|
|
34529
|
+
}
|
|
34530
|
+
return lineIndex;
|
|
34531
|
+
}
|
|
34532
|
+
}
|
|
34533
|
+
return null;
|
|
34534
|
+
};
|
|
34535
|
+
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
34536
|
+
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
34537
|
+
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
34538
|
+
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
34539
|
+
}
|
|
34540
|
+
return null;
|
|
34541
|
+
};
|
|
34542
|
+
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34543
|
+
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
34544
|
+
const collected = [];
|
|
34545
|
+
let isStillInChain = true;
|
|
34546
|
+
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
34547
|
+
const candidateLine = lines[candidateIndex];
|
|
34548
|
+
if (candidateLine === void 0) break;
|
|
34549
|
+
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
34550
|
+
if (match) {
|
|
34551
|
+
collected.push({
|
|
34552
|
+
commentLineIndex: candidateIndex,
|
|
34553
|
+
ruleList: match[1],
|
|
34554
|
+
isInChain: isStillInChain
|
|
34555
|
+
});
|
|
34556
|
+
continue;
|
|
34557
|
+
}
|
|
34558
|
+
isStillInChain = false;
|
|
34559
|
+
}
|
|
34560
|
+
return collected;
|
|
34561
|
+
};
|
|
34562
|
+
const isRuleListedInComment = (ruleList, ruleId) => {
|
|
34563
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34564
|
+
if (tokens.length === 0) return true;
|
|
34565
|
+
return tokens.some((token) => isSameRuleKey(token, ruleId));
|
|
34489
34566
|
};
|
|
34490
34567
|
const DISABLE_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34491
34568
|
const formatLineGap = (gapLineCount) => `${gapLineCount} line${gapLineCount === 1 ? "" : "s"}`;
|
|
@@ -34529,7 +34606,7 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34529
34606
|
};
|
|
34530
34607
|
return {
|
|
34531
34608
|
isSuppressed: false,
|
|
34532
|
-
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId)
|
|
34609
|
+
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId) ?? detectForeignDisableNearMiss(lines, diagnosticLineIndex, ruleId)
|
|
34533
34610
|
};
|
|
34534
34611
|
};
|
|
34535
34612
|
/**
|
|
@@ -35532,6 +35609,29 @@ const resolveScanTarget = async (requestedDirectory, options = {}) => {
|
|
|
35532
35609
|
didRedirectViaRootDir: redirectedDirectory !== null
|
|
35533
35610
|
};
|
|
35534
35611
|
};
|
|
35612
|
+
const buildFixGroupId = (diagnostic) => createHash("sha1").update(JSON.stringify([
|
|
35613
|
+
diagnostic.filePath,
|
|
35614
|
+
`${diagnostic.plugin}/${diagnostic.rule}`,
|
|
35615
|
+
diagnostic.message
|
|
35616
|
+
])).digest("hex").slice(0, 16);
|
|
35617
|
+
const isGroupableRule = (diagnostic) => ROOT_CAUSE_GROUPABLE_RULE_KEYS.has(`${diagnostic.plugin}/${diagnostic.rule}`);
|
|
35618
|
+
const assignFixGroups = (diagnostics) => {
|
|
35619
|
+
const siteCountByGroupId = /* @__PURE__ */ new Map();
|
|
35620
|
+
for (const diagnostic of diagnostics) {
|
|
35621
|
+
if (!isGroupableRule(diagnostic)) continue;
|
|
35622
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
35623
|
+
siteCountByGroupId.set(groupId, (siteCountByGroupId.get(groupId) ?? 0) + 1);
|
|
35624
|
+
}
|
|
35625
|
+
return diagnostics.map((diagnostic) => {
|
|
35626
|
+
if (!isGroupableRule(diagnostic)) return diagnostic;
|
|
35627
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
35628
|
+
if ((siteCountByGroupId.get(groupId) ?? 0) < 2) return diagnostic;
|
|
35629
|
+
return {
|
|
35630
|
+
...diagnostic,
|
|
35631
|
+
fixGroupId: groupId
|
|
35632
|
+
};
|
|
35633
|
+
});
|
|
35634
|
+
};
|
|
35535
35635
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
35536
35636
|
const buildExpoCheckContext = (rootDirectory, expoVersion) => {
|
|
35537
35637
|
const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
|
|
@@ -39978,12 +40078,12 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
39978
40078
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
39979
40079
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
39980
40080
|
yield* reporterService.finalize;
|
|
39981
|
-
const finalDiagnostics = [
|
|
40081
|
+
const finalDiagnostics = assignFixGroups([
|
|
39982
40082
|
...envCollected,
|
|
39983
40083
|
...supplyChainCollected,
|
|
39984
40084
|
...lintCollected,
|
|
39985
40085
|
...deadCodeCollected
|
|
39986
|
-
];
|
|
40086
|
+
]);
|
|
39987
40087
|
const githubViewerPermission = yield* join(githubViewerPermissionFiber);
|
|
39988
40088
|
const scoreMetadata = {
|
|
39989
40089
|
...repo !== null ? { repo } : {},
|
|
@@ -40329,6 +40429,7 @@ const buildJsonReport = (input) => {
|
|
|
40329
40429
|
score: result.score,
|
|
40330
40430
|
skippedChecks: result.skippedChecks,
|
|
40331
40431
|
...result.skippedCheckReasons ? { skippedCheckReasons: result.skippedCheckReasons } : {},
|
|
40432
|
+
...typeof result.scannedFileCount === "number" ? { scannedFileCount: result.scannedFileCount } : {},
|
|
40332
40433
|
elapsedMilliseconds: result.elapsedMilliseconds
|
|
40333
40434
|
}));
|
|
40334
40435
|
const flattenedDiagnostics = projects.flatMap((entry) => entry.diagnostics);
|
|
@@ -40595,4 +40696,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
40595
40696
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
40596
40697
|
|
|
40597
40698
|
//# sourceMappingURL=index.js.map
|
|
40598
|
-
//# debugId=
|
|
40699
|
+
//# debugId=03972752-ed17-5a0b-a633-71b652d2f457
|