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/lsp.js
CHANGED
|
@@ -14,7 +14,7 @@ import * as NodeUrl from "node:url";
|
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { createJiti } from "jiti";
|
|
16
16
|
import * as Crypto from "node:crypto";
|
|
17
|
-
import crypto from "node:crypto";
|
|
17
|
+
import crypto, { createHash } from "node:crypto";
|
|
18
18
|
import { gzipSync } from "node:zlib";
|
|
19
19
|
import { CodeActionKind, CodeActionTriggerKind, DidChangeWatchedFilesNotification, DocumentDiagnosticReportKind, FileChangeType, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/node.js";
|
|
20
20
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
@@ -19286,7 +19286,8 @@ var Diagnostic = class extends Class("Diagnostic")({
|
|
|
19286
19286
|
category: String$1,
|
|
19287
19287
|
fileContext: optional(Literals(["test", "story"])),
|
|
19288
19288
|
suppressionHint: optional(String$1),
|
|
19289
|
-
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation))
|
|
19289
|
+
relatedLocations: optional(ArraySchema(DiagnosticRelatedLocation)),
|
|
19290
|
+
fixGroupId: optional(String$1)
|
|
19290
19291
|
}) {};
|
|
19291
19292
|
/**
|
|
19292
19293
|
* Deterministic identity string for a diagnostic. Same diagnostic
|
|
@@ -19335,6 +19336,7 @@ var JsonReportProjectEntry = class extends Class("JsonReportProjectEntry")({
|
|
|
19335
19336
|
score: Unknown,
|
|
19336
19337
|
skippedChecks: ArraySchema(String$1),
|
|
19337
19338
|
skippedCheckReasons: optional(Record$1(String$1, String$1)),
|
|
19339
|
+
scannedFileCount: optional(Number$1),
|
|
19338
19340
|
elapsedMilliseconds: Number$1
|
|
19339
19341
|
}) {};
|
|
19340
19342
|
/**
|
|
@@ -33720,6 +33722,13 @@ const APP_ONLY_RULE_KEYS = new Set([
|
|
|
33720
33722
|
]);
|
|
33721
33723
|
const COMPILER_CLEANUP_BUCKET = "compiler-cleanup";
|
|
33722
33724
|
const COMPILER_CLEANUP_RULE_KEYS = new Set(["react-doctor/react-compiler-no-manual-memoization"]);
|
|
33725
|
+
const ROOT_CAUSE_GROUPABLE_RULE_KEYS = new Set([
|
|
33726
|
+
"react-doctor/no-derived-state",
|
|
33727
|
+
"react-doctor/no-derived-state-effect",
|
|
33728
|
+
"react-doctor/no-derived-useState",
|
|
33729
|
+
"react-doctor/no-adjust-state-on-prop-change",
|
|
33730
|
+
"react-doctor/no-reset-all-state-on-prop-change"
|
|
33731
|
+
]);
|
|
33723
33732
|
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
33724
33733
|
const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
33725
33734
|
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
@@ -34297,115 +34306,6 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34297
34306
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34298
34307
|
};
|
|
34299
34308
|
};
|
|
34300
|
-
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34301
|
-
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34302
|
-
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
34303
|
-
let stringDelimiter = null;
|
|
34304
|
-
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
34305
|
-
const character = line[charIndex];
|
|
34306
|
-
if (stringDelimiter !== null) {
|
|
34307
|
-
if (character === "\\") {
|
|
34308
|
-
charIndex++;
|
|
34309
|
-
continue;
|
|
34310
|
-
}
|
|
34311
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
34312
|
-
continue;
|
|
34313
|
-
}
|
|
34314
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
34315
|
-
stringDelimiter = character;
|
|
34316
|
-
continue;
|
|
34317
|
-
}
|
|
34318
|
-
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
34319
|
-
}
|
|
34320
|
-
return false;
|
|
34321
|
-
};
|
|
34322
|
-
const findOpenerTagOnLine = (line) => {
|
|
34323
|
-
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
34324
|
-
if (match.index === void 0) continue;
|
|
34325
|
-
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
34326
|
-
}
|
|
34327
|
-
return null;
|
|
34328
|
-
};
|
|
34329
|
-
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
34330
|
-
const openerLine = lines[openerLineIndex];
|
|
34331
|
-
if (openerLine === void 0) return null;
|
|
34332
|
-
const opener = findOpenerTagOnLine(openerLine);
|
|
34333
|
-
if (!opener) return null;
|
|
34334
|
-
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
34335
|
-
let braceDepth = 0;
|
|
34336
|
-
let innerAngleDepth = 0;
|
|
34337
|
-
let stringDelimiter = null;
|
|
34338
|
-
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
34339
|
-
const currentLine = lines[lineIndex];
|
|
34340
|
-
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
34341
|
-
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
34342
|
-
const character = currentLine[charIndex];
|
|
34343
|
-
if (stringDelimiter !== null) {
|
|
34344
|
-
if (character === "\\") {
|
|
34345
|
-
charIndex++;
|
|
34346
|
-
continue;
|
|
34347
|
-
}
|
|
34348
|
-
if (character === stringDelimiter) stringDelimiter = null;
|
|
34349
|
-
continue;
|
|
34350
|
-
}
|
|
34351
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
34352
|
-
stringDelimiter = character;
|
|
34353
|
-
continue;
|
|
34354
|
-
}
|
|
34355
|
-
if (character === "{") {
|
|
34356
|
-
braceDepth++;
|
|
34357
|
-
continue;
|
|
34358
|
-
}
|
|
34359
|
-
if (character === "}") {
|
|
34360
|
-
braceDepth--;
|
|
34361
|
-
continue;
|
|
34362
|
-
}
|
|
34363
|
-
if (braceDepth !== 0) continue;
|
|
34364
|
-
if (character === "<") {
|
|
34365
|
-
const followCharacter = currentLine[charIndex + 1];
|
|
34366
|
-
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
34367
|
-
continue;
|
|
34368
|
-
}
|
|
34369
|
-
if (character !== ">") continue;
|
|
34370
|
-
const previousCharacter = currentLine[charIndex - 1];
|
|
34371
|
-
const nextCharacter = currentLine[charIndex + 1];
|
|
34372
|
-
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
34373
|
-
if (innerAngleDepth > 0) {
|
|
34374
|
-
innerAngleDepth--;
|
|
34375
|
-
continue;
|
|
34376
|
-
}
|
|
34377
|
-
return lineIndex;
|
|
34378
|
-
}
|
|
34379
|
-
}
|
|
34380
|
-
return null;
|
|
34381
|
-
};
|
|
34382
|
-
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
34383
|
-
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
34384
|
-
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
34385
|
-
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
34386
|
-
}
|
|
34387
|
-
return null;
|
|
34388
|
-
};
|
|
34389
|
-
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34390
|
-
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
34391
|
-
const collected = [];
|
|
34392
|
-
let isStillInChain = true;
|
|
34393
|
-
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
34394
|
-
const candidateLine = lines[candidateIndex];
|
|
34395
|
-
if (candidateLine === void 0) break;
|
|
34396
|
-
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
34397
|
-
if (match) {
|
|
34398
|
-
collected.push({
|
|
34399
|
-
commentLineIndex: candidateIndex,
|
|
34400
|
-
ruleList: match[1],
|
|
34401
|
-
isInChain: isStillInChain
|
|
34402
|
-
});
|
|
34403
|
-
continue;
|
|
34404
|
-
}
|
|
34405
|
-
isStillInChain = false;
|
|
34406
|
-
}
|
|
34407
|
-
return collected;
|
|
34408
|
-
};
|
|
34409
34309
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34410
34310
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34411
34311
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34530,7 +34430,13 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
|
|
|
34530
34430
|
}
|
|
34531
34431
|
const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
|
|
34532
34432
|
const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
|
|
34533
|
-
const
|
|
34433
|
+
const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
|
|
34434
|
+
const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
|
|
34435
|
+
const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
|
|
34436
|
+
const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
|
|
34437
|
+
if (canonicalCandidate === canonicalTarget) return true;
|
|
34438
|
+
return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
|
|
34439
|
+
};
|
|
34534
34440
|
const getEquivalentRuleKeys = (ruleKey) => {
|
|
34535
34441
|
const nativeRuleKey = canonicalizeRuleKey(ruleKey);
|
|
34536
34442
|
return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
|
|
@@ -34540,12 +34446,182 @@ const stripDescriptionTail = (ruleList) => {
|
|
|
34540
34446
|
if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
|
|
34541
34447
|
return ruleList.slice(0, descriptionMatch.index);
|
|
34542
34448
|
};
|
|
34543
|
-
const
|
|
34449
|
+
const tokenizeRuleList = (ruleList) => {
|
|
34544
34450
|
const trimmed = ruleList?.trim();
|
|
34545
|
-
if (!trimmed) return
|
|
34451
|
+
if (!trimmed) return [];
|
|
34546
34452
|
const ruleSection = stripDescriptionTail(trimmed).trim();
|
|
34547
|
-
if (!ruleSection) return
|
|
34548
|
-
return ruleSection.split(/[,\s]+/).
|
|
34453
|
+
if (!ruleSection) return [];
|
|
34454
|
+
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34455
|
+
};
|
|
34456
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34457
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34458
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34459
|
+
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}\`.`;
|
|
34460
|
+
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34461
|
+
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34462
|
+
const candidates = [{
|
|
34463
|
+
line: lines[diagnosticLineIndex],
|
|
34464
|
+
requiredScope: "line"
|
|
34465
|
+
}, {
|
|
34466
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34467
|
+
requiredScope: "next-line"
|
|
34468
|
+
}];
|
|
34469
|
+
for (const { line, requiredScope } of candidates) {
|
|
34470
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34471
|
+
if (!match) continue;
|
|
34472
|
+
const [, tool, scope, ruleList] = match;
|
|
34473
|
+
if (scope !== requiredScope) continue;
|
|
34474
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34475
|
+
if (tokens.includes(ruleId)) continue;
|
|
34476
|
+
for (const token of tokens) if (tokenMisnamesRule(token, ruleId)) return buildHint(tool, token, ruleId);
|
|
34477
|
+
}
|
|
34478
|
+
return null;
|
|
34479
|
+
};
|
|
34480
|
+
const detectBlockNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34481
|
+
let openMisname = null;
|
|
34482
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34483
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34484
|
+
const line = lines[lineIndex];
|
|
34485
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34486
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34487
|
+
if (disableMatch) {
|
|
34488
|
+
const [, tool, ruleList] = disableMatch;
|
|
34489
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34490
|
+
if (tokens.includes(ruleId)) openMisname = null;
|
|
34491
|
+
else {
|
|
34492
|
+
const misnamed = tokens.find((token) => tokenMisnamesRule(token, ruleId));
|
|
34493
|
+
if (misnamed) openMisname = {
|
|
34494
|
+
tool,
|
|
34495
|
+
token: misnamed
|
|
34496
|
+
};
|
|
34497
|
+
}
|
|
34498
|
+
continue;
|
|
34499
|
+
}
|
|
34500
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34501
|
+
if (enableMatch) {
|
|
34502
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34503
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => isSameRuleKey(rule, ruleId))) openMisname = null;
|
|
34504
|
+
}
|
|
34505
|
+
}
|
|
34506
|
+
return openMisname ? buildHint(openMisname.tool, openMisname.token, ruleId) : null;
|
|
34507
|
+
};
|
|
34508
|
+
const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34509
|
+
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34510
|
+
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34511
|
+
};
|
|
34512
|
+
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34513
|
+
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34514
|
+
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
34515
|
+
let stringDelimiter = null;
|
|
34516
|
+
for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
|
|
34517
|
+
const character = line[charIndex];
|
|
34518
|
+
if (stringDelimiter !== null) {
|
|
34519
|
+
if (character === "\\") {
|
|
34520
|
+
charIndex++;
|
|
34521
|
+
continue;
|
|
34522
|
+
}
|
|
34523
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
34524
|
+
continue;
|
|
34525
|
+
}
|
|
34526
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
34527
|
+
stringDelimiter = character;
|
|
34528
|
+
continue;
|
|
34529
|
+
}
|
|
34530
|
+
if (character === "/" && line[charIndex + 1] === "/") return true;
|
|
34531
|
+
}
|
|
34532
|
+
return false;
|
|
34533
|
+
};
|
|
34534
|
+
const findOpenerTagOnLine = (line) => {
|
|
34535
|
+
for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
|
|
34536
|
+
if (match.index === void 0) continue;
|
|
34537
|
+
if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
|
|
34538
|
+
}
|
|
34539
|
+
return null;
|
|
34540
|
+
};
|
|
34541
|
+
const findJsxOpenerSpan = (lines, openerLineIndex) => {
|
|
34542
|
+
const openerLine = lines[openerLineIndex];
|
|
34543
|
+
if (openerLine === void 0) return null;
|
|
34544
|
+
const opener = findOpenerTagOnLine(openerLine);
|
|
34545
|
+
if (!opener) return null;
|
|
34546
|
+
const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
|
|
34547
|
+
let braceDepth = 0;
|
|
34548
|
+
let innerAngleDepth = 0;
|
|
34549
|
+
let stringDelimiter = null;
|
|
34550
|
+
for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
|
|
34551
|
+
const currentLine = lines[lineIndex];
|
|
34552
|
+
const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
|
|
34553
|
+
for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
|
|
34554
|
+
const character = currentLine[charIndex];
|
|
34555
|
+
if (stringDelimiter !== null) {
|
|
34556
|
+
if (character === "\\") {
|
|
34557
|
+
charIndex++;
|
|
34558
|
+
continue;
|
|
34559
|
+
}
|
|
34560
|
+
if (character === stringDelimiter) stringDelimiter = null;
|
|
34561
|
+
continue;
|
|
34562
|
+
}
|
|
34563
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
34564
|
+
stringDelimiter = character;
|
|
34565
|
+
continue;
|
|
34566
|
+
}
|
|
34567
|
+
if (character === "{") {
|
|
34568
|
+
braceDepth++;
|
|
34569
|
+
continue;
|
|
34570
|
+
}
|
|
34571
|
+
if (character === "}") {
|
|
34572
|
+
braceDepth--;
|
|
34573
|
+
continue;
|
|
34574
|
+
}
|
|
34575
|
+
if (braceDepth !== 0) continue;
|
|
34576
|
+
if (character === "<") {
|
|
34577
|
+
const followCharacter = currentLine[charIndex + 1];
|
|
34578
|
+
if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
|
|
34579
|
+
continue;
|
|
34580
|
+
}
|
|
34581
|
+
if (character !== ">") continue;
|
|
34582
|
+
const previousCharacter = currentLine[charIndex - 1];
|
|
34583
|
+
const nextCharacter = currentLine[charIndex + 1];
|
|
34584
|
+
if (previousCharacter === "=" || nextCharacter === "=") continue;
|
|
34585
|
+
if (innerAngleDepth > 0) {
|
|
34586
|
+
innerAngleDepth--;
|
|
34587
|
+
continue;
|
|
34588
|
+
}
|
|
34589
|
+
return lineIndex;
|
|
34590
|
+
}
|
|
34591
|
+
}
|
|
34592
|
+
return null;
|
|
34593
|
+
};
|
|
34594
|
+
const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
|
|
34595
|
+
for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
|
|
34596
|
+
const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
|
|
34597
|
+
if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
|
|
34598
|
+
}
|
|
34599
|
+
return null;
|
|
34600
|
+
};
|
|
34601
|
+
const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34602
|
+
const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
|
|
34603
|
+
const collected = [];
|
|
34604
|
+
let isStillInChain = true;
|
|
34605
|
+
for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
|
|
34606
|
+
const candidateLine = lines[candidateIndex];
|
|
34607
|
+
if (candidateLine === void 0) break;
|
|
34608
|
+
const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
|
|
34609
|
+
if (match) {
|
|
34610
|
+
collected.push({
|
|
34611
|
+
commentLineIndex: candidateIndex,
|
|
34612
|
+
ruleList: match[1],
|
|
34613
|
+
isInChain: isStillInChain
|
|
34614
|
+
});
|
|
34615
|
+
continue;
|
|
34616
|
+
}
|
|
34617
|
+
isStillInChain = false;
|
|
34618
|
+
}
|
|
34619
|
+
return collected;
|
|
34620
|
+
};
|
|
34621
|
+
const isRuleListedInComment = (ruleList, ruleId) => {
|
|
34622
|
+
const tokens = tokenizeRuleList(ruleList);
|
|
34623
|
+
if (tokens.length === 0) return true;
|
|
34624
|
+
return tokens.some((token) => isSameRuleKey(token, ruleId));
|
|
34549
34625
|
};
|
|
34550
34626
|
const DISABLE_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
|
|
34551
34627
|
const formatLineGap = (gapLineCount) => `${gapLineCount} line${gapLineCount === 1 ? "" : "s"}`;
|
|
@@ -34589,7 +34665,7 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34589
34665
|
};
|
|
34590
34666
|
return {
|
|
34591
34667
|
isSuppressed: false,
|
|
34592
|
-
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId)
|
|
34668
|
+
nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId) ?? detectForeignDisableNearMiss(lines, diagnosticLineIndex, ruleId)
|
|
34593
34669
|
};
|
|
34594
34670
|
};
|
|
34595
34671
|
/**
|
|
@@ -35518,6 +35594,29 @@ const resolveConfigRootDir = (config, configSourceDirectory) => {
|
|
|
35518
35594
|
}
|
|
35519
35595
|
return resolvedRootDir;
|
|
35520
35596
|
};
|
|
35597
|
+
const buildFixGroupId = (diagnostic) => createHash("sha1").update(JSON.stringify([
|
|
35598
|
+
diagnostic.filePath,
|
|
35599
|
+
`${diagnostic.plugin}/${diagnostic.rule}`,
|
|
35600
|
+
diagnostic.message
|
|
35601
|
+
])).digest("hex").slice(0, 16);
|
|
35602
|
+
const isGroupableRule = (diagnostic) => ROOT_CAUSE_GROUPABLE_RULE_KEYS.has(`${diagnostic.plugin}/${diagnostic.rule}`);
|
|
35603
|
+
const assignFixGroups = (diagnostics) => {
|
|
35604
|
+
const siteCountByGroupId = /* @__PURE__ */ new Map();
|
|
35605
|
+
for (const diagnostic of diagnostics) {
|
|
35606
|
+
if (!isGroupableRule(diagnostic)) continue;
|
|
35607
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
35608
|
+
siteCountByGroupId.set(groupId, (siteCountByGroupId.get(groupId) ?? 0) + 1);
|
|
35609
|
+
}
|
|
35610
|
+
return diagnostics.map((diagnostic) => {
|
|
35611
|
+
if (!isGroupableRule(diagnostic)) return diagnostic;
|
|
35612
|
+
const groupId = buildFixGroupId(diagnostic);
|
|
35613
|
+
if ((siteCountByGroupId.get(groupId) ?? 0) < 2) return diagnostic;
|
|
35614
|
+
return {
|
|
35615
|
+
...diagnostic,
|
|
35616
|
+
fixGroupId: groupId
|
|
35617
|
+
};
|
|
35618
|
+
});
|
|
35619
|
+
};
|
|
35521
35620
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
35522
35621
|
const buildExpoCheckContext = (rootDirectory, expoVersion) => {
|
|
35523
35622
|
const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
|
|
@@ -39964,12 +40063,12 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
39964
40063
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
39965
40064
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
39966
40065
|
yield* reporterService.finalize;
|
|
39967
|
-
const finalDiagnostics = [
|
|
40066
|
+
const finalDiagnostics = assignFixGroups([
|
|
39968
40067
|
...envCollected,
|
|
39969
40068
|
...supplyChainCollected,
|
|
39970
40069
|
...lintCollected,
|
|
39971
40070
|
...deadCodeCollected
|
|
39972
|
-
];
|
|
40071
|
+
]);
|
|
39973
40072
|
const githubViewerPermission = yield* join(githubViewerPermissionFiber);
|
|
39974
40073
|
const scoreMetadata = {
|
|
39975
40074
|
...repo !== null ? { repo } : {},
|
|
@@ -42377,5 +42476,5 @@ const startLanguageServer = () => {
|
|
|
42377
42476
|
};
|
|
42378
42477
|
//#endregion
|
|
42379
42478
|
export { startLanguageServer };
|
|
42380
|
-
!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]="
|
|
42381
|
-
//# debugId=
|
|
42479
|
+
!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]="6fbf847f-43b8-5c5c-ba97-c26c2e08e250")}catch(e){}}();
|
|
42480
|
+
//# debugId=6fbf847f-43b8-5c5c-ba97-c26c2e08e250
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.5.6-dev.
|
|
3
|
+
"version": "0.5.6-dev.66133f8",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"vscode-languageserver": "^9.0.1",
|
|
65
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
|
-
"oxlint-plugin-react-doctor": "0.5.6-dev.
|
|
67
|
+
"oxlint-plugin-react-doctor": "0.5.6-dev.66133f8"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"commander": "^14.0.3",
|
|
74
74
|
"ora": "^9.4.0",
|
|
75
75
|
"@react-doctor/api": "0.5.6",
|
|
76
|
-
"@react-doctor/
|
|
77
|
-
"@react-doctor/
|
|
76
|
+
"@react-doctor/core": "0.5.6",
|
|
77
|
+
"@react-doctor/language-server": "0.5.6"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
80
80
|
"node": "^20.19.0 || >=22.13.0"
|