react-doctor 0.5.6-dev.44db3e0 → 0.5.6-dev.4e06b2a

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.
Files changed (4) hide show
  1. package/dist/cli.js +120 -187
  2. package/dist/index.js +117 -184
  3. package/dist/lsp.js +117 -184
  4. 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]="e076a347-15ab-55bb-b11a-a813bb818760")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f029f05b-4c71-52d3-b523-0834d67de2d4")}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";
@@ -37434,6 +37434,115 @@ const buildRuleSeverityControls = (config) => {
37434
37434
  ...config.buckets !== void 0 ? { buckets: config.buckets } : {}
37435
37435
  };
37436
37436
  };
37437
+ const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
37438
+ const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
37439
+ const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
37440
+ let stringDelimiter = null;
37441
+ for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
37442
+ const character = line[charIndex];
37443
+ if (stringDelimiter !== null) {
37444
+ if (character === "\\") {
37445
+ charIndex++;
37446
+ continue;
37447
+ }
37448
+ if (character === stringDelimiter) stringDelimiter = null;
37449
+ continue;
37450
+ }
37451
+ if (character === "\"" || character === "'" || character === "`") {
37452
+ stringDelimiter = character;
37453
+ continue;
37454
+ }
37455
+ if (character === "/" && line[charIndex + 1] === "/") return true;
37456
+ }
37457
+ return false;
37458
+ };
37459
+ const findOpenerTagOnLine = (line) => {
37460
+ for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
37461
+ if (match.index === void 0) continue;
37462
+ if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
37463
+ }
37464
+ return null;
37465
+ };
37466
+ const findJsxOpenerSpan = (lines, openerLineIndex) => {
37467
+ const openerLine = lines[openerLineIndex];
37468
+ if (openerLine === void 0) return null;
37469
+ const opener = findOpenerTagOnLine(openerLine);
37470
+ if (!opener) return null;
37471
+ const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
37472
+ let braceDepth = 0;
37473
+ let innerAngleDepth = 0;
37474
+ let stringDelimiter = null;
37475
+ for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
37476
+ const currentLine = lines[lineIndex];
37477
+ const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
37478
+ for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
37479
+ const character = currentLine[charIndex];
37480
+ if (stringDelimiter !== null) {
37481
+ if (character === "\\") {
37482
+ charIndex++;
37483
+ continue;
37484
+ }
37485
+ if (character === stringDelimiter) stringDelimiter = null;
37486
+ continue;
37487
+ }
37488
+ if (character === "\"" || character === "'" || character === "`") {
37489
+ stringDelimiter = character;
37490
+ continue;
37491
+ }
37492
+ if (character === "{") {
37493
+ braceDepth++;
37494
+ continue;
37495
+ }
37496
+ if (character === "}") {
37497
+ braceDepth--;
37498
+ continue;
37499
+ }
37500
+ if (braceDepth !== 0) continue;
37501
+ if (character === "<") {
37502
+ const followCharacter = currentLine[charIndex + 1];
37503
+ if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
37504
+ continue;
37505
+ }
37506
+ if (character !== ">") continue;
37507
+ const previousCharacter = currentLine[charIndex - 1];
37508
+ const nextCharacter = currentLine[charIndex + 1];
37509
+ if (previousCharacter === "=" || nextCharacter === "=") continue;
37510
+ if (innerAngleDepth > 0) {
37511
+ innerAngleDepth--;
37512
+ continue;
37513
+ }
37514
+ return lineIndex;
37515
+ }
37516
+ }
37517
+ return null;
37518
+ };
37519
+ const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
37520
+ for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
37521
+ const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
37522
+ if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
37523
+ }
37524
+ return null;
37525
+ };
37526
+ const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
37527
+ const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
37528
+ const collected = [];
37529
+ let isStillInChain = true;
37530
+ for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
37531
+ const candidateLine = lines[candidateIndex];
37532
+ if (candidateLine === void 0) break;
37533
+ const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
37534
+ if (match) {
37535
+ collected.push({
37536
+ commentLineIndex: candidateIndex,
37537
+ ruleList: match[1],
37538
+ isInChain: isStillInChain
37539
+ });
37540
+ continue;
37541
+ }
37542
+ isStillInChain = false;
37543
+ }
37544
+ return collected;
37545
+ };
37437
37546
  const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
37438
37547
  "effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
37439
37548
  "effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
@@ -37558,13 +37667,7 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
37558
37667
  }
37559
37668
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
37560
37669
  const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
37561
- const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
37562
- const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
37563
- const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
37564
- const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
37565
- if (canonicalCandidate === canonicalTarget) return true;
37566
- return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
37567
- };
37670
+ const isSameRuleKey = (candidateRuleKey, targetRuleKey) => canonicalizeRuleKey(candidateRuleKey) === canonicalizeRuleKey(targetRuleKey);
37568
37671
  const getEquivalentRuleKeys = (ruleKey) => {
37569
37672
  const nativeRuleKey = canonicalizeRuleKey(ruleKey);
37570
37673
  return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
@@ -37574,182 +37677,12 @@ const stripDescriptionTail = (ruleList) => {
37574
37677
  if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
37575
37678
  return ruleList.slice(0, descriptionMatch.index);
37576
37679
  };
37577
- const tokenizeRuleList = (ruleList) => {
37680
+ const isRuleListedInComment = (ruleList, ruleId) => {
37578
37681
  const trimmed = ruleList?.trim();
37579
- if (!trimmed) return [];
37682
+ if (!trimmed) return true;
37580
37683
  const ruleSection = stripDescriptionTail(trimmed).trim();
37581
- if (!ruleSection) return [];
37582
- return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
37583
- };
37584
- const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
37585
- const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
37586
- const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
37587
- 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}\`.`;
37588
- const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
37589
- const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
37590
- const candidates = [{
37591
- line: lines[diagnosticLineIndex],
37592
- requiredScope: "line"
37593
- }, {
37594
- line: lines[diagnosticLineIndex - 1],
37595
- requiredScope: "next-line"
37596
- }];
37597
- for (const { line, requiredScope } of candidates) {
37598
- const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
37599
- if (!match) continue;
37600
- const [, tool, scope, ruleList] = match;
37601
- if (scope !== requiredScope) continue;
37602
- const tokens = tokenizeRuleList(ruleList);
37603
- if (tokens.includes(ruleId)) continue;
37604
- for (const token of tokens) if (tokenMisnamesRule(token, ruleId)) return buildHint(tool, token, ruleId);
37605
- }
37606
- return null;
37607
- };
37608
- const detectBlockNearMiss = (lines, diagnosticLineIndex, ruleId) => {
37609
- let openMisname = null;
37610
- const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
37611
- for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
37612
- const line = lines[lineIndex];
37613
- if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
37614
- const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
37615
- if (disableMatch) {
37616
- const [, tool, ruleList] = disableMatch;
37617
- const tokens = tokenizeRuleList(ruleList);
37618
- if (tokens.includes(ruleId)) openMisname = null;
37619
- else {
37620
- const misnamed = tokens.find((token) => tokenMisnamesRule(token, ruleId));
37621
- if (misnamed) openMisname = {
37622
- tool,
37623
- token: misnamed
37624
- };
37625
- }
37626
- continue;
37627
- }
37628
- const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
37629
- if (enableMatch) {
37630
- const enabledRules = tokenizeRuleList(enableMatch[1]);
37631
- if (enabledRules.length === 0 || enabledRules.some((rule) => isSameRuleKey(rule, ruleId))) openMisname = null;
37632
- }
37633
- }
37634
- return openMisname ? buildHint(openMisname.tool, openMisname.token, ruleId) : null;
37635
- };
37636
- const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
37637
- if (!ruleId.startsWith("react-doctor/")) return null;
37638
- return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
37639
- };
37640
- const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
37641
- const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
37642
- const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
37643
- let stringDelimiter = null;
37644
- for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
37645
- const character = line[charIndex];
37646
- if (stringDelimiter !== null) {
37647
- if (character === "\\") {
37648
- charIndex++;
37649
- continue;
37650
- }
37651
- if (character === stringDelimiter) stringDelimiter = null;
37652
- continue;
37653
- }
37654
- if (character === "\"" || character === "'" || character === "`") {
37655
- stringDelimiter = character;
37656
- continue;
37657
- }
37658
- if (character === "/" && line[charIndex + 1] === "/") return true;
37659
- }
37660
- return false;
37661
- };
37662
- const findOpenerTagOnLine = (line) => {
37663
- for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
37664
- if (match.index === void 0) continue;
37665
- if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
37666
- }
37667
- return null;
37668
- };
37669
- const findJsxOpenerSpan = (lines, openerLineIndex) => {
37670
- const openerLine = lines[openerLineIndex];
37671
- if (openerLine === void 0) return null;
37672
- const opener = findOpenerTagOnLine(openerLine);
37673
- if (!opener) return null;
37674
- const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
37675
- let braceDepth = 0;
37676
- let innerAngleDepth = 0;
37677
- let stringDelimiter = null;
37678
- for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
37679
- const currentLine = lines[lineIndex];
37680
- const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
37681
- for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
37682
- const character = currentLine[charIndex];
37683
- if (stringDelimiter !== null) {
37684
- if (character === "\\") {
37685
- charIndex++;
37686
- continue;
37687
- }
37688
- if (character === stringDelimiter) stringDelimiter = null;
37689
- continue;
37690
- }
37691
- if (character === "\"" || character === "'" || character === "`") {
37692
- stringDelimiter = character;
37693
- continue;
37694
- }
37695
- if (character === "{") {
37696
- braceDepth++;
37697
- continue;
37698
- }
37699
- if (character === "}") {
37700
- braceDepth--;
37701
- continue;
37702
- }
37703
- if (braceDepth !== 0) continue;
37704
- if (character === "<") {
37705
- const followCharacter = currentLine[charIndex + 1];
37706
- if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
37707
- continue;
37708
- }
37709
- if (character !== ">") continue;
37710
- const previousCharacter = currentLine[charIndex - 1];
37711
- const nextCharacter = currentLine[charIndex + 1];
37712
- if (previousCharacter === "=" || nextCharacter === "=") continue;
37713
- if (innerAngleDepth > 0) {
37714
- innerAngleDepth--;
37715
- continue;
37716
- }
37717
- return lineIndex;
37718
- }
37719
- }
37720
- return null;
37721
- };
37722
- const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
37723
- for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
37724
- const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
37725
- if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
37726
- }
37727
- return null;
37728
- };
37729
- const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
37730
- const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
37731
- const collected = [];
37732
- let isStillInChain = true;
37733
- for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
37734
- const candidateLine = lines[candidateIndex];
37735
- if (candidateLine === void 0) break;
37736
- const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
37737
- if (match) {
37738
- collected.push({
37739
- commentLineIndex: candidateIndex,
37740
- ruleList: match[1],
37741
- isInChain: isStillInChain
37742
- });
37743
- continue;
37744
- }
37745
- isStillInChain = false;
37746
- }
37747
- return collected;
37748
- };
37749
- const isRuleListedInComment = (ruleList, ruleId) => {
37750
- const tokens = tokenizeRuleList(ruleList);
37751
- if (tokens.length === 0) return true;
37752
- return tokens.some((token) => isSameRuleKey(token, ruleId));
37684
+ if (!ruleSection) return true;
37685
+ return ruleSection.split(/[,\s]+/).some((token) => isSameRuleKey(token.trim(), ruleId));
37753
37686
  };
37754
37687
  const DISABLE_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
37755
37688
  const formatLineGap = (gapLineCount) => `${gapLineCount} line${gapLineCount === 1 ? "" : "s"}`;
@@ -37793,7 +37726,7 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
37793
37726
  };
37794
37727
  return {
37795
37728
  isSuppressed: false,
37796
- nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId) ?? detectForeignDisableNearMiss(lines, diagnosticLineIndex, ruleId)
37729
+ nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId)
37797
37730
  };
37798
37731
  };
37799
37732
  /**
@@ -44111,7 +44044,7 @@ const makeNoopConsole = () => ({
44111
44044
  });
44112
44045
  //#endregion
44113
44046
  //#region src/cli/utils/version.ts
44114
- const VERSION = "0.5.6-dev.44db3e0";
44047
+ const VERSION = "0.5.6-dev.4e06b2a";
44115
44048
  //#endregion
44116
44049
  //#region src/cli/utils/json-mode.ts
44117
44050
  let context = null;
@@ -44473,13 +44406,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
44473
44406
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
44474
44407
  * standard `SENTRY_RELEASE` override.
44475
44408
  */
44476
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.44db3e0`;
44409
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.6-dev.4e06b2a`;
44477
44410
  /**
44478
44411
  * Deployment environment shown in Sentry's environment filter. Defaults to
44479
44412
  * `production` for tagged releases and `development` for dev/unbuilt versions,
44480
44413
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
44481
44414
  */
44482
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.44db3e0") ? "development" : "production");
44415
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.6-dev.4e06b2a") ? "development" : "production");
44483
44416
  /**
44484
44417
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
44485
44418
  * (set to `0` to disable tracing) and falls back to
@@ -54223,4 +54156,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
54223
54156
  export {};
54224
54157
 
54225
54158
  //# sourceMappingURL=cli.js.map
54226
- //# debugId=e076a347-15ab-55bb-b11a-a813bb818760
54159
+ //# debugId=f029f05b-4c71-52d3-b523-0834d67de2d4
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]="24b11cb3-4e8c-544c-bacf-6393dd5abd59")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="efaa91ed-3659-50a5-b802-51753472b2d6")}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";
@@ -34238,6 +34238,115 @@ const buildRuleSeverityControls = (config) => {
34238
34238
  ...config.buckets !== void 0 ? { buckets: config.buckets } : {}
34239
34239
  };
34240
34240
  };
34241
+ const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
34242
+ const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
34243
+ const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
34244
+ let stringDelimiter = null;
34245
+ for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
34246
+ const character = line[charIndex];
34247
+ if (stringDelimiter !== null) {
34248
+ if (character === "\\") {
34249
+ charIndex++;
34250
+ continue;
34251
+ }
34252
+ if (character === stringDelimiter) stringDelimiter = null;
34253
+ continue;
34254
+ }
34255
+ if (character === "\"" || character === "'" || character === "`") {
34256
+ stringDelimiter = character;
34257
+ continue;
34258
+ }
34259
+ if (character === "/" && line[charIndex + 1] === "/") return true;
34260
+ }
34261
+ return false;
34262
+ };
34263
+ const findOpenerTagOnLine = (line) => {
34264
+ for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
34265
+ if (match.index === void 0) continue;
34266
+ if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
34267
+ }
34268
+ return null;
34269
+ };
34270
+ const findJsxOpenerSpan = (lines, openerLineIndex) => {
34271
+ const openerLine = lines[openerLineIndex];
34272
+ if (openerLine === void 0) return null;
34273
+ const opener = findOpenerTagOnLine(openerLine);
34274
+ if (!opener) return null;
34275
+ const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
34276
+ let braceDepth = 0;
34277
+ let innerAngleDepth = 0;
34278
+ let stringDelimiter = null;
34279
+ for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
34280
+ const currentLine = lines[lineIndex];
34281
+ const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
34282
+ for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
34283
+ const character = currentLine[charIndex];
34284
+ if (stringDelimiter !== null) {
34285
+ if (character === "\\") {
34286
+ charIndex++;
34287
+ continue;
34288
+ }
34289
+ if (character === stringDelimiter) stringDelimiter = null;
34290
+ continue;
34291
+ }
34292
+ if (character === "\"" || character === "'" || character === "`") {
34293
+ stringDelimiter = character;
34294
+ continue;
34295
+ }
34296
+ if (character === "{") {
34297
+ braceDepth++;
34298
+ continue;
34299
+ }
34300
+ if (character === "}") {
34301
+ braceDepth--;
34302
+ continue;
34303
+ }
34304
+ if (braceDepth !== 0) continue;
34305
+ if (character === "<") {
34306
+ const followCharacter = currentLine[charIndex + 1];
34307
+ if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
34308
+ continue;
34309
+ }
34310
+ if (character !== ">") continue;
34311
+ const previousCharacter = currentLine[charIndex - 1];
34312
+ const nextCharacter = currentLine[charIndex + 1];
34313
+ if (previousCharacter === "=" || nextCharacter === "=") continue;
34314
+ if (innerAngleDepth > 0) {
34315
+ innerAngleDepth--;
34316
+ continue;
34317
+ }
34318
+ return lineIndex;
34319
+ }
34320
+ }
34321
+ return null;
34322
+ };
34323
+ const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
34324
+ for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
34325
+ const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
34326
+ if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
34327
+ }
34328
+ return null;
34329
+ };
34330
+ const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
34331
+ const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
34332
+ const collected = [];
34333
+ let isStillInChain = true;
34334
+ for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
34335
+ const candidateLine = lines[candidateIndex];
34336
+ if (candidateLine === void 0) break;
34337
+ const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
34338
+ if (match) {
34339
+ collected.push({
34340
+ commentLineIndex: candidateIndex,
34341
+ ruleList: match[1],
34342
+ isInChain: isStillInChain
34343
+ });
34344
+ continue;
34345
+ }
34346
+ isStillInChain = false;
34347
+ }
34348
+ return collected;
34349
+ };
34241
34350
  const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
34242
34351
  "effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
34243
34352
  "effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
@@ -34362,13 +34471,7 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
34362
34471
  }
34363
34472
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
34364
34473
  const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
34365
- const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
34366
- const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
34367
- const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
34368
- const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
34369
- if (canonicalCandidate === canonicalTarget) return true;
34370
- return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
34371
- };
34474
+ const isSameRuleKey = (candidateRuleKey, targetRuleKey) => canonicalizeRuleKey(candidateRuleKey) === canonicalizeRuleKey(targetRuleKey);
34372
34475
  const getEquivalentRuleKeys = (ruleKey) => {
34373
34476
  const nativeRuleKey = canonicalizeRuleKey(ruleKey);
34374
34477
  return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
@@ -34378,182 +34481,12 @@ const stripDescriptionTail = (ruleList) => {
34378
34481
  if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
34379
34482
  return ruleList.slice(0, descriptionMatch.index);
34380
34483
  };
34381
- const tokenizeRuleList = (ruleList) => {
34484
+ const isRuleListedInComment = (ruleList, ruleId) => {
34382
34485
  const trimmed = ruleList?.trim();
34383
- if (!trimmed) return [];
34486
+ if (!trimmed) return true;
34384
34487
  const ruleSection = stripDescriptionTail(trimmed).trim();
34385
- if (!ruleSection) return [];
34386
- return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
34387
- };
34388
- const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
34389
- const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
34390
- const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
34391
- 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}\`.`;
34392
- const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
34393
- const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34394
- const candidates = [{
34395
- line: lines[diagnosticLineIndex],
34396
- requiredScope: "line"
34397
- }, {
34398
- line: lines[diagnosticLineIndex - 1],
34399
- requiredScope: "next-line"
34400
- }];
34401
- for (const { line, requiredScope } of candidates) {
34402
- const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
34403
- if (!match) continue;
34404
- const [, tool, scope, ruleList] = match;
34405
- if (scope !== requiredScope) continue;
34406
- const tokens = tokenizeRuleList(ruleList);
34407
- if (tokens.includes(ruleId)) continue;
34408
- for (const token of tokens) if (tokenMisnamesRule(token, ruleId)) return buildHint(tool, token, ruleId);
34409
- }
34410
- return null;
34411
- };
34412
- const detectBlockNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34413
- let openMisname = null;
34414
- const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
34415
- for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
34416
- const line = lines[lineIndex];
34417
- if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
34418
- const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
34419
- if (disableMatch) {
34420
- const [, tool, ruleList] = disableMatch;
34421
- const tokens = tokenizeRuleList(ruleList);
34422
- if (tokens.includes(ruleId)) openMisname = null;
34423
- else {
34424
- const misnamed = tokens.find((token) => tokenMisnamesRule(token, ruleId));
34425
- if (misnamed) openMisname = {
34426
- tool,
34427
- token: misnamed
34428
- };
34429
- }
34430
- continue;
34431
- }
34432
- const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
34433
- if (enableMatch) {
34434
- const enabledRules = tokenizeRuleList(enableMatch[1]);
34435
- if (enabledRules.length === 0 || enabledRules.some((rule) => isSameRuleKey(rule, ruleId))) openMisname = null;
34436
- }
34437
- }
34438
- return openMisname ? buildHint(openMisname.tool, openMisname.token, ruleId) : null;
34439
- };
34440
- const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34441
- if (!ruleId.startsWith("react-doctor/")) return null;
34442
- return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
34443
- };
34444
- const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
34445
- const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
34446
- const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
34447
- let stringDelimiter = null;
34448
- for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
34449
- const character = line[charIndex];
34450
- if (stringDelimiter !== null) {
34451
- if (character === "\\") {
34452
- charIndex++;
34453
- continue;
34454
- }
34455
- if (character === stringDelimiter) stringDelimiter = null;
34456
- continue;
34457
- }
34458
- if (character === "\"" || character === "'" || character === "`") {
34459
- stringDelimiter = character;
34460
- continue;
34461
- }
34462
- if (character === "/" && line[charIndex + 1] === "/") return true;
34463
- }
34464
- return false;
34465
- };
34466
- const findOpenerTagOnLine = (line) => {
34467
- for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
34468
- if (match.index === void 0) continue;
34469
- if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
34470
- }
34471
- return null;
34472
- };
34473
- const findJsxOpenerSpan = (lines, openerLineIndex) => {
34474
- const openerLine = lines[openerLineIndex];
34475
- if (openerLine === void 0) return null;
34476
- const opener = findOpenerTagOnLine(openerLine);
34477
- if (!opener) return null;
34478
- const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
34479
- let braceDepth = 0;
34480
- let innerAngleDepth = 0;
34481
- let stringDelimiter = null;
34482
- for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
34483
- const currentLine = lines[lineIndex];
34484
- const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
34485
- for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
34486
- const character = currentLine[charIndex];
34487
- if (stringDelimiter !== null) {
34488
- if (character === "\\") {
34489
- charIndex++;
34490
- continue;
34491
- }
34492
- if (character === stringDelimiter) stringDelimiter = null;
34493
- continue;
34494
- }
34495
- if (character === "\"" || character === "'" || character === "`") {
34496
- stringDelimiter = character;
34497
- continue;
34498
- }
34499
- if (character === "{") {
34500
- braceDepth++;
34501
- continue;
34502
- }
34503
- if (character === "}") {
34504
- braceDepth--;
34505
- continue;
34506
- }
34507
- if (braceDepth !== 0) continue;
34508
- if (character === "<") {
34509
- const followCharacter = currentLine[charIndex + 1];
34510
- if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
34511
- continue;
34512
- }
34513
- if (character !== ">") continue;
34514
- const previousCharacter = currentLine[charIndex - 1];
34515
- const nextCharacter = currentLine[charIndex + 1];
34516
- if (previousCharacter === "=" || nextCharacter === "=") continue;
34517
- if (innerAngleDepth > 0) {
34518
- innerAngleDepth--;
34519
- continue;
34520
- }
34521
- return lineIndex;
34522
- }
34523
- }
34524
- return null;
34525
- };
34526
- const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
34527
- for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
34528
- const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
34529
- if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
34530
- }
34531
- return null;
34532
- };
34533
- const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
34534
- const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
34535
- const collected = [];
34536
- let isStillInChain = true;
34537
- for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
34538
- const candidateLine = lines[candidateIndex];
34539
- if (candidateLine === void 0) break;
34540
- const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
34541
- if (match) {
34542
- collected.push({
34543
- commentLineIndex: candidateIndex,
34544
- ruleList: match[1],
34545
- isInChain: isStillInChain
34546
- });
34547
- continue;
34548
- }
34549
- isStillInChain = false;
34550
- }
34551
- return collected;
34552
- };
34553
- const isRuleListedInComment = (ruleList, ruleId) => {
34554
- const tokens = tokenizeRuleList(ruleList);
34555
- if (tokens.length === 0) return true;
34556
- return tokens.some((token) => isSameRuleKey(token, ruleId));
34488
+ if (!ruleSection) return true;
34489
+ return ruleSection.split(/[,\s]+/).some((token) => isSameRuleKey(token.trim(), ruleId));
34557
34490
  };
34558
34491
  const DISABLE_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
34559
34492
  const formatLineGap = (gapLineCount) => `${gapLineCount} line${gapLineCount === 1 ? "" : "s"}`;
@@ -34597,7 +34530,7 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
34597
34530
  };
34598
34531
  return {
34599
34532
  isSuppressed: false,
34600
- nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId) ?? detectForeignDisableNearMiss(lines, diagnosticLineIndex, ruleId)
34533
+ nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId)
34601
34534
  };
34602
34535
  };
34603
34536
  /**
@@ -40664,4 +40597,4 @@ const toJsonReport = (result, options) => buildJsonReport({
40664
40597
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
40665
40598
 
40666
40599
  //# sourceMappingURL=index.js.map
40667
- //# debugId=24b11cb3-4e8c-544c-bacf-6393dd5abd59
40600
+ //# debugId=efaa91ed-3659-50a5-b802-51753472b2d6
package/dist/lsp.js CHANGED
@@ -34298,6 +34298,115 @@ const buildRuleSeverityControls = (config) => {
34298
34298
  ...config.buckets !== void 0 ? { buckets: config.buckets } : {}
34299
34299
  };
34300
34300
  };
34301
+ const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
34302
+ const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
34303
+ const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
34304
+ let stringDelimiter = null;
34305
+ for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
34306
+ const character = line[charIndex];
34307
+ if (stringDelimiter !== null) {
34308
+ if (character === "\\") {
34309
+ charIndex++;
34310
+ continue;
34311
+ }
34312
+ if (character === stringDelimiter) stringDelimiter = null;
34313
+ continue;
34314
+ }
34315
+ if (character === "\"" || character === "'" || character === "`") {
34316
+ stringDelimiter = character;
34317
+ continue;
34318
+ }
34319
+ if (character === "/" && line[charIndex + 1] === "/") return true;
34320
+ }
34321
+ return false;
34322
+ };
34323
+ const findOpenerTagOnLine = (line) => {
34324
+ for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
34325
+ if (match.index === void 0) continue;
34326
+ if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
34327
+ }
34328
+ return null;
34329
+ };
34330
+ const findJsxOpenerSpan = (lines, openerLineIndex) => {
34331
+ const openerLine = lines[openerLineIndex];
34332
+ if (openerLine === void 0) return null;
34333
+ const opener = findOpenerTagOnLine(openerLine);
34334
+ if (!opener) return null;
34335
+ const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
34336
+ let braceDepth = 0;
34337
+ let innerAngleDepth = 0;
34338
+ let stringDelimiter = null;
34339
+ for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
34340
+ const currentLine = lines[lineIndex];
34341
+ const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
34342
+ for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
34343
+ const character = currentLine[charIndex];
34344
+ if (stringDelimiter !== null) {
34345
+ if (character === "\\") {
34346
+ charIndex++;
34347
+ continue;
34348
+ }
34349
+ if (character === stringDelimiter) stringDelimiter = null;
34350
+ continue;
34351
+ }
34352
+ if (character === "\"" || character === "'" || character === "`") {
34353
+ stringDelimiter = character;
34354
+ continue;
34355
+ }
34356
+ if (character === "{") {
34357
+ braceDepth++;
34358
+ continue;
34359
+ }
34360
+ if (character === "}") {
34361
+ braceDepth--;
34362
+ continue;
34363
+ }
34364
+ if (braceDepth !== 0) continue;
34365
+ if (character === "<") {
34366
+ const followCharacter = currentLine[charIndex + 1];
34367
+ if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
34368
+ continue;
34369
+ }
34370
+ if (character !== ">") continue;
34371
+ const previousCharacter = currentLine[charIndex - 1];
34372
+ const nextCharacter = currentLine[charIndex + 1];
34373
+ if (previousCharacter === "=" || nextCharacter === "=") continue;
34374
+ if (innerAngleDepth > 0) {
34375
+ innerAngleDepth--;
34376
+ continue;
34377
+ }
34378
+ return lineIndex;
34379
+ }
34380
+ }
34381
+ return null;
34382
+ };
34383
+ const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
34384
+ for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
34385
+ const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
34386
+ if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
34387
+ }
34388
+ return null;
34389
+ };
34390
+ const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
34391
+ const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
34392
+ const collected = [];
34393
+ let isStillInChain = true;
34394
+ for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
34395
+ const candidateLine = lines[candidateIndex];
34396
+ if (candidateLine === void 0) break;
34397
+ const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
34398
+ if (match) {
34399
+ collected.push({
34400
+ commentLineIndex: candidateIndex,
34401
+ ruleList: match[1],
34402
+ isInChain: isStillInChain
34403
+ });
34404
+ continue;
34405
+ }
34406
+ isStillInChain = false;
34407
+ }
34408
+ return collected;
34409
+ };
34301
34410
  const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
34302
34411
  "effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
34303
34412
  "effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
@@ -34422,13 +34531,7 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
34422
34531
  }
34423
34532
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
34424
34533
  const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
34425
- const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
34426
- const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
34427
- const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
34428
- const canonicalTarget = canonicalizeRuleKey(targetRuleKey);
34429
- if (canonicalCandidate === canonicalTarget) return true;
34430
- return isReactDoctorShortIdOf(canonicalCandidate, canonicalTarget) || isReactDoctorShortIdOf(canonicalTarget, canonicalCandidate);
34431
- };
34534
+ const isSameRuleKey = (candidateRuleKey, targetRuleKey) => canonicalizeRuleKey(candidateRuleKey) === canonicalizeRuleKey(targetRuleKey);
34432
34535
  const getEquivalentRuleKeys = (ruleKey) => {
34433
34536
  const nativeRuleKey = canonicalizeRuleKey(ruleKey);
34434
34537
  return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
@@ -34438,182 +34541,12 @@ const stripDescriptionTail = (ruleList) => {
34438
34541
  if (!descriptionMatch || descriptionMatch.index === void 0) return ruleList;
34439
34542
  return ruleList.slice(0, descriptionMatch.index);
34440
34543
  };
34441
- const tokenizeRuleList = (ruleList) => {
34544
+ const isRuleListedInComment = (ruleList, ruleId) => {
34442
34545
  const trimmed = ruleList?.trim();
34443
- if (!trimmed) return [];
34546
+ if (!trimmed) return true;
34444
34547
  const ruleSection = stripDescriptionTail(trimmed).trim();
34445
- if (!ruleSection) return [];
34446
- return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
34447
- };
34448
- const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
34449
- const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
34450
- const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
34451
- 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}\`.`;
34452
- const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
34453
- const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34454
- const candidates = [{
34455
- line: lines[diagnosticLineIndex],
34456
- requiredScope: "line"
34457
- }, {
34458
- line: lines[diagnosticLineIndex - 1],
34459
- requiredScope: "next-line"
34460
- }];
34461
- for (const { line, requiredScope } of candidates) {
34462
- const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
34463
- if (!match) continue;
34464
- const [, tool, scope, ruleList] = match;
34465
- if (scope !== requiredScope) continue;
34466
- const tokens = tokenizeRuleList(ruleList);
34467
- if (tokens.includes(ruleId)) continue;
34468
- for (const token of tokens) if (tokenMisnamesRule(token, ruleId)) return buildHint(tool, token, ruleId);
34469
- }
34470
- return null;
34471
- };
34472
- const detectBlockNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34473
- let openMisname = null;
34474
- const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
34475
- for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
34476
- const line = lines[lineIndex];
34477
- if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
34478
- const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
34479
- if (disableMatch) {
34480
- const [, tool, ruleList] = disableMatch;
34481
- const tokens = tokenizeRuleList(ruleList);
34482
- if (tokens.includes(ruleId)) openMisname = null;
34483
- else {
34484
- const misnamed = tokens.find((token) => tokenMisnamesRule(token, ruleId));
34485
- if (misnamed) openMisname = {
34486
- tool,
34487
- token: misnamed
34488
- };
34489
- }
34490
- continue;
34491
- }
34492
- const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
34493
- if (enableMatch) {
34494
- const enabledRules = tokenizeRuleList(enableMatch[1]);
34495
- if (enabledRules.length === 0 || enabledRules.some((rule) => isSameRuleKey(rule, ruleId))) openMisname = null;
34496
- }
34497
- }
34498
- return openMisname ? buildHint(openMisname.tool, openMisname.token, ruleId) : null;
34499
- };
34500
- const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34501
- if (!ruleId.startsWith("react-doctor/")) return null;
34502
- return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
34503
- };
34504
- const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
34505
- const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
34506
- const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
34507
- let stringDelimiter = null;
34508
- for (let charIndex = 0; charIndex < openerCharIndex; charIndex++) {
34509
- const character = line[charIndex];
34510
- if (stringDelimiter !== null) {
34511
- if (character === "\\") {
34512
- charIndex++;
34513
- continue;
34514
- }
34515
- if (character === stringDelimiter) stringDelimiter = null;
34516
- continue;
34517
- }
34518
- if (character === "\"" || character === "'" || character === "`") {
34519
- stringDelimiter = character;
34520
- continue;
34521
- }
34522
- if (character === "/" && line[charIndex + 1] === "/") return true;
34523
- }
34524
- return false;
34525
- };
34526
- const findOpenerTagOnLine = (line) => {
34527
- for (const match of line.matchAll(JSX_OPENER_TAG_PATTERN)) {
34528
- if (match.index === void 0) continue;
34529
- if (!isOpenerMatchInsideLineComment(line, match.index)) return { startCharIndex: match.index + match[0].length };
34530
- }
34531
- return null;
34532
- };
34533
- const findJsxOpenerSpan = (lines, openerLineIndex) => {
34534
- const openerLine = lines[openerLineIndex];
34535
- if (openerLine === void 0) return null;
34536
- const opener = findOpenerTagOnLine(openerLine);
34537
- if (!opener) return null;
34538
- const lookaheadLimit = Math.min(lines.length, openerLineIndex + 32);
34539
- let braceDepth = 0;
34540
- let innerAngleDepth = 0;
34541
- let stringDelimiter = null;
34542
- for (let lineIndex = openerLineIndex; lineIndex < lookaheadLimit; lineIndex++) {
34543
- const currentLine = lines[lineIndex];
34544
- const startCharForLine = lineIndex === openerLineIndex ? opener.startCharIndex : 0;
34545
- for (let charIndex = startCharForLine; charIndex < currentLine.length; charIndex++) {
34546
- const character = currentLine[charIndex];
34547
- if (stringDelimiter !== null) {
34548
- if (character === "\\") {
34549
- charIndex++;
34550
- continue;
34551
- }
34552
- if (character === stringDelimiter) stringDelimiter = null;
34553
- continue;
34554
- }
34555
- if (character === "\"" || character === "'" || character === "`") {
34556
- stringDelimiter = character;
34557
- continue;
34558
- }
34559
- if (character === "{") {
34560
- braceDepth++;
34561
- continue;
34562
- }
34563
- if (character === "}") {
34564
- braceDepth--;
34565
- continue;
34566
- }
34567
- if (braceDepth !== 0) continue;
34568
- if (character === "<") {
34569
- const followCharacter = currentLine[charIndex + 1];
34570
- if (followCharacter !== void 0 && JSX_TAG_NAME_FOLLOW.test(followCharacter)) innerAngleDepth++;
34571
- continue;
34572
- }
34573
- if (character !== ">") continue;
34574
- const previousCharacter = currentLine[charIndex - 1];
34575
- const nextCharacter = currentLine[charIndex + 1];
34576
- if (previousCharacter === "=" || nextCharacter === "=") continue;
34577
- if (innerAngleDepth > 0) {
34578
- innerAngleDepth--;
34579
- continue;
34580
- }
34581
- return lineIndex;
34582
- }
34583
- }
34584
- return null;
34585
- };
34586
- const findEnclosingMultilineJsxOpenerStart = (lines, diagnosticLineIndex) => {
34587
- for (let candidateIndex = diagnosticLineIndex - 1; candidateIndex >= 0 && diagnosticLineIndex - candidateIndex <= 32; candidateIndex--) {
34588
- const openerCloseIndex = findJsxOpenerSpan(lines, candidateIndex);
34589
- if (openerCloseIndex !== null && openerCloseIndex >= diagnosticLineIndex) return candidateIndex;
34590
- }
34591
- return null;
34592
- };
34593
- const DISABLE_NEXT_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-next-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
34594
- const findStackedDisableCommentsAbove = (lines, anchorIndex) => {
34595
- const collected = [];
34596
- let isStillInChain = true;
34597
- for (let candidateIndex = anchorIndex - 1; candidateIndex >= 0 && anchorIndex - candidateIndex <= 10; candidateIndex--) {
34598
- const candidateLine = lines[candidateIndex];
34599
- if (candidateLine === void 0) break;
34600
- const match = candidateLine.match(DISABLE_NEXT_LINE_PATTERN);
34601
- if (match) {
34602
- collected.push({
34603
- commentLineIndex: candidateIndex,
34604
- ruleList: match[1],
34605
- isInChain: isStillInChain
34606
- });
34607
- continue;
34608
- }
34609
- isStillInChain = false;
34610
- }
34611
- return collected;
34612
- };
34613
- const isRuleListedInComment = (ruleList, ruleId) => {
34614
- const tokens = tokenizeRuleList(ruleList);
34615
- if (tokens.length === 0) return true;
34616
- return tokens.some((token) => isSameRuleKey(token, ruleId));
34548
+ if (!ruleSection) return true;
34549
+ return ruleSection.split(/[,\s]+/).some((token) => isSameRuleKey(token.trim(), ruleId));
34617
34550
  };
34618
34551
  const DISABLE_LINE_PATTERN = /(?:\/\/|\/\*)\s*react-doctor-disable-line\b(?:\s+([^\r\n]*?))?\s*(?:\*\/)?\s*\}?\s*$/;
34619
34552
  const formatLineGap = (gapLineCount) => `${gapLineCount} line${gapLineCount === 1 ? "" : "s"}`;
@@ -34657,7 +34590,7 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
34657
34590
  };
34658
34591
  return {
34659
34592
  isSuppressed: false,
34660
- nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId) ?? detectForeignDisableNearMiss(lines, diagnosticLineIndex, ruleId)
34593
+ nearMissHint: classifyFromComments([directComments, openerComments], diagnosticLineIndex, ruleId)
34661
34594
  };
34662
34595
  };
34663
34596
  /**
@@ -42445,5 +42378,5 @@ const startLanguageServer = () => {
42445
42378
  };
42446
42379
  //#endregion
42447
42380
  export { startLanguageServer };
42448
- !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]="66ef8d70-3b06-5d78-ba3e-0907cc82425c")}catch(e){}}();
42449
- //# debugId=66ef8d70-3b06-5d78-ba3e-0907cc82425c
42381
+ !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]="657a7bbf-22e5-505b-a956-410727faec39")}catch(e){}}();
42382
+ //# debugId=657a7bbf-22e5-505b-a956-410727faec39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.6-dev.44db3e0",
3
+ "version": "0.5.6-dev.4e06b2a",
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.44db3e0"
67
+ "oxlint-plugin-react-doctor": "0.5.6-dev.4e06b2a"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/babel__code-frame": "^7.27.0",