oxlint-plugin-react-doctor 0.5.6-dev.424d8f9 → 0.5.6-dev.44db3e0

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 (3) hide show
  1. package/dist/index.d.ts +0 -505
  2. package/dist/index.js +225 -944
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1915,7 +1915,7 @@ const anchorAmbiguousText = defineRule({
1915
1915
  });
1916
1916
  //#endregion
1917
1917
  //#region src/plugin/rules/a11y/anchor-has-content.ts
1918
- const MESSAGE$64 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
1918
+ const MESSAGE$59 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
1919
1919
  const anchorHasContent = defineRule({
1920
1920
  id: "anchor-has-content",
1921
1921
  title: "Anchor has no content",
@@ -1931,7 +1931,7 @@ const anchorHasContent = defineRule({
1931
1931
  for (const attribute of ["title", "aria-label"]) if (hasJsxPropIgnoreCase(opening.attributes, attribute)) return;
1932
1932
  context.report({
1933
1933
  node: opening.name,
1934
- message: MESSAGE$64
1934
+ message: MESSAGE$59
1935
1935
  });
1936
1936
  } })
1937
1937
  });
@@ -2325,7 +2325,7 @@ const parseJsxValue = (value) => {
2325
2325
  };
2326
2326
  //#endregion
2327
2327
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
2328
- const MESSAGE$63 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2328
+ const MESSAGE$58 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2329
2329
  const ariaActivedescendantHasTabindex = defineRule({
2330
2330
  id: "aria-activedescendant-has-tabindex",
2331
2331
  title: "aria-activedescendant missing tabindex",
@@ -2343,14 +2343,14 @@ const ariaActivedescendantHasTabindex = defineRule({
2343
2343
  if (tabIndexValue === null || tabIndexValue >= -1) return;
2344
2344
  context.report({
2345
2345
  node: node.name,
2346
- message: MESSAGE$63
2346
+ message: MESSAGE$58
2347
2347
  });
2348
2348
  return;
2349
2349
  }
2350
2350
  if (isInteractiveElement(tag, node)) return;
2351
2351
  context.report({
2352
2352
  node: node.name,
2353
- message: MESSAGE$63
2353
+ message: MESSAGE$58
2354
2354
  });
2355
2355
  } })
2356
2356
  });
@@ -3019,7 +3019,7 @@ const ABSTRACT_ROLES = new Set([
3019
3019
  "widget",
3020
3020
  "window"
3021
3021
  ]);
3022
- const PRESENTATION_ROLES$2 = new Set(["presentation", "none"]);
3022
+ const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
3023
3023
  //#endregion
3024
3024
  //#region src/plugin/rules/a11y/aria-role.ts
3025
3025
  const buildBaseMessage = (suffix) => `This \`role\` is not a valid ARIA role, so assistive tech cannot expose it correctly. Use a real, non-abstract role.${suffix}`;
@@ -4326,7 +4326,7 @@ const asyncParallel = defineRule({
4326
4326
  });
4327
4327
  //#endregion
4328
4328
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
4329
- const MESSAGE$62 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
4329
+ const MESSAGE$57 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
4330
4330
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
4331
4331
  const STORAGE_GLOBALS = new Set([
4332
4332
  "window",
@@ -4360,7 +4360,7 @@ const authTokenInWebStorage = defineRule({
4360
4360
  if (!SENSITIVE_KEY_PATTERN.test(keyArgument.value)) return;
4361
4361
  context.report({
4362
4362
  node,
4363
- message: MESSAGE$62
4363
+ message: MESSAGE$57
4364
4364
  });
4365
4365
  },
4366
4366
  AssignmentExpression(node) {
@@ -4371,7 +4371,7 @@ const authTokenInWebStorage = defineRule({
4371
4371
  if (!propertyName || !SENSITIVE_KEY_PATTERN.test(propertyName)) return;
4372
4372
  context.report({
4373
4373
  node: target,
4374
- message: MESSAGE$62
4374
+ message: MESSAGE$57
4375
4375
  });
4376
4376
  }
4377
4377
  })
@@ -4710,6 +4710,14 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
4710
4710
  }
4711
4711
  });
4712
4712
  //#endregion
4713
+ //#region src/plugin/utils/is-presentation-role.ts
4714
+ const isPresentationRole = (openingElement) => {
4715
+ const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
4716
+ if (!roleAttribute) return false;
4717
+ const value = getJsxPropStringValue(roleAttribute);
4718
+ return value !== null && PRESENTATION_ROLES$1.has(value);
4719
+ };
4720
+ //#endregion
4713
4721
  //#region src/plugin/utils/is-pure-event-blocker-handler.ts
4714
4722
  const BLOCKER_METHOD_NAMES = new Set([
4715
4723
  "stopPropagation",
@@ -4747,8 +4755,7 @@ const isPureEventBlockerHandler = (attribute) => {
4747
4755
  };
4748
4756
  //#endregion
4749
4757
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
4750
- const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
4751
- const MESSAGE$61 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
4758
+ const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
4752
4759
  const KEY_HANDLERS = [
4753
4760
  "onKeyUp",
4754
4761
  "onKeyDown",
@@ -4772,15 +4779,11 @@ const clickEventsHaveKeyEvents = defineRule({
4772
4779
  if (!onClick) return;
4773
4780
  if (isPureEventBlockerHandler(onClick)) return;
4774
4781
  if (isHiddenFromScreenReader(node, context.settings)) return;
4775
- const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
4776
- if (roleAttribute) {
4777
- const roleValue = getJsxPropStringValue(roleAttribute);
4778
- if (roleValue && PRESENTATION_ROLES$1.has(roleValue)) return;
4779
- }
4782
+ if (isPresentationRole(node)) return;
4780
4783
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
4781
4784
  context.report({
4782
4785
  node: node.name,
4783
- message: MESSAGE$61
4786
+ message: MESSAGE$56
4784
4787
  });
4785
4788
  } };
4786
4789
  }
@@ -4895,7 +4898,7 @@ const isReactComponentName = (name) => {
4895
4898
  };
4896
4899
  //#endregion
4897
4900
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
4898
- const MESSAGE$60 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
4901
+ const MESSAGE$55 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
4899
4902
  const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
4900
4903
  const DEFAULT_LABELLING_PROPS = [
4901
4904
  "alt",
@@ -5056,7 +5059,7 @@ const controlHasAssociatedLabel = defineRule({
5056
5059
  for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
5057
5060
  context.report({
5058
5061
  node: opening,
5059
- message: MESSAGE$60
5062
+ message: MESSAGE$55
5060
5063
  });
5061
5064
  } };
5062
5065
  }
@@ -5185,7 +5188,6 @@ const dangerousHtmlSink = defineRule({
5185
5188
  return findings;
5186
5189
  }
5187
5190
  });
5188
- const WCAG_CONTRAST_NORMAL_MIN = 4.5;
5189
5191
  const LONG_TRANSITION_DURATION_THRESHOLD_MS = 1e3;
5190
5192
  const VAGUE_BUTTON_LABELS = new Set([
5191
5193
  "continue",
@@ -5484,10 +5486,10 @@ const noVagueButtonLabel = defineRule({
5484
5486
  });
5485
5487
  //#endregion
5486
5488
  //#region src/plugin/utils/has-jsx-spread-attribute.ts
5487
- const hasJsxSpreadAttribute = (attributes) => attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
5489
+ const hasJsxSpreadAttribute$1 = (attributes) => attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
5488
5490
  //#endregion
5489
5491
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
5490
- const MESSAGE$59 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
5492
+ const MESSAGE$54 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
5491
5493
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
5492
5494
  const NAME_PROVIDING_ATTRIBUTES = [
5493
5495
  "aria-label",
@@ -5506,11 +5508,11 @@ const dialogHasAccessibleName = defineRule({
5506
5508
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
5507
5509
  const roleValue = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
5508
5510
  if (!(tagName === "dialog" || roleValue !== null && DIALOG_ROLES.has(roleValue))) return;
5509
- if (hasJsxSpreadAttribute(node.attributes)) return;
5511
+ if (hasJsxSpreadAttribute$1(node.attributes)) return;
5510
5512
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
5511
5513
  context.report({
5512
5514
  node: node.name,
5513
- message: MESSAGE$59
5515
+ message: MESSAGE$54
5514
5516
  });
5515
5517
  } })
5516
5518
  });
@@ -5549,7 +5551,7 @@ const isEs6Component = (node) => {
5549
5551
  };
5550
5552
  //#endregion
5551
5553
  //#region src/plugin/rules/react-builtins/display-name.ts
5552
- const MESSAGE$58 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
5554
+ const MESSAGE$53 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
5553
5555
  const DEFAULT_ADDITIONAL_HOCS = [
5554
5556
  "observer",
5555
5557
  "lazy",
@@ -5748,11 +5750,11 @@ const displayName = defineRule({
5748
5750
  category: "Architecture",
5749
5751
  create: (context) => {
5750
5752
  const settings = resolveSettings$44(context.settings);
5751
- const ignoreNamed = settings.ignoreTranspilerName ? false : true;
5753
+ const ignoreNamed = !settings.ignoreTranspilerName;
5752
5754
  const reportAt = (node) => {
5753
5755
  context.report({
5754
5756
  node,
5755
- message: MESSAGE$58
5757
+ message: MESSAGE$53
5756
5758
  });
5757
5759
  };
5758
5760
  return {
@@ -7900,7 +7902,7 @@ const forbidElements = defineRule({
7900
7902
  });
7901
7903
  //#endregion
7902
7904
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
7903
- const MESSAGE$57 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
7905
+ const MESSAGE$52 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
7904
7906
  const forwardRefUsesRef = defineRule({
7905
7907
  id: "forward-ref-uses-ref",
7906
7908
  title: "forwardRef without ref parameter",
@@ -7920,7 +7922,7 @@ const forwardRefUsesRef = defineRule({
7920
7922
  if (isNodeOfType(onlyParam, "RestElement")) return;
7921
7923
  context.report({
7922
7924
  node: inner,
7923
- message: MESSAGE$57
7925
+ message: MESSAGE$52
7924
7926
  });
7925
7927
  } })
7926
7928
  });
@@ -7957,7 +7959,7 @@ const gitProviderUrlInjectionRisk = defineRule({
7957
7959
  });
7958
7960
  //#endregion
7959
7961
  //#region src/plugin/rules/a11y/heading-has-content.ts
7960
- const MESSAGE$56 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
7962
+ const MESSAGE$51 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
7961
7963
  const DEFAULT_HEADING_TAGS = [
7962
7964
  "h1",
7963
7965
  "h2",
@@ -7990,7 +7992,7 @@ const headingHasContent = defineRule({
7990
7992
  if (isHiddenFromScreenReader(node, context.settings)) return;
7991
7993
  context.report({
7992
7994
  node,
7993
- message: MESSAGE$56
7995
+ message: MESSAGE$51
7994
7996
  });
7995
7997
  } };
7996
7998
  }
@@ -8128,7 +8130,7 @@ const hooksNoNanInDeps = defineRule({
8128
8130
  });
8129
8131
  //#endregion
8130
8132
  //#region src/plugin/rules/a11y/html-has-lang.ts
8131
- const MESSAGE$55 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
8133
+ const MESSAGE$50 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
8132
8134
  const resolveSettings$38 = (settings) => {
8133
8135
  const reactDoctor = settings?.["react-doctor"];
8134
8136
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -8171,26 +8173,17 @@ const htmlHasLang = defineRule({
8171
8173
  return { JSXOpeningElement(node) {
8172
8174
  const tag = getElementType(node, context.settings);
8173
8175
  if (!tagSet.has(tag)) return;
8174
- const hasSpread = node.attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
8175
8176
  const lang = hasJsxPropIgnoreCase(node.attributes, "lang");
8176
8177
  if (!lang) {
8177
8178
  context.report({
8178
8179
  node: node.name,
8179
- message: MESSAGE$55
8180
- });
8181
- return;
8182
- }
8183
- const verdict = evaluateLang(lang.value);
8184
- if (verdict === "missing" || verdict === "empty") {
8185
- context.report({
8186
- node: lang,
8187
- message: MESSAGE$55
8180
+ message: MESSAGE$50
8188
8181
  });
8189
8182
  return;
8190
8183
  }
8191
- if (hasSpread && !lang) context.report({
8192
- node: node.name,
8193
- message: MESSAGE$55
8184
+ if (evaluateLang(lang.value) === "empty") context.report({
8185
+ node: lang,
8186
+ message: MESSAGE$50
8194
8187
  });
8195
8188
  } };
8196
8189
  }
@@ -8404,7 +8397,7 @@ const htmlNoNestedInteractive = defineRule({
8404
8397
  });
8405
8398
  //#endregion
8406
8399
  //#region src/plugin/rules/a11y/iframe-has-title.ts
8407
- const MESSAGE$54 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
8400
+ const MESSAGE$49 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
8408
8401
  const evaluateTitleValue = (value) => {
8409
8402
  if (!value) return "missing";
8410
8403
  if (isNodeOfType(value, "Literal")) {
@@ -8444,14 +8437,14 @@ const iframeHasTitle = defineRule({
8444
8437
  if (!titleAttr) {
8445
8438
  if (hasSpread || tag === "iframe") context.report({
8446
8439
  node: node.name,
8447
- message: MESSAGE$54
8440
+ message: MESSAGE$49
8448
8441
  });
8449
8442
  return;
8450
8443
  }
8451
8444
  const verdict = evaluateTitleValue(titleAttr.value);
8452
8445
  if (verdict === "missing" || verdict === "empty") context.report({
8453
8446
  node: titleAttr,
8454
- message: MESSAGE$54
8447
+ message: MESSAGE$49
8455
8448
  });
8456
8449
  } })
8457
8450
  });
@@ -8555,7 +8548,7 @@ const iframeMissingSandbox = defineRule({
8555
8548
  });
8556
8549
  //#endregion
8557
8550
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
8558
- const MESSAGE$53 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
8551
+ const MESSAGE$48 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
8559
8552
  const DEFAULT_COMPONENTS = ["img"];
8560
8553
  const DEFAULT_REDUNDANT_WORDS = [
8561
8554
  "image",
@@ -8620,7 +8613,7 @@ const imgRedundantAlt = defineRule({
8620
8613
  if (!altAttribute) return;
8621
8614
  if (altValueRedundant(altAttribute, settings.words)) context.report({
8622
8615
  node: altAttribute,
8623
- message: MESSAGE$53
8616
+ message: MESSAGE$48
8624
8617
  });
8625
8618
  } };
8626
8619
  }
@@ -8903,14 +8896,6 @@ const isNonInteractiveElement = (elementType, openingElement) => {
8903
8896
  //#region src/plugin/utils/is-non-interactive-role.ts
8904
8897
  const isNonInteractiveRole = (role) => NON_INTERACTIVE_ROLES.has(role);
8905
8898
  //#endregion
8906
- //#region src/plugin/utils/is-presentation-role.ts
8907
- const isPresentationRole = (openingElement) => {
8908
- const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
8909
- if (!roleAttribute) return false;
8910
- const value = getJsxPropStringValue(roleAttribute);
8911
- return value !== null && PRESENTATION_ROLES$2.has(value);
8912
- };
8913
- //#endregion
8914
8899
  //#region src/plugin/rules/a11y/interactive-supports-focus.ts
8915
8900
  const buildTabbableMessage = (role) => `Keyboard users can't tab to this '${role}' because it isn't focusable, so add \`tabIndex={0}\`.`;
8916
8901
  const buildFocusableMessage = (role) => `Keyboard users can't focus this '${role}' because it can't receive focus, so add \`tabIndex={0}\` or \`tabIndex={-1}\`.`;
@@ -10685,6 +10670,24 @@ const hasJsxKeyAttribute = (openingElement) => {
10685
10670
  return false;
10686
10671
  };
10687
10672
  //#endregion
10673
+ //#region src/plugin/utils/is-non-children-jsx-attribute-value.ts
10674
+ const ascendThroughJsxValueWrappers = (node) => {
10675
+ let current = node;
10676
+ while (current.parent) {
10677
+ const parent = current.parent;
10678
+ if (!(isNodeOfType(parent, "ChainExpression") || isNodeOfType(parent, "TSAsExpression") || isNodeOfType(parent, "TSSatisfiesExpression") || isNodeOfType(parent, "TSNonNullExpression") || isNodeOfType(parent, "LogicalExpression") || isNodeOfType(parent, "ConditionalExpression") && parent.test !== current)) break;
10679
+ current = parent;
10680
+ }
10681
+ return current;
10682
+ };
10683
+ const isNonChildrenJsxAttributeValue = (node) => {
10684
+ const container = ascendThroughJsxValueWrappers(node).parent;
10685
+ if (!container || !isNodeOfType(container, "JSXExpressionContainer")) return false;
10686
+ const attribute = container.parent;
10687
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute")) return false;
10688
+ return getJsxAttributeName(attribute.name) !== "children";
10689
+ };
10690
+ //#endregion
10688
10691
  //#region src/plugin/rules/react-builtins/jsx-key.ts
10689
10692
  const ITERATOR_METHOD_NAMES = new Set([
10690
10693
  "map",
@@ -10723,6 +10726,7 @@ const findEnclosingIteratorContext = (jsxNode) => {
10723
10726
  const arrayParent = parent.parent;
10724
10727
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
10725
10728
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
10729
+ if (isNonChildrenJsxAttributeValue(parent)) return null;
10726
10730
  return { kind: "array" };
10727
10731
  } else if (isNodeOfType(parent, "CallExpression")) {
10728
10732
  const callee = parent.callee;
@@ -10735,10 +10739,13 @@ const findEnclosingIteratorContext = (jsxNode) => {
10735
10739
  if (!targetArg) return null;
10736
10740
  let walker = current;
10737
10741
  while (walker && walker !== parent) {
10738
- if (walker === targetArg) return {
10739
- kind: "iterator",
10740
- callExpression: parent
10741
- };
10742
+ if (walker === targetArg) {
10743
+ if (isNonChildrenJsxAttributeValue(parent)) return null;
10744
+ return {
10745
+ kind: "iterator",
10746
+ callExpression: parent
10747
+ };
10748
+ }
10742
10749
  walker = walker.parent ?? null;
10743
10750
  }
10744
10751
  return null;
@@ -10977,7 +10984,7 @@ const jsxMaxDepth = defineRule({
10977
10984
  });
10978
10985
  //#endregion
10979
10986
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
10980
- const MESSAGE$52 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
10987
+ const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
10981
10988
  const LITERAL_TEXT_TAGS = new Set([
10982
10989
  "code",
10983
10990
  "pre",
@@ -11013,7 +11020,7 @@ const jsxNoCommentTextnodes = defineRule({
11013
11020
  if (isInsideLiteralTextTag(node)) return;
11014
11021
  context.report({
11015
11022
  node,
11016
- message: MESSAGE$52
11023
+ message: MESSAGE$47
11017
11024
  });
11018
11025
  } })
11019
11026
  });
@@ -11044,7 +11051,7 @@ const isInsideFunctionScope = (node) => {
11044
11051
  };
11045
11052
  //#endregion
11046
11053
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
11047
- const MESSAGE$51 = "Every reader of this context redraws on each render because you build its `value` inline.";
11054
+ const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
11048
11055
  const CONTEXT_MODULES$1 = [
11049
11056
  "react",
11050
11057
  "use-context-selector",
@@ -11142,7 +11149,7 @@ const jsxNoConstructedContextValues = defineRule({
11142
11149
  if (!isConstructedValue(innerExpression)) continue;
11143
11150
  context.report({
11144
11151
  node: attribute,
11145
- message: MESSAGE$51
11152
+ message: MESSAGE$46
11146
11153
  });
11147
11154
  }
11148
11155
  }
@@ -11228,7 +11235,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
11228
11235
  };
11229
11236
  //#endregion
11230
11237
  //#region src/plugin/rules/react-builtins/jsx-no-jsx-as-prop.ts
11231
- const MESSAGE$50 = "This child redraws every render because the prop gets brand new JSX each time.";
11238
+ const MESSAGE$45 = "This child redraws every render because the prop gets brand new JSX each time.";
11232
11239
  const KNOWN_SLOT_PROP_NAMES = new Set([
11233
11240
  "icon",
11234
11241
  "Icon",
@@ -11497,7 +11504,7 @@ const jsxNoJsxAsProp = defineRule({
11497
11504
  if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
11498
11505
  context.report({
11499
11506
  node,
11500
- message: MESSAGE$50
11507
+ message: MESSAGE$45
11501
11508
  });
11502
11509
  }
11503
11510
  };
@@ -11785,7 +11792,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
11785
11792
  ];
11786
11793
  //#endregion
11787
11794
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
11788
- const MESSAGE$49 = "This child redraws every render because the prop gets a brand new array each time.";
11795
+ const MESSAGE$44 = "This child redraws every render because the prop gets a brand new array each time.";
11789
11796
  const isDataArrayPropName = (propName) => {
11790
11797
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
11791
11798
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -11869,7 +11876,7 @@ const jsxNoNewArrayAsProp = defineRule({
11869
11876
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
11870
11877
  context.report({
11871
11878
  node,
11872
- message: MESSAGE$49
11879
+ message: MESSAGE$44
11873
11880
  });
11874
11881
  }
11875
11882
  };
@@ -12127,7 +12134,7 @@ const SAFE_RECEIVER_NAMES = new Set([
12127
12134
  ]);
12128
12135
  //#endregion
12129
12136
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
12130
- const MESSAGE$48 = "This child redraws every render because the prop gets a brand new function each time.";
12137
+ const MESSAGE$43 = "This child redraws every render because the prop gets a brand new function each time.";
12131
12138
  const isAccessorPredicateName = (propName) => {
12132
12139
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
12133
12140
  if (propName.length <= prefix.length) continue;
@@ -12333,7 +12340,7 @@ const jsxNoNewFunctionAsProp = defineRule({
12333
12340
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
12334
12341
  context.report({
12335
12342
  node,
12336
- message: MESSAGE$48
12343
+ message: MESSAGE$43
12337
12344
  });
12338
12345
  }
12339
12346
  };
@@ -12553,7 +12560,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
12553
12560
  ];
12554
12561
  //#endregion
12555
12562
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
12556
- const MESSAGE$47 = "This child redraws every render because the prop gets a brand new object each time.";
12563
+ const MESSAGE$42 = "This child redraws every render because the prop gets a brand new object each time.";
12557
12564
  const isConfigObjectPropName = (propName) => {
12558
12565
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
12559
12566
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -12641,7 +12648,7 @@ const jsxNoNewObjectAsProp = defineRule({
12641
12648
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
12642
12649
  context.report({
12643
12650
  node,
12644
- message: MESSAGE$47
12651
+ message: MESSAGE$42
12645
12652
  });
12646
12653
  }
12647
12654
  };
@@ -12649,7 +12656,7 @@ const jsxNoNewObjectAsProp = defineRule({
12649
12656
  });
12650
12657
  //#endregion
12651
12658
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
12652
- const MESSAGE$46 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
12659
+ const MESSAGE$41 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
12653
12660
  const JAVASCRIPT_URL_PATTERN = /j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
12654
12661
  const resolveSettings$28 = (settings) => {
12655
12662
  const reactDoctor = settings?.["react-doctor"];
@@ -12690,7 +12697,7 @@ const jsxNoScriptUrl = defineRule({
12690
12697
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
12691
12698
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
12692
12699
  node: attribute,
12693
- message: MESSAGE$46
12700
+ message: MESSAGE$41
12694
12701
  });
12695
12702
  }
12696
12703
  } };
@@ -13005,7 +13012,7 @@ const jsxPropsNoSpreadMulti = defineRule({
13005
13012
  });
13006
13013
  //#endregion
13007
13014
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
13008
- const MESSAGE$45 = "You can't tell what props reach this element when you spread them.";
13015
+ const MESSAGE$40 = "You can't tell what props reach this element when you spread them.";
13009
13016
  const resolveSettings$25 = (settings) => {
13010
13017
  const reactDoctor = settings?.["react-doctor"];
13011
13018
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -13046,7 +13053,7 @@ const jsxPropsNoSpreading = defineRule({
13046
13053
  }
13047
13054
  context.report({
13048
13055
  node: attribute,
13049
- message: MESSAGE$45
13056
+ message: MESSAGE$40
13050
13057
  });
13051
13058
  }
13052
13059
  } };
@@ -13274,7 +13281,7 @@ const labelHasAssociatedControl = defineRule({
13274
13281
  });
13275
13282
  //#endregion
13276
13283
  //#region src/plugin/rules/a11y/lang.ts
13277
- const MESSAGE$44 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
13284
+ const MESSAGE$39 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
13278
13285
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
13279
13286
  "aa",
13280
13287
  "ab",
@@ -13486,7 +13493,7 @@ const lang = defineRule({
13486
13493
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
13487
13494
  context.report({
13488
13495
  node: langAttr,
13489
- message: MESSAGE$44
13496
+ message: MESSAGE$39
13490
13497
  });
13491
13498
  return;
13492
13499
  }
@@ -13495,7 +13502,7 @@ const lang = defineRule({
13495
13502
  if (value === null) return;
13496
13503
  if (!isValidLangTag(value)) context.report({
13497
13504
  node: langAttr,
13498
- message: MESSAGE$44
13505
+ message: MESSAGE$39
13499
13506
  });
13500
13507
  } })
13501
13508
  });
@@ -13540,7 +13547,7 @@ const mdxSsrExecutionRisk = defineRule({
13540
13547
  });
13541
13548
  //#endregion
13542
13549
  //#region src/plugin/rules/a11y/media-has-caption.ts
13543
- const MESSAGE$43 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
13550
+ const MESSAGE$38 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
13544
13551
  const DEFAULT_AUDIO = ["audio"];
13545
13552
  const DEFAULT_VIDEO = ["video"];
13546
13553
  const DEFAULT_TRACK = ["track"];
@@ -13581,7 +13588,7 @@ const mediaHasCaption = defineRule({
13581
13588
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
13582
13589
  context.report({
13583
13590
  node: node.name,
13584
- message: MESSAGE$43
13591
+ message: MESSAGE$38
13585
13592
  });
13586
13593
  return;
13587
13594
  }
@@ -13598,7 +13605,7 @@ const mediaHasCaption = defineRule({
13598
13605
  return kindValue.value.toLowerCase() === "captions";
13599
13606
  })) context.report({
13600
13607
  node: node.name,
13601
- message: MESSAGE$43
13608
+ message: MESSAGE$38
13602
13609
  });
13603
13610
  } };
13604
13611
  }
@@ -15399,7 +15406,7 @@ const nextjsNoVercelOgImport = defineRule({
15399
15406
  });
15400
15407
  //#endregion
15401
15408
  //#region src/plugin/rules/a11y/no-access-key.ts
15402
- const MESSAGE$42 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
15409
+ const MESSAGE$37 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
15403
15410
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
15404
15411
  const noAccessKey = defineRule({
15405
15412
  id: "no-access-key",
@@ -15416,7 +15423,7 @@ const noAccessKey = defineRule({
15416
15423
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
15417
15424
  context.report({
15418
15425
  node: accessKey,
15419
- message: MESSAGE$42
15426
+ message: MESSAGE$37
15420
15427
  });
15421
15428
  return;
15422
15429
  }
@@ -15426,7 +15433,7 @@ const noAccessKey = defineRule({
15426
15433
  if (isUndefinedIdentifier(expression)) return;
15427
15434
  context.report({
15428
15435
  node: accessKey,
15429
- message: MESSAGE$42
15436
+ message: MESSAGE$37
15430
15437
  });
15431
15438
  }
15432
15439
  } })
@@ -15908,41 +15915,8 @@ const noAdjustStateOnPropChange = defineRule({
15908
15915
  } })
15909
15916
  });
15910
15917
  //#endregion
15911
- //#region src/plugin/rules/design/utils/get-string-from-class-name-attr.ts
15912
- const getStringFromClassNameAttr = (node) => {
15913
- if (!isNodeOfType(node, "JSXOpeningElement")) return null;
15914
- const classAttr = findJsxAttribute(node.attributes ?? [], "className");
15915
- if (!classAttr?.value) return null;
15916
- if (isNodeOfType(classAttr.value, "Literal") && typeof classAttr.value.value === "string") return classAttr.value.value;
15917
- if (isNodeOfType(classAttr.value, "JSXExpressionContainer") && isNodeOfType(classAttr.value.expression, "Literal") && typeof classAttr.value.expression.value === "string") return classAttr.value.expression.value;
15918
- if (isNodeOfType(classAttr.value, "JSXExpressionContainer") && isNodeOfType(classAttr.value.expression, "TemplateLiteral") && classAttr.value.expression.quasis?.length === 1) return classAttr.value.expression.quasis[0].value?.raw ?? null;
15919
- return null;
15920
- };
15921
- //#endregion
15922
- //#region src/plugin/rules/design/no-arbitrary-px-font-size.ts
15923
- const ARBITRARY_PX_FONT_SIZE = /(?:^|\s)(?:\w+:)*text-\[(\d+(?:\.\d+)?)px\]/g;
15924
- const noArbitraryPxFontSize = defineRule({
15925
- id: "no-arbitrary-px-font-size",
15926
- title: "Pixel arbitrary font size",
15927
- tags: ["design", "test-noise"],
15928
- severity: "warn",
15929
- category: "Accessibility",
15930
- recommendation: "Use `rem` for arbitrary font sizes (`text-[0.8125rem]`, not `text-[13px]`) so text scales with the user's root font-size preference. Pixels stay fine for `border-*` / `outline-*`.",
15931
- create: (context) => ({ JSXOpeningElement(node) {
15932
- const classNameValue = getStringFromClassNameAttr(node);
15933
- if (!classNameValue) return;
15934
- for (const match of classNameValue.matchAll(ARBITRARY_PX_FONT_SIZE)) {
15935
- const rem = parseFloat(match[1]) / 16;
15936
- context.report({
15937
- node,
15938
- message: `\`text-[${match[1]}px]\` doesn't scale with the user's font-size preference — use rem, e.g. \`text-[${rem}rem]\`.`
15939
- });
15940
- }
15941
- } })
15942
- });
15943
- //#endregion
15944
15918
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
15945
- const MESSAGE$41 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
15919
+ const MESSAGE$36 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
15946
15920
  const noAriaHiddenOnFocusable = defineRule({
15947
15921
  id: "no-aria-hidden-on-focusable",
15948
15922
  title: "aria-hidden on focusable element",
@@ -15969,7 +15943,7 @@ const noAriaHiddenOnFocusable = defineRule({
15969
15943
  const isImplicitlyFocusable = isInteractiveElement(tag, node);
15970
15944
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
15971
15945
  node: ariaHidden,
15972
- message: MESSAGE$41
15946
+ message: MESSAGE$36
15973
15947
  });
15974
15948
  } })
15975
15949
  });
@@ -16337,7 +16311,7 @@ const noArrayIndexAsKey = defineRule({
16337
16311
  });
16338
16312
  //#endregion
16339
16313
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
16340
- const MESSAGE$40 = "Your users can see & submit the wrong data when this list reorders.";
16314
+ const MESSAGE$35 = "Your users can see & submit the wrong data when this list reorders.";
16341
16315
  const SECOND_INDEX_METHODS = new Set([
16342
16316
  "every",
16343
16317
  "filter",
@@ -16541,7 +16515,7 @@ const noArrayIndexKey = defineRule({
16541
16515
  }
16542
16516
  context.report({
16543
16517
  node: keyAttribute,
16544
- message: MESSAGE$40
16518
+ message: MESSAGE$35
16545
16519
  });
16546
16520
  },
16547
16521
  CallExpression(node) {
@@ -16561,7 +16535,7 @@ const noArrayIndexKey = defineRule({
16561
16535
  if (propName !== "key") continue;
16562
16536
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
16563
16537
  node: property,
16564
- message: MESSAGE$40
16538
+ message: MESSAGE$35
16565
16539
  });
16566
16540
  }
16567
16541
  }
@@ -16569,7 +16543,7 @@ const noArrayIndexKey = defineRule({
16569
16543
  });
16570
16544
  //#endregion
16571
16545
  //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
16572
- const MESSAGE$39 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
16546
+ const MESSAGE$34 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
16573
16547
  const noAsyncEffectCallback = defineRule({
16574
16548
  id: "no-async-effect-callback",
16575
16549
  title: "Async effect callback",
@@ -16583,13 +16557,13 @@ const noAsyncEffectCallback = defineRule({
16583
16557
  if (!callback.async) return;
16584
16558
  context.report({
16585
16559
  node: callback,
16586
- message: MESSAGE$39
16560
+ message: MESSAGE$34
16587
16561
  });
16588
16562
  } })
16589
16563
  });
16590
16564
  //#endregion
16591
16565
  //#region src/plugin/rules/a11y/no-autofocus.ts
16592
- const MESSAGE$38 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
16566
+ const MESSAGE$33 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
16593
16567
  const resolveSettings$21 = (settings) => {
16594
16568
  const reactDoctor = settings?.["react-doctor"];
16595
16569
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -16645,45 +16619,12 @@ const noAutofocus = defineRule({
16645
16619
  }
16646
16620
  context.report({
16647
16621
  node: autoFocusAttribute,
16648
- message: MESSAGE$38
16622
+ message: MESSAGE$33
16649
16623
  });
16650
16624
  } };
16651
16625
  }
16652
16626
  });
16653
16627
  //#endregion
16654
- //#region src/plugin/rules/a11y/no-autoplay-without-muted.ts
16655
- const MESSAGE$37 = "Autoplaying media with sound is hostile to your users (and browsers block it). Add `muted` (with `playsInline`) to the autoplaying `<video>` / `<audio>`, or drop `autoPlay`.";
16656
- const resolveStaticBoolean = (attribute) => {
16657
- const value = attribute.value;
16658
- if (!value) return true;
16659
- const literal = isNodeOfType(value, "JSXExpressionContainer") ? value.expression : value;
16660
- if (isNodeOfType(literal, "Literal")) {
16661
- if (literal.value === true || literal.value === "true") return true;
16662
- if (literal.value === false || literal.value === "false") return false;
16663
- }
16664
- return null;
16665
- };
16666
- const noAutoplayWithoutMuted = defineRule({
16667
- id: "no-autoplay-without-muted",
16668
- title: "Autoplaying media without muted",
16669
- severity: "warn",
16670
- recommendation: "Always pair `autoPlay` with `muted` (and `playsInline`): `<video autoPlay muted loop playsInline />`. If the sound matters, drop `autoPlay` and let users start it.",
16671
- create: (context) => ({ JSXOpeningElement(node) {
16672
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
16673
- const tagName = node.name.name;
16674
- if (tagName !== "video" && tagName !== "audio") return;
16675
- if (hasJsxSpreadAttribute(node.attributes)) return;
16676
- const autoPlay = hasJsxPropIgnoreCase(node.attributes, "autoplay");
16677
- if (!autoPlay || resolveStaticBoolean(autoPlay) !== true) return;
16678
- const muted = hasJsxPropIgnoreCase(node.attributes, "muted");
16679
- if (muted && resolveStaticBoolean(muted) !== false) return;
16680
- context.report({
16681
- node: node.name,
16682
- message: MESSAGE$37
16683
- });
16684
- } })
16685
- });
16686
- //#endregion
16687
16628
  //#region src/plugin/utils/create-relative-import-source.ts
16688
16629
  const createRelativeImportSource = (filename, targetFilePath) => {
16689
16630
  const targetPathWithoutExtension = targetFilePath.slice(0, targetFilePath.length - path.extname(targetFilePath).length);
@@ -17182,7 +17123,7 @@ const noChainStateUpdates = defineRule({
17182
17123
  });
17183
17124
  //#endregion
17184
17125
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
17185
- const MESSAGE$36 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
17126
+ const MESSAGE$32 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
17186
17127
  const noChildrenProp = defineRule({
17187
17128
  id: "no-children-prop",
17188
17129
  title: "Children passed as a prop",
@@ -17194,7 +17135,7 @@ const noChildrenProp = defineRule({
17194
17135
  if (node.name.name !== "children") return;
17195
17136
  context.report({
17196
17137
  node: node.name,
17197
- message: MESSAGE$36
17138
+ message: MESSAGE$32
17198
17139
  });
17199
17140
  },
17200
17141
  CallExpression(node) {
@@ -17207,7 +17148,7 @@ const noChildrenProp = defineRule({
17207
17148
  const propertyKey = property.key;
17208
17149
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
17209
17150
  node: propertyKey,
17210
- message: MESSAGE$36
17151
+ message: MESSAGE$32
17211
17152
  });
17212
17153
  }
17213
17154
  }
@@ -17215,7 +17156,7 @@ const noChildrenProp = defineRule({
17215
17156
  });
17216
17157
  //#endregion
17217
17158
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
17218
- const MESSAGE$35 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
17159
+ const MESSAGE$31 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
17219
17160
  const noCloneElement = defineRule({
17220
17161
  id: "no-clone-element",
17221
17162
  title: "cloneElement makes child props fragile",
@@ -17228,7 +17169,7 @@ const noCloneElement = defineRule({
17228
17169
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
17229
17170
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
17230
17171
  node: callee,
17231
- message: MESSAGE$35
17172
+ message: MESSAGE$31
17232
17173
  });
17233
17174
  return;
17234
17175
  }
@@ -17241,7 +17182,7 @@ const noCloneElement = defineRule({
17241
17182
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
17242
17183
  context.report({
17243
17184
  node: callee,
17244
- message: MESSAGE$35
17185
+ message: MESSAGE$31
17245
17186
  });
17246
17187
  }
17247
17188
  } })
@@ -17290,7 +17231,7 @@ const enclosingComponentOrHookName = (node) => {
17290
17231
  };
17291
17232
  //#endregion
17292
17233
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
17293
- const MESSAGE$34 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
17234
+ const MESSAGE$30 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
17294
17235
  const CONTEXT_MODULES = [
17295
17236
  "react",
17296
17237
  "use-context-selector",
@@ -17326,13 +17267,13 @@ const noCreateContextInRender = defineRule({
17326
17267
  if (!componentOrHookName) return;
17327
17268
  context.report({
17328
17269
  node,
17329
- message: `${MESSAGE$34} (called inside "${componentOrHookName}")`
17270
+ message: `${MESSAGE$30} (called inside "${componentOrHookName}")`
17330
17271
  });
17331
17272
  } })
17332
17273
  });
17333
17274
  //#endregion
17334
17275
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
17335
- const MESSAGE$33 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
17276
+ const MESSAGE$29 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
17336
17277
  const noCreateRefInFunctionComponent = defineRule({
17337
17278
  id: "no-create-ref-in-function-component",
17338
17279
  title: "createRef in function component",
@@ -17351,7 +17292,7 @@ const noCreateRefInFunctionComponent = defineRule({
17351
17292
  if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
17352
17293
  context.report({
17353
17294
  node,
17354
- message: MESSAGE$33
17295
+ message: MESSAGE$29
17355
17296
  });
17356
17297
  } })
17357
17298
  });
@@ -17491,12 +17432,13 @@ const noCreateStoreInRender = defineRule({
17491
17432
  });
17492
17433
  //#endregion
17493
17434
  //#region src/plugin/rules/react-builtins/no-danger.ts
17494
- const MESSAGE$32 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
17435
+ const MESSAGE$28 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
17495
17436
  const noDanger = defineRule({
17496
17437
  id: "no-danger",
17497
17438
  title: "Raw HTML injection can run unsafe markup",
17498
17439
  severity: "warn",
17499
17440
  category: "Security",
17441
+ defaultEnabled: false,
17500
17442
  recommendation: "Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.",
17501
17443
  create: (context) => ({
17502
17444
  JSXOpeningElement(node) {
@@ -17504,7 +17446,7 @@ const noDanger = defineRule({
17504
17446
  if (!propAttribute) return;
17505
17447
  context.report({
17506
17448
  node: propAttribute.name,
17507
- message: MESSAGE$32
17449
+ message: MESSAGE$28
17508
17450
  });
17509
17451
  },
17510
17452
  CallExpression(node) {
@@ -17516,7 +17458,7 @@ const noDanger = defineRule({
17516
17458
  const propertyKey = property.key;
17517
17459
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
17518
17460
  node: propertyKey,
17519
- message: MESSAGE$32
17461
+ message: MESSAGE$28
17520
17462
  });
17521
17463
  }
17522
17464
  }
@@ -17524,7 +17466,7 @@ const noDanger = defineRule({
17524
17466
  });
17525
17467
  //#endregion
17526
17468
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
17527
- const MESSAGE$31 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
17469
+ const MESSAGE$27 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
17528
17470
  const isLineBreak = (child) => {
17529
17471
  if (!isNodeOfType(child, "JSXText")) return false;
17530
17472
  return child.value.trim().length === 0 && child.value.includes("\n");
@@ -17594,7 +17536,7 @@ const noDangerWithChildren = defineRule({
17594
17536
  if (!hasChildrenProp && !hasNestedChildren) return;
17595
17537
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
17596
17538
  node: opening,
17597
- message: MESSAGE$31
17539
+ message: MESSAGE$27
17598
17540
  });
17599
17541
  },
17600
17542
  CallExpression(node) {
@@ -17606,7 +17548,7 @@ const noDangerWithChildren = defineRule({
17606
17548
  if (!propsShape.hasDangerously) return;
17607
17549
  if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
17608
17550
  node,
17609
- message: MESSAGE$31
17551
+ message: MESSAGE$27
17610
17552
  });
17611
17553
  }
17612
17554
  })
@@ -17771,37 +17713,6 @@ const noDefaultProps = defineRule({
17771
17713
  } })
17772
17714
  });
17773
17715
  //#endregion
17774
- //#region src/plugin/utils/get-class-name-tokens.ts
17775
- const getClassNameTokens = (classNameValue) => classNameValue.split(/\s+/).filter((token) => token.length > 0).map((token) => token.split(":").pop() ?? token);
17776
- //#endregion
17777
- //#region src/plugin/rules/design/no-deprecated-tailwind-class.ts
17778
- const renameDeprecatedToken = (token) => {
17779
- if (token === "overflow-ellipsis") return "text-ellipsis";
17780
- if (token.startsWith("flex-shrink")) return token.replace("flex-shrink", "shrink");
17781
- if (token.startsWith("flex-grow")) return token.replace("flex-grow", "grow");
17782
- if (token.startsWith("bg-gradient-to-")) return token.replace("bg-gradient-to-", "bg-linear-to-");
17783
- return null;
17784
- };
17785
- const noDeprecatedTailwindClass = defineRule({
17786
- id: "no-deprecated-tailwind-class",
17787
- title: "Deprecated Tailwind v4 utility",
17788
- tags: ["design", "test-noise"],
17789
- severity: "warn",
17790
- requires: ["tailwind:4"],
17791
- recommendation: "Tailwind v4 renamed these utilities: `bg-gradient-*` → `bg-linear-*`, `flex-shrink-*` → `shrink-*`, `flex-grow-*` → `grow-*`, `overflow-ellipsis` → `text-ellipsis`. Use the new names.",
17792
- create: (context) => ({ JSXOpeningElement(node) {
17793
- const classNameValue = getStringFromClassNameAttr(node);
17794
- if (!classNameValue) return;
17795
- for (const token of getClassNameTokens(classNameValue)) {
17796
- const replacement = renameDeprecatedToken(token);
17797
- if (replacement) context.report({
17798
- node,
17799
- message: `\`${token}\` was renamed in Tailwind v4 and no longer applies — use \`${replacement}\`.`
17800
- });
17801
- }
17802
- } })
17803
- });
17804
- //#endregion
17805
17716
  //#region src/plugin/utils/is-initial-only-prop-name.ts
17806
17717
  const isInitialOnlyPropName = (propName) => {
17807
17718
  if (propName === "initialValue" || propName === "defaultValue" || propName === "seedValue") return true;
@@ -18214,7 +18125,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
18214
18125
  //#endregion
18215
18126
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
18216
18127
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
18217
- const MESSAGE$30 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
18128
+ const MESSAGE$26 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
18218
18129
  const resolveSettings$20 = (settings) => {
18219
18130
  const reactDoctor = settings?.["react-doctor"];
18220
18131
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
@@ -18233,7 +18144,7 @@ const noDidMountSetState = defineRule({
18233
18144
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
18234
18145
  context.report({
18235
18146
  node: node.callee,
18236
- message: MESSAGE$30
18147
+ message: MESSAGE$26
18237
18148
  });
18238
18149
  } };
18239
18150
  }
@@ -18241,7 +18152,7 @@ const noDidMountSetState = defineRule({
18241
18152
  //#endregion
18242
18153
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
18243
18154
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
18244
- const MESSAGE$29 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
18155
+ const MESSAGE$25 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
18245
18156
  const resolveSettings$19 = (settings) => {
18246
18157
  const reactDoctor = settings?.["react-doctor"];
18247
18158
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -18260,7 +18171,7 @@ const noDidUpdateSetState = defineRule({
18260
18171
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
18261
18172
  context.report({
18262
18173
  node: node.callee,
18263
- message: MESSAGE$29
18174
+ message: MESSAGE$25
18264
18175
  });
18265
18176
  } };
18266
18177
  }
@@ -18283,7 +18194,7 @@ const isStateMemberExpression = (node) => {
18283
18194
  };
18284
18195
  //#endregion
18285
18196
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
18286
- const MESSAGE$28 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
18197
+ const MESSAGE$24 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
18287
18198
  const shouldIgnoreMutation = (node) => {
18288
18199
  let isConstructor = false;
18289
18200
  let isInsideCallExpression = false;
@@ -18305,7 +18216,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
18305
18216
  if (shouldIgnoreMutation(reportNode)) return;
18306
18217
  context.report({
18307
18218
  node: reportNode,
18308
- message: MESSAGE$28
18219
+ message: MESSAGE$24
18309
18220
  });
18310
18221
  };
18311
18222
  const noDirectMutationState = defineRule({
@@ -18516,7 +18427,7 @@ const noDocumentStartViewTransition = defineRule({
18516
18427
  });
18517
18428
  //#endregion
18518
18429
  //#region src/plugin/rules/js-performance/no-document-write.ts
18519
- const MESSAGE$27 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
18430
+ const MESSAGE$23 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
18520
18431
  const WRITE_METHODS = new Set(["write", "writeln"]);
18521
18432
  const noDocumentWrite = defineRule({
18522
18433
  id: "no-document-write",
@@ -18530,7 +18441,7 @@ const noDocumentWrite = defineRule({
18530
18441
  if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
18531
18442
  context.report({
18532
18443
  node,
18533
- message: MESSAGE$27
18444
+ message: MESSAGE$23
18534
18445
  });
18535
18446
  } })
18536
18447
  });
@@ -19913,7 +19824,7 @@ const ALLOWED_NAMESPACES = new Set([
19913
19824
  "ReactDOM",
19914
19825
  "ReactDom"
19915
19826
  ]);
19916
- const MESSAGE$26 = "`findDOMNode` crashes your app in React 19 because it was removed.";
19827
+ const MESSAGE$22 = "`findDOMNode` crashes your app in React 19 because it was removed.";
19917
19828
  const noFindDomNode = defineRule({
19918
19829
  id: "no-find-dom-node",
19919
19830
  title: "findDOMNode breaks component encapsulation",
@@ -19924,7 +19835,7 @@ const noFindDomNode = defineRule({
19924
19835
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
19925
19836
  context.report({
19926
19837
  node: callee,
19927
- message: MESSAGE$26
19838
+ message: MESSAGE$22
19928
19839
  });
19929
19840
  return;
19930
19841
  }
@@ -19935,7 +19846,7 @@ const noFindDomNode = defineRule({
19935
19846
  if (callee.property.name !== "findDOMNode") return;
19936
19847
  context.report({
19937
19848
  node: callee.property,
19938
- message: MESSAGE$26
19849
+ message: MESSAGE$22
19939
19850
  });
19940
19851
  }
19941
19852
  } })
@@ -19976,41 +19887,6 @@ const noFullLodashImport = defineRule({
19976
19887
  } })
19977
19888
  });
19978
19889
  //#endregion
19979
- //#region src/plugin/rules/design/no-full-viewport-width.ts
19980
- const FULL_VIEWPORT_WIDTH_CLASS = /(?:^|\s)(?:min-)?w-(?:screen|\[100vw\])(?:$|\s)/;
19981
- const WIDTH_KEYS = new Set(["width", "minWidth"]);
19982
- const MESSAGE$25 = "`100vw` is wider than the viewport whenever a scrollbar is visible, so it triggers horizontal scroll on most desktops. Use `w-full` / `width: 100%` (with the parent's padding) for a full-bleed element.";
19983
- const noFullViewportWidth = defineRule({
19984
- id: "no-full-viewport-width",
19985
- title: "Full viewport width causes overflow",
19986
- tags: ["design", "test-noise"],
19987
- severity: "warn",
19988
- recommendation: "Prefer `w-full` (`width: 100%`) over `w-screen` / `100vw`. `100vw` ignores the scrollbar gutter and overflows horizontally.",
19989
- create: (context) => ({
19990
- JSXAttribute(node) {
19991
- const expression = getInlineStyleExpression(node);
19992
- if (!expression) return;
19993
- for (const property of expression.properties ?? []) {
19994
- const key = getStylePropertyKey(property);
19995
- if (!key || !WIDTH_KEYS.has(key)) continue;
19996
- const value = getStylePropertyStringValue(property);
19997
- if (value && value.trim().toLowerCase() === "100vw") context.report({
19998
- node: property,
19999
- message: MESSAGE$25
20000
- });
20001
- }
20002
- },
20003
- JSXOpeningElement(node) {
20004
- const classNameValue = getStringFromClassNameAttr(node);
20005
- if (!classNameValue) return;
20006
- if (FULL_VIEWPORT_WIDTH_CLASS.test(classNameValue)) context.report({
20007
- node,
20008
- message: MESSAGE$25
20009
- });
20010
- }
20011
- })
20012
- });
20013
- //#endregion
20014
19890
  //#region src/plugin/rules/architecture/no-generic-handler-names.ts
20015
19891
  const noGenericHandlerNames = defineRule({
20016
19892
  id: "no-generic-handler-names",
@@ -20073,7 +19949,7 @@ const noGiantComponent = defineRule({
20073
19949
  });
20074
19950
  //#endregion
20075
19951
  //#region src/plugin/constants/style.ts
20076
- const LAYOUT_PROPERTIES$1 = new Set([
19952
+ const LAYOUT_PROPERTIES = new Set([
20077
19953
  "width",
20078
19954
  "height",
20079
19955
  "top",
@@ -20143,6 +20019,17 @@ const noGlobalCssVariableAnimation = defineRule({
20143
20019
  } })
20144
20020
  });
20145
20021
  //#endregion
20022
+ //#region src/plugin/rules/design/utils/get-string-from-class-name-attr.ts
20023
+ const getStringFromClassNameAttr = (node) => {
20024
+ if (!isNodeOfType(node, "JSXOpeningElement")) return null;
20025
+ const classAttr = findJsxAttribute(node.attributes ?? [], "className");
20026
+ if (!classAttr?.value) return null;
20027
+ if (isNodeOfType(classAttr.value, "Literal") && typeof classAttr.value.value === "string") return classAttr.value.value;
20028
+ if (isNodeOfType(classAttr.value, "JSXExpressionContainer") && isNodeOfType(classAttr.value.expression, "Literal") && typeof classAttr.value.expression.value === "string") return classAttr.value.expression.value;
20029
+ if (isNodeOfType(classAttr.value, "JSXExpressionContainer") && isNodeOfType(classAttr.value.expression, "TemplateLiteral") && classAttr.value.expression.quasis?.length === 1) return classAttr.value.expression.quasis[0].value?.raw ?? null;
20030
+ return null;
20031
+ };
20032
+ //#endregion
20146
20033
  //#region src/plugin/rules/design/no-gradient-text.ts
20147
20034
  const noGradientText = defineRule({
20148
20035
  id: "no-gradient-text",
@@ -20201,7 +20088,7 @@ const noGrayOnColoredBackground = defineRule({
20201
20088
  });
20202
20089
  //#endregion
20203
20090
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
20204
- const MESSAGE$24 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
20091
+ const MESSAGE$21 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
20205
20092
  const noImgLazyWithHighFetchpriority = defineRule({
20206
20093
  id: "no-img-lazy-with-high-fetchpriority",
20207
20094
  title: "Lazy image with high fetchPriority",
@@ -20215,7 +20102,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
20215
20102
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
20216
20103
  context.report({
20217
20104
  node: node.name,
20218
- message: MESSAGE$24
20105
+ message: MESSAGE$21
20219
20106
  });
20220
20107
  } })
20221
20108
  });
@@ -20312,15 +20199,20 @@ const noInlineExhaustiveStyle = defineRule({
20312
20199
  severity: "warn",
20313
20200
  tags: ["test-noise", "react-jsx-only"],
20314
20201
  recommendation: "Move the styles to a CSS class, CSS module, Tailwind utilities, or a styled component. Big inline objects are hard to read and rebuild on every update.",
20315
- create: (context) => ({ JSXAttribute(node) {
20316
- const expression = getInlineStyleExpression(node);
20317
- if (!expression) return;
20318
- const propertyCount = expression.properties?.filter((property) => isNodeOfType(property, "Property")).length ?? 0;
20319
- if (propertyCount >= 8) context.report({
20320
- node: expression,
20321
- message: `This inline style has ${propertyCount} properties, which is hard to read & rebuilds every render. Move it to a CSS class, CSS module, or styled component.`
20322
- });
20323
- } })
20202
+ create: (context) => {
20203
+ if (isGeneratedImageRenderContext(context)) return {};
20204
+ return { JSXAttribute(node) {
20205
+ const expression = getInlineStyleExpression(node);
20206
+ if (!expression) return;
20207
+ const propertyCount = expression.properties?.filter((property) => isNodeOfType(property, "Property")).length ?? 0;
20208
+ if (propertyCount < 8) return;
20209
+ if (isGeneratedImageRenderContext(context, node.parent ?? void 0)) return;
20210
+ context.report({
20211
+ node: expression,
20212
+ message: `This inline style has ${propertyCount} properties, which is hard to read & rebuilds every render. Move it to a CSS class, CSS module, or styled component.`
20213
+ });
20214
+ } };
20215
+ }
20324
20216
  });
20325
20217
  //#endregion
20326
20218
  //#region src/plugin/rules/performance/no-inline-prop-on-memo-component.ts
@@ -20450,7 +20342,7 @@ const noIsMounted = defineRule({
20450
20342
  });
20451
20343
  //#endregion
20452
20344
  //#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
20453
- const MESSAGE$23 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
20345
+ const MESSAGE$20 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
20454
20346
  const isJsonMethodCall = (node, method) => {
20455
20347
  if (!isNodeOfType(node, "CallExpression")) return false;
20456
20348
  const callee = node.callee;
@@ -20467,13 +20359,13 @@ const noJsonParseStringifyClone = defineRule({
20467
20359
  if (!firstArgument || !isJsonMethodCall(firstArgument, "stringify")) return;
20468
20360
  context.report({
20469
20361
  node,
20470
- message: MESSAGE$23
20362
+ message: MESSAGE$20
20471
20363
  });
20472
20364
  } })
20473
20365
  });
20474
20366
  //#endregion
20475
20367
  //#region src/plugin/rules/correctness/no-jsx-element-type.ts
20476
- const MESSAGE$22 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
20368
+ const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
20477
20369
  const isJsxElementTypeReference = (node) => {
20478
20370
  if (!isNodeOfType(node, "TSTypeReference")) return false;
20479
20371
  const typeName = node.typeName;
@@ -20490,7 +20382,7 @@ const checkReturnType = (context, returnType) => {
20490
20382
  if (!typeAnnotation) return;
20491
20383
  if (isJsxElementTypeReference(typeAnnotation)) context.report({
20492
20384
  node: typeAnnotation,
20493
- message: MESSAGE$22
20385
+ message: MESSAGE$19
20494
20386
  });
20495
20387
  };
20496
20388
  const noJsxElementType = defineRule({
@@ -20600,7 +20492,7 @@ const noLayoutPropertyAnimation = defineRule({
20600
20492
  let propertyName = null;
20601
20493
  if (isNodeOfType(property.key, "Identifier")) propertyName = property.key.name;
20602
20494
  else if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") propertyName = property.key.value;
20603
- if (propertyName && LAYOUT_PROPERTIES$1.has(propertyName)) context.report({
20495
+ if (propertyName && LAYOUT_PROPERTIES.has(propertyName)) context.report({
20604
20496
  node: property,
20605
20497
  message: `This stutters because animating "${propertyName}" makes the browser redo page layout every frame, so animate transform or scale instead, or use the layout prop`
20606
20498
  });
@@ -20790,134 +20682,6 @@ const noLongTransitionDuration = defineRule({
20790
20682
  } })
20791
20683
  });
20792
20684
  //#endregion
20793
- //#region src/plugin/rules/design/utils/get-style-property-number-value.ts
20794
- const getStylePropertyNumberValue = (property) => {
20795
- if (!isNodeOfType(property, "Property")) return null;
20796
- if (isNodeOfType(property.value, "Literal") && typeof property.value.value === "number") return property.value.value;
20797
- if (isNodeOfType(property.value, "UnaryExpression") && property.value.operator === "-" && isNodeOfType(property.value.argument, "Literal") && typeof property.value.argument.value === "number") return -property.value.argument.value;
20798
- return null;
20799
- };
20800
- //#endregion
20801
- //#region src/plugin/rules/design/utils/get-wcag-contrast-ratio.ts
20802
- const linearizeChannel = (channel) => {
20803
- const normalized = channel / 255;
20804
- return normalized <= .03928 ? normalized / 12.92 : Math.pow((normalized + .055) / 1.055, 2.4);
20805
- };
20806
- const relativeLuminance = (color) => .2126 * linearizeChannel(color.red) + .7152 * linearizeChannel(color.green) + .0722 * linearizeChannel(color.blue);
20807
- const getWcagContrastRatio = (foreground, background) => {
20808
- const foregroundLuminance = relativeLuminance(foreground);
20809
- const backgroundLuminance = relativeLuminance(background);
20810
- const lighter = Math.max(foregroundLuminance, backgroundLuminance);
20811
- const darker = Math.min(foregroundLuminance, backgroundLuminance);
20812
- return (lighter + .05) / (darker + .05);
20813
- };
20814
- //#endregion
20815
- //#region src/plugin/rules/design/no-low-contrast-inline-style.ts
20816
- const UNRESOLVABLE = new Set([
20817
- "transparent",
20818
- "currentcolor",
20819
- "inherit",
20820
- "initial",
20821
- "unset",
20822
- "revert",
20823
- "none"
20824
- ]);
20825
- const resolveOpaqueColor = (raw) => {
20826
- const value = raw.trim().toLowerCase();
20827
- if (UNRESOLVABLE.has(value)) return null;
20828
- if (value === "white") return {
20829
- red: 255,
20830
- green: 255,
20831
- blue: 255
20832
- };
20833
- if (value === "black") return {
20834
- red: 0,
20835
- green: 0,
20836
- blue: 0
20837
- };
20838
- if (value.startsWith("var(")) return null;
20839
- if (/^#(?:[0-9a-f]{4}|[0-9a-f]{8})$/.test(value)) return null;
20840
- if (value.startsWith("hsl") || value.startsWith("oklch")) return null;
20841
- if (value.startsWith("rgb")) {
20842
- const inner = value.slice(value.indexOf("(") + 1, value.lastIndexOf(")"));
20843
- if (inner.includes("/") || inner.split(",").length >= 4) return null;
20844
- }
20845
- return parseColorToRgb(value);
20846
- };
20847
- const toPx = (property) => {
20848
- const numberValue = getStylePropertyNumberValue(property);
20849
- if (numberValue !== null) return numberValue;
20850
- const stringValue = getStylePropertyStringValue(property);
20851
- if (stringValue === null) return null;
20852
- const pxMatch = stringValue.match(/^([\d.]+)px$/);
20853
- if (pxMatch) return parseFloat(pxMatch[1]);
20854
- const remMatch = stringValue.match(/^([\d.]+)rem$/);
20855
- if (remMatch) return parseFloat(remMatch[1]) * 16;
20856
- return null;
20857
- };
20858
- const isBoldWeight = (property) => {
20859
- const numberValue = getStylePropertyNumberValue(property);
20860
- if (numberValue !== null) return numberValue >= 700;
20861
- const stringValue = getStylePropertyStringValue(property);
20862
- if (stringValue === null) return false;
20863
- if (stringValue === "bold" || stringValue === "bolder") return true;
20864
- const numericWeight = Number(stringValue);
20865
- return Number.isFinite(numericWeight) && numericWeight >= 700;
20866
- };
20867
- const noLowContrastInlineStyle = defineRule({
20868
- id: "no-low-contrast-inline-style",
20869
- title: "Low-contrast text in inline style",
20870
- tags: ["test-noise"],
20871
- severity: "warn",
20872
- category: "Accessibility",
20873
- recommendation: "Text needs a WCAG contrast ratio of at least 4.5:1 (3:1 for large/bold text) against its background. Darken or lighten one of the colors until it passes.",
20874
- create: (context) => ({ JSXAttribute(node) {
20875
- const expression = getInlineStyleExpression(node);
20876
- if (!expression) return;
20877
- const properties = expression.properties ?? [];
20878
- if (properties.some((property) => property.type === "SpreadElement")) return;
20879
- let foreground = null;
20880
- let backgroundColorRaw = null;
20881
- let backgroundShorthandRaw = null;
20882
- let backgroundIsUnknown = false;
20883
- let fontSizePx = null;
20884
- let isBold = false;
20885
- for (const property of properties) {
20886
- const key = getStylePropertyKey(property);
20887
- if (!key) continue;
20888
- if (key === "backgroundImage") {
20889
- backgroundIsUnknown = true;
20890
- continue;
20891
- }
20892
- if (key === "fontSize" && property.type === "Property") {
20893
- fontSizePx = toPx(property);
20894
- continue;
20895
- }
20896
- if (key === "fontWeight" && property.type === "Property") {
20897
- isBold = isBoldWeight(property);
20898
- continue;
20899
- }
20900
- const stringValue = getStylePropertyStringValue(property);
20901
- if (key === "color") {
20902
- if (stringValue !== null) foreground = resolveOpaqueColor(stringValue);
20903
- } else if (key === "backgroundColor") backgroundColorRaw = stringValue;
20904
- else if (key === "background") if (stringValue === null) backgroundIsUnknown = true;
20905
- else backgroundShorthandRaw = stringValue;
20906
- }
20907
- if (backgroundIsUnknown) return;
20908
- if (backgroundColorRaw !== null && backgroundShorthandRaw !== null) return;
20909
- const backgroundRaw = backgroundColorRaw ?? backgroundShorthandRaw;
20910
- const background = backgroundRaw === null ? null : resolveOpaqueColor(backgroundRaw);
20911
- if (!foreground || !background) return;
20912
- const threshold = fontSizePx === null || fontSizePx >= 24 || isBold && fontSizePx >= 18.66 ? 3 : WCAG_CONTRAST_NORMAL_MIN;
20913
- const ratio = getWcagContrastRatio(foreground, background);
20914
- if (ratio < threshold) context.report({
20915
- node,
20916
- message: `Your users struggle to read this text: its contrast against the background is ${ratio.toFixed(2)}:1, below the ${threshold}:1 WCAG minimum, so darken or lighten one of the colors.`
20917
- });
20918
- } })
20919
- });
20920
- //#endregion
20921
20685
  //#region src/plugin/utils/is-boolean-prefixed-prop-name.ts
20922
20686
  const BOOLEAN_PROP_PREFIX_PATTERN = /^(?:is|has|should|can|show|hide|enable|disable|with)[A-Z]/;
20923
20687
  const isBooleanPrefixedPropName = (propName) => BOOLEAN_PROP_PREFIX_PATTERN.test(propName);
@@ -21073,7 +20837,7 @@ const noMoment = defineRule({
21073
20837
  });
21074
20838
  //#endregion
21075
20839
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
21076
- const MESSAGE$21 = "This file declares several components, so each component is harder to find, test, and change.";
20840
+ const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
21077
20841
  const resolveSettings$16 = (settings) => {
21078
20842
  const reactDoctor = settings?.["react-doctor"];
21079
20843
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -21395,7 +21159,7 @@ const noMultiComp = defineRule({
21395
21159
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
21396
21160
  for (const component of flagged.slice(1)) context.report({
21397
21161
  node: component.reportNode,
21398
- message: MESSAGE$21
21162
+ message: MESSAGE$18
21399
21163
  });
21400
21164
  } };
21401
21165
  }
@@ -21563,7 +21327,7 @@ const resolveReducerFunction = (node, currentFilename) => {
21563
21327
  };
21564
21328
  //#endregion
21565
21329
  //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
21566
- const MESSAGE$20 = "This reducer changes state in place, so your update is silently skipped.";
21330
+ const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
21567
21331
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
21568
21332
  "copyWithin",
21569
21333
  "fill",
@@ -21773,7 +21537,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21773
21537
  reportedNodes.add(options.crossFileConsumerCallSite);
21774
21538
  context.report({
21775
21539
  node: options.crossFileConsumerCallSite,
21776
- message: `${MESSAGE$20} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21540
+ message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21777
21541
  });
21778
21542
  return;
21779
21543
  }
@@ -21782,7 +21546,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21782
21546
  reportedNodes.add(mutation.node);
21783
21547
  context.report({
21784
21548
  node: mutation.node,
21785
- message: MESSAGE$20
21549
+ message: MESSAGE$17
21786
21550
  });
21787
21551
  }
21788
21552
  };
@@ -22054,7 +21818,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
22054
21818
  });
22055
21819
  //#endregion
22056
21820
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
22057
- const MESSAGE$19 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
21821
+ const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
22058
21822
  const resolveSettings$14 = (settings) => {
22059
21823
  const reactDoctor = settings?.["react-doctor"];
22060
21824
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noNoninteractiveTabindex ?? {} : {};
@@ -22082,7 +21846,7 @@ const noNoninteractiveTabindex = defineRule({
22082
21846
  if (numeric === null) {
22083
21847
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues) context.report({
22084
21848
  node: tabIndex,
22085
- message: MESSAGE$19
21849
+ message: MESSAGE$16
22086
21850
  });
22087
21851
  return;
22088
21852
  }
@@ -22095,7 +21859,7 @@ const noNoninteractiveTabindex = defineRule({
22095
21859
  if (!roleAttribute) {
22096
21860
  context.report({
22097
21861
  node: tabIndex,
22098
- message: MESSAGE$19
21862
+ message: MESSAGE$16
22099
21863
  });
22100
21864
  return;
22101
21865
  }
@@ -22109,12 +21873,20 @@ const noNoninteractiveTabindex = defineRule({
22109
21873
  }
22110
21874
  context.report({
22111
21875
  node: tabIndex,
22112
- message: MESSAGE$19
21876
+ message: MESSAGE$16
22113
21877
  });
22114
21878
  } };
22115
21879
  }
22116
21880
  });
22117
21881
  //#endregion
21882
+ //#region src/plugin/rules/design/utils/get-style-property-number-value.ts
21883
+ const getStylePropertyNumberValue = (property) => {
21884
+ if (!isNodeOfType(property, "Property")) return null;
21885
+ if (isNodeOfType(property.value, "Literal") && typeof property.value.value === "number") return property.value.value;
21886
+ if (isNodeOfType(property.value, "UnaryExpression") && property.value.operator === "-" && isNodeOfType(property.value.argument, "Literal") && typeof property.value.argument.value === "number") return -property.value.argument.value;
21887
+ return null;
21888
+ };
21889
+ //#endregion
22118
21890
  //#region src/plugin/rules/design/no-outline-none.ts
22119
21891
  const noOutlineNone = defineRule({
22120
21892
  id: "no-outline-none",
@@ -22792,7 +22564,7 @@ const noRandomKey = defineRule({
22792
22564
  });
22793
22565
  //#endregion
22794
22566
  //#region src/plugin/rules/react-builtins/no-react-children.ts
22795
- const MESSAGE$18 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
22567
+ const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
22796
22568
  const isChildrenIdentifier = (node, contextNode) => {
22797
22569
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
22798
22570
  return isImportedFromModule(contextNode, "Children", "react");
@@ -22818,13 +22590,13 @@ const noReactChildren = defineRule({
22818
22590
  if (isChildrenIdentifier(memberObject, node)) {
22819
22591
  context.report({
22820
22592
  node: calleeOuter,
22821
- message: MESSAGE$18
22593
+ message: MESSAGE$15
22822
22594
  });
22823
22595
  return;
22824
22596
  }
22825
22597
  if (isReactNamespaceMember(memberObject, node)) context.report({
22826
22598
  node: calleeOuter,
22827
- message: MESSAGE$18
22599
+ message: MESSAGE$15
22828
22600
  });
22829
22601
  } })
22830
22602
  });
@@ -22935,86 +22707,6 @@ const noReact19DeprecatedApis = defineRule({
22935
22707
  })
22936
22708
  });
22937
22709
  //#endregion
22938
- //#region src/plugin/rules/design/no-redundant-display-class.ts
22939
- const BLOCK_DEFAULT_TAGS = new Set([
22940
- "div",
22941
- "p",
22942
- "section",
22943
- "article",
22944
- "main",
22945
- "header",
22946
- "footer",
22947
- "nav",
22948
- "aside",
22949
- "figure",
22950
- "figcaption",
22951
- "blockquote",
22952
- "form",
22953
- "fieldset",
22954
- "address",
22955
- "pre",
22956
- "ul",
22957
- "ol",
22958
- "dl",
22959
- "dt",
22960
- "dd",
22961
- "h1",
22962
- "h2",
22963
- "h3",
22964
- "h4",
22965
- "h5",
22966
- "h6"
22967
- ]);
22968
- const INLINE_DEFAULT_TAGS = new Set([
22969
- "span",
22970
- "a",
22971
- "b",
22972
- "i",
22973
- "em",
22974
- "strong",
22975
- "small",
22976
- "code",
22977
- "abbr",
22978
- "cite",
22979
- "label",
22980
- "mark",
22981
- "q",
22982
- "s",
22983
- "u",
22984
- "sub",
22985
- "sup",
22986
- "kbd",
22987
- "samp",
22988
- "var",
22989
- "time"
22990
- ]);
22991
- const STANDALONE_BLOCK = /(?:^|\s)block(?:$|\s)/;
22992
- const STANDALONE_INLINE = /(?:^|\s)inline(?:$|\s)/;
22993
- const noRedundantDisplayClass = defineRule({
22994
- id: "no-redundant-display-class",
22995
- title: "Redundant display utility",
22996
- tags: ["design", "test-noise"],
22997
- severity: "warn",
22998
- recommendation: "Drop the display class that matches the element's default (`block` on a `<div>`, `inline` on a `<span>`). It is pure noise; keep only display changes like `flex`, `grid`, or `hidden`.",
22999
- create: (context) => ({ JSXOpeningElement(node) {
23000
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
23001
- const tagName = node.name.name;
23002
- const classNameValue = getStringFromClassNameAttr(node);
23003
- if (!classNameValue) return;
23004
- if (BLOCK_DEFAULT_TAGS.has(tagName) && STANDALONE_BLOCK.test(classNameValue)) {
23005
- context.report({
23006
- node,
23007
- message: `\`block\` is the default display of \`<${tagName}>\`, so the class does nothing — remove it.`
23008
- });
23009
- return;
23010
- }
23011
- if (INLINE_DEFAULT_TAGS.has(tagName) && STANDALONE_INLINE.test(classNameValue)) context.report({
23012
- node,
23013
- message: `\`inline\` is the default display of \`<${tagName}>\`, so the class does nothing — remove it.`
23014
- });
23015
- } })
23016
- });
23017
- //#endregion
23018
22710
  //#region src/plugin/constants/aria-element-roles.ts
23019
22711
  const ELEMENT_ROLE_PAIRS = [
23020
22712
  ["a", "link"],
@@ -23227,7 +22919,7 @@ const noRenderPropChildren = defineRule({
23227
22919
  });
23228
22920
  //#endregion
23229
22921
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
23230
- const MESSAGE$17 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
22922
+ const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
23231
22923
  const isReactDomRenderCall = (node) => {
23232
22924
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
23233
22925
  if (!isNodeOfType(node.callee.object, "Identifier")) return false;
@@ -23251,7 +22943,7 @@ const noRenderReturnValue = defineRule({
23251
22943
  if (!isUsedAsReturnValue(node.parent)) return;
23252
22944
  context.report({
23253
22945
  node: node.callee,
23254
- message: MESSAGE$17
22946
+ message: MESSAGE$14
23255
22947
  });
23256
22948
  } })
23257
22949
  });
@@ -23949,7 +23641,7 @@ const getParentComponent = (node) => {
23949
23641
  };
23950
23642
  //#endregion
23951
23643
  //#region src/plugin/rules/react-builtins/no-set-state.ts
23952
- const MESSAGE$16 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
23644
+ const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
23953
23645
  const noSetState = defineRule({
23954
23646
  id: "no-set-state",
23955
23647
  title: "Local class state forbidden",
@@ -23964,7 +23656,7 @@ const noSetState = defineRule({
23964
23656
  if (!getParentComponent(node)) return;
23965
23657
  context.report({
23966
23658
  node: node.callee,
23967
- message: MESSAGE$16
23659
+ message: MESSAGE$13
23968
23660
  });
23969
23661
  } })
23970
23662
  });
@@ -24126,7 +23818,7 @@ const isAbstractRole = (openingElement, settings) => {
24126
23818
  };
24127
23819
  //#endregion
24128
23820
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
24129
- const MESSAGE$15 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
23821
+ const MESSAGE$12 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
24130
23822
  const DEFAULT_HANDLERS = [
24131
23823
  "onClick",
24132
23824
  "onMouseDown",
@@ -24186,7 +23878,7 @@ const noStaticElementInteractions = defineRule({
24186
23878
  if (!roleAttribute || !roleAttribute.value) {
24187
23879
  context.report({
24188
23880
  node: node.name,
24189
- message: MESSAGE$15
23881
+ message: MESSAGE$12
24190
23882
  });
24191
23883
  return;
24192
23884
  }
@@ -24196,14 +23888,14 @@ const noStaticElementInteractions = defineRule({
24196
23888
  if (firstRole && (isInteractiveRole(firstRole) || isNonInteractiveRole(firstRole))) return;
24197
23889
  context.report({
24198
23890
  node: node.name,
24199
- message: MESSAGE$15
23891
+ message: MESSAGE$12
24200
23892
  });
24201
23893
  return;
24202
23894
  }
24203
23895
  if (isNodeOfType(attributeValue, "JSXExpressionContainer") && settings.allowExpressionValues) return;
24204
23896
  context.report({
24205
23897
  node: node.name,
24206
- message: MESSAGE$15
23898
+ message: MESSAGE$12
24207
23899
  });
24208
23900
  } };
24209
23901
  }
@@ -24306,43 +23998,8 @@ const noStringRefs = defineRule({
24306
23998
  }
24307
23999
  });
24308
24000
  //#endregion
24309
- //#region src/plugin/rules/design/no-svg-currentcolor-with-fill-class.ts
24310
- const hasColorUtility = (classNameValue, prefix) => classNameValue.split(/\s+/).some((token) => {
24311
- if (token.includes(":")) return false;
24312
- if (!token.startsWith(prefix)) return false;
24313
- const value = token.slice(prefix.length);
24314
- if (value === "" || value === "current") return false;
24315
- if (/^\d/.test(value) || /^\[\d/.test(value)) return false;
24316
- return true;
24317
- });
24318
- const isCurrentColor = (attribute) => {
24319
- const value = getJsxPropStringValue(attribute);
24320
- return value !== null && value.trim().toLowerCase() === "currentcolor";
24321
- };
24322
- const noSvgCurrentcolorWithFillClass = defineRule({
24323
- id: "no-svg-currentcolor-with-fill-class",
24324
- title: "currentColor fights a fill/stroke class",
24325
- tags: ["design", "test-noise"],
24326
- severity: "warn",
24327
- recommendation: "Pick one source of truth: drop the `fill=\"currentColor\"` attribute and keep the `fill-*` class, or use `fill-current` to inherit the text color. Having both means the class silently wins.",
24328
- create: (context) => ({ JSXOpeningElement(node) {
24329
- const classNameValue = getStringFromClassNameAttr(node);
24330
- if (!classNameValue) return;
24331
- for (const paint of ["fill", "stroke"]) {
24332
- const attribute = findJsxAttribute(node.attributes, paint);
24333
- if (attribute && isCurrentColor(attribute) && hasColorUtility(classNameValue, `${paint}-`)) {
24334
- context.report({
24335
- node: attribute,
24336
- message: `\`${paint}="currentColor"\` and a \`${paint}-*\` color class on the same element conflict — the class wins. Remove one, or use \`${paint}-current\` to inherit the text color.`
24337
- });
24338
- return;
24339
- }
24340
- }
24341
- } })
24342
- });
24343
- //#endregion
24344
24001
  //#region src/plugin/rules/js-performance/no-sync-xhr.ts
24345
- const MESSAGE$14 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
24002
+ const MESSAGE$11 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
24346
24003
  const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
24347
24004
  const noSyncXhr = defineRule({
24348
24005
  id: "no-sync-xhr",
@@ -24357,103 +24014,13 @@ const noSyncXhr = defineRule({
24357
24014
  if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
24358
24015
  context.report({
24359
24016
  node,
24360
- message: MESSAGE$14
24361
- });
24362
- } })
24363
- });
24364
- //#endregion
24365
- //#region src/plugin/rules/design/no-tailwind-layout-transition.ts
24366
- const ARBITRARY_TRANSITION_PROPERTY = /transition-\[([^\]]+)\]/g;
24367
- const LAYOUT_PROPERTIES = new Set([
24368
- "width",
24369
- "height",
24370
- "min-width",
24371
- "max-width",
24372
- "min-height",
24373
- "max-height",
24374
- "top",
24375
- "left",
24376
- "right",
24377
- "bottom",
24378
- "inset",
24379
- "inset-block",
24380
- "inset-inline",
24381
- "margin",
24382
- "margin-top",
24383
- "margin-right",
24384
- "margin-bottom",
24385
- "margin-left",
24386
- "margin-block",
24387
- "margin-inline",
24388
- "padding",
24389
- "padding-top",
24390
- "padding-right",
24391
- "padding-bottom",
24392
- "padding-left",
24393
- "padding-block",
24394
- "padding-inline"
24395
- ]);
24396
- const noTailwindLayoutTransition = defineRule({
24397
- id: "no-tailwind-layout-transition",
24398
- title: "Animating a layout property",
24399
- tags: ["design", "test-noise"],
24400
- severity: "warn",
24401
- category: "Performance",
24402
- recommendation: "Animate `transform` and `opacity` instead, since they skip layout and run on the compositor. For height, animate `grid-template-rows` from `0fr` to `1fr`.",
24403
- create: (context) => ({ JSXOpeningElement(node) {
24404
- const classNameValue = getStringFromClassNameAttr(node);
24405
- if (!classNameValue) return;
24406
- for (const transitionMatch of classNameValue.matchAll(ARBITRARY_TRANSITION_PROPERTY)) {
24407
- const animatedProperties = transitionMatch[1];
24408
- const layoutProperty = animatedProperties.split(",").map((property) => property.trim()).find((property) => LAYOUT_PROPERTIES.has(property));
24409
- if (layoutProperty) context.report({
24410
- node,
24411
- message: `Your users see janky animation because \`transition-[${animatedProperties}]\` animates "${layoutProperty}", a layout property the browser recomputes every frame, so animate transform & opacity instead.`
24412
- });
24413
- }
24414
- } })
24415
- });
24416
- //#endregion
24417
- //#region src/plugin/rules/a11y/no-target-blank-without-rel.ts
24418
- const MESSAGE$13 = "`<a target=\"_blank\">` without `rel=\"noopener\"` lets the opened page script your tab via `window.opener` (reverse tabnabbing). Add `rel=\"noopener noreferrer\"`.";
24419
- const targetIsBlank = (attribute) => {
24420
- const stringValue = getJsxPropStringValue(attribute);
24421
- if (stringValue !== null) return stringValue === "_blank";
24422
- const value = attribute.value;
24423
- if (value && isNodeOfType(value, "JSXExpressionContainer")) {
24424
- const expression = value.expression;
24425
- if (isNodeOfType(expression, "Literal") && expression.value === "_blank") return true;
24426
- }
24427
- return false;
24428
- };
24429
- const noTargetBlankWithoutRel = defineRule({
24430
- id: "no-target-blank-without-rel",
24431
- title: "target=_blank without rel=noopener",
24432
- severity: "warn",
24433
- recommendation: "Add `rel=\"noopener noreferrer\"` to every `target=\"_blank\"` link. `noopener` blocks reverse tabnabbing; `noreferrer` also strips the `Referer` header.",
24434
- create: (context) => ({ JSXOpeningElement(node) {
24435
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
24436
- const tagName = node.name.name;
24437
- if (tagName !== "a" && tagName !== "area") return;
24438
- if (hasJsxSpreadAttribute(node.attributes)) return;
24439
- const targetAttribute = findJsxAttribute(node.attributes, "target");
24440
- if (!targetAttribute || !targetIsBlank(targetAttribute)) return;
24441
- const relAttribute = findJsxAttribute(node.attributes, "rel");
24442
- if (relAttribute) {
24443
- const relValue = getJsxPropStringValue(relAttribute);
24444
- if (relValue === null) return;
24445
- const tokens = relValue.toLowerCase().split(/\s+/);
24446
- if (tokens.includes("noopener") || tokens.includes("noreferrer")) return;
24447
- }
24448
- context.report({
24449
- node: node.name,
24450
- message: MESSAGE$13
24017
+ message: MESSAGE$11
24451
24018
  });
24452
24019
  } })
24453
24020
  });
24454
24021
  //#endregion
24455
24022
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
24456
- const MESSAGE$12 = "This value is `undefined` because function components have no `this`.";
24023
+ const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
24457
24024
  const isInsideClassMethod = (node, customClassFactoryNames) => {
24458
24025
  let ancestor = node.parent;
24459
24026
  while (ancestor) {
@@ -24522,7 +24089,7 @@ const noThisInSfc = defineRule({
24522
24089
  if (!looksLikeFunctionComponent(enclosingFunction)) return;
24523
24090
  context.report({
24524
24091
  node,
24525
- message: MESSAGE$12
24092
+ message: MESSAGE$10
24526
24093
  });
24527
24094
  } };
24528
24095
  }
@@ -24560,39 +24127,26 @@ const noTinyText = defineRule({
24560
24127
  });
24561
24128
  //#endregion
24562
24129
  //#region src/plugin/rules/performance/no-transition-all.ts
24563
- const hasTransitionAllClass = (classNameValue) => getClassNameTokens(classNameValue).some((token) => token === "transition-all");
24564
- const TAILWIND_MESSAGE = "Your users see janky animation because `transition-all` animates every property that changes, including expensive layout ones and instant ones like focus rings. Name the properties: `transition-colors`, `transition-opacity`, or `transition-transform`.";
24565
24130
  const noTransitionAll = defineRule({
24566
24131
  id: "no-transition-all",
24567
24132
  title: "transition: all animates everything",
24568
24133
  tags: ["test-noise"],
24569
24134
  severity: "warn",
24570
24135
  recommendation: "List the specific properties: `transition: \"opacity 200ms, transform 200ms\"`. In Tailwind, use `transition-colors`, `transition-opacity`, or `transition-transform`",
24571
- create: (context) => ({
24572
- JSXAttribute(node) {
24573
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "style") return;
24574
- if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
24575
- const expression = node.value.expression;
24576
- if (!isNodeOfType(expression, "ObjectExpression")) return;
24577
- for (const property of expression.properties ?? []) {
24578
- if (!isNodeOfType(property, "Property")) continue;
24579
- const key = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
24580
- if (key !== "transition" && key !== "transitionProperty") continue;
24581
- if (isNodeOfType(property.value, "Literal") && typeof property.value.value === "string" && property.value.value.trim().startsWith("all")) context.report({
24582
- node: property,
24583
- message: "This can stutter because transition: \"all\" animates every property, even slow layout ones, so list only the properties you actually change"
24584
- });
24585
- }
24586
- },
24587
- JSXOpeningElement(node) {
24588
- const classNameValue = getStringFromClassNameAttr(node);
24589
- if (!classNameValue) return;
24590
- if (hasTransitionAllClass(classNameValue)) context.report({
24591
- node,
24592
- message: TAILWIND_MESSAGE
24136
+ create: (context) => ({ JSXAttribute(node) {
24137
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "style") return;
24138
+ if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
24139
+ const expression = node.value.expression;
24140
+ if (!isNodeOfType(expression, "ObjectExpression")) return;
24141
+ for (const property of expression.properties ?? []) {
24142
+ if (!isNodeOfType(property, "Property")) continue;
24143
+ if ((isNodeOfType(property.key, "Identifier") ? property.key.name : null) !== "transition") continue;
24144
+ if (isNodeOfType(property.value, "Literal") && typeof property.value.value === "string" && property.value.value.startsWith("all")) context.report({
24145
+ node: property,
24146
+ message: "This can stutter because transition: \"all\" animates every property, even slow layout ones, so list only the properties you actually change"
24593
24147
  });
24594
24148
  }
24595
- })
24149
+ } })
24596
24150
  });
24597
24151
  //#endregion
24598
24152
  //#region src/plugin/rules/correctness/no-uncontrolled-input.ts
@@ -24636,6 +24190,7 @@ const collectUndefinedInitialStateNames = (componentBody) => {
24636
24190
  }
24637
24191
  return stateNames;
24638
24192
  };
24193
+ const hasJsxSpreadAttribute = (attributes) => attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
24639
24194
  const noUncontrolledInput = defineRule({
24640
24195
  id: "no-uncontrolled-input",
24641
24196
  title: "Uncontrolled input value",
@@ -24739,38 +24294,6 @@ const noUnescapedEntities = defineRule({
24739
24294
  } })
24740
24295
  });
24741
24296
  //#endregion
24742
- //#region src/plugin/rules/a11y/no-uninformative-aria-label.ts
24743
- const UNINFORMATIVE_LABELS = new Set([
24744
- "icon",
24745
- "button",
24746
- "image",
24747
- "img",
24748
- "link",
24749
- "graphic",
24750
- "svg",
24751
- "picture",
24752
- "element",
24753
- "field",
24754
- "input"
24755
- ]);
24756
- const MESSAGE$11 = "An `aria-label` should name the action or destination, not the element type — this value tells screen-reader users nothing. Use something like `aria-label=\"Search\"` or `aria-label=\"Close dialog\"`.";
24757
- const noUninformativeAriaLabel = defineRule({
24758
- id: "no-uninformative-aria-label",
24759
- title: "Uninformative aria-label",
24760
- severity: "warn",
24761
- recommendation: "Name the action, not the element type: `aria-label=\"Search\"`, not `aria-label=\"icon\"` or `aria-label=\"button\"`.",
24762
- create: (context) => ({ JSXOpeningElement(node) {
24763
- const ariaLabel = findJsxAttribute(node.attributes, "aria-label");
24764
- if (!ariaLabel) return;
24765
- const labelValue = getJsxPropStringValue(ariaLabel);
24766
- if (labelValue === null) return;
24767
- if (UNINFORMATIVE_LABELS.has(labelValue.trim().toLowerCase())) context.report({
24768
- node: ariaLabel,
24769
- message: MESSAGE$11
24770
- });
24771
- } })
24772
- });
24773
- //#endregion
24774
24297
  //#region src/plugin/constants/dom-aria-properties.ts
24775
24298
  const ARIA_PROPERTY_NAMES = new Set([
24776
24299
  "activedescendant",
@@ -25905,15 +25428,8 @@ const expressionContainsJsxOrCreateElement = (root) => {
25905
25428
  visit(root);
25906
25429
  return found;
25907
25430
  };
25908
- const classExtendsReactComponent$1 = (classNode) => {
25909
- const superClass = classNode.superClass;
25910
- if (!superClass) return false;
25911
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
25912
- if (isNodeOfType(superClass, "MemberExpression") && isNodeOfType(superClass.object, "Identifier") && superClass.object.name === "React" && isNodeOfType(superClass.property, "Identifier") && (superClass.property.name === "Component" || superClass.property.name === "PureComponent")) return true;
25913
- return false;
25914
- };
25915
25431
  const isReactClassComponent = (classNode) => {
25916
- if (classExtendsReactComponent$1(classNode)) return true;
25432
+ if (isEs6Component(classNode)) return true;
25917
25433
  return expressionContainsJsxOrCreateElement(classNode);
25918
25434
  };
25919
25435
  const findEnclosingComponent = (node) => {
@@ -26073,7 +25589,7 @@ const noUnstableNestedComponents = defineRule({
26073
25589
  create: (context) => {
26074
25590
  const settings = resolveSettings$8(context.settings);
26075
25591
  const renderPropRegex = compileGlob(settings.propNamePattern);
26076
- const reportCandidate = (candidateNode, reportNode, candidateName) => {
25592
+ const reportCandidate = (candidateNode, reportNode) => {
26077
25593
  if (isFirstArgumentOfHocCall(candidateNode)) return;
26078
25594
  if (isReturnOfMapCallback(candidateNode)) return;
26079
25595
  const propInfo = isComponentDeclaredInProp(candidateNode);
@@ -26094,7 +25610,7 @@ const noUnstableNestedComponents = defineRule({
26094
25610
  const inferredName = inferFunctionLikeName(node);
26095
25611
  const propInfo = isComponentDeclaredInProp(node);
26096
25612
  if (!(inferredName !== null && isReactComponentName(inferredName) || propInfo !== null || isObjectCallbackCandidate(node))) return;
26097
- reportCandidate(node, node, inferredName);
25613
+ reportCandidate(node, node);
26098
25614
  };
26099
25615
  return {
26100
25616
  FunctionDeclaration: checkFunctionLike,
@@ -26104,18 +25620,18 @@ const noUnstableNestedComponents = defineRule({
26104
25620
  if (!node.id) return;
26105
25621
  if (!isReactComponentName(node.id.name)) return;
26106
25622
  if (!isReactClassComponent(node)) return;
26107
- reportCandidate(node, node, node.id.name);
25623
+ reportCandidate(node, node);
26108
25624
  },
26109
25625
  ClassExpression(node) {
26110
25626
  const inferredName = node.id?.name ?? inferFunctionLikeName(node);
26111
25627
  if (!inferredName || !isReactComponentName(inferredName)) return;
26112
25628
  if (!isReactClassComponent(node)) return;
26113
- reportCandidate(node, node, inferredName);
25629
+ reportCandidate(node, node);
26114
25630
  },
26115
25631
  CallExpression(node) {
26116
25632
  if (!isHocCallee$1(node)) return;
26117
25633
  if (!hocCallContainsComponent(node)) return;
26118
- reportCandidate(node, node, null);
25634
+ reportCandidate(node, node);
26119
25635
  }
26120
25636
  };
26121
25637
  }
@@ -26242,7 +25758,7 @@ const noWideLetterSpacing = defineRule({
26242
25758
  //#endregion
26243
25759
  //#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
26244
25760
  const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
26245
- const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
25761
+ const MESSAGE$9 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
26246
25762
  const resolveSettings$7 = (settings) => {
26247
25763
  const reactDoctor = settings?.["react-doctor"];
26248
25764
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -26276,7 +25792,7 @@ const noWillUpdateSetState = defineRule({
26276
25792
  if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
26277
25793
  context.report({
26278
25794
  node: node.callee,
26279
- message: MESSAGE$10
25795
+ message: MESSAGE$9
26280
25796
  });
26281
25797
  } };
26282
25798
  }
@@ -26555,13 +26071,6 @@ const skipTsExpression = (expression) => {
26555
26071
  if (expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSNonNullExpression") return skipTsExpression(expression.expression);
26556
26072
  return expression;
26557
26073
  };
26558
- const classExtendsReactComponent = (classNode) => {
26559
- const superClass = classNode.superClass;
26560
- if (!superClass) return false;
26561
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
26562
- if (isNodeOfType(superClass, "MemberExpression") && isNodeOfType(superClass.object, "Identifier") && superClass.object.name === "React" && isNodeOfType(superClass.property, "Identifier") && (superClass.property.name === "Component" || superClass.property.name === "PureComponent")) return true;
26563
- return false;
26564
- };
26565
26074
  const isReactCreateContext = (initializer) => {
26566
26075
  if (!initializer) return false;
26567
26076
  const expression = skipTsExpression(initializer);
@@ -26752,7 +26261,7 @@ const onlyExportComponents = defineRule({
26752
26261
  if (stripped.id) {
26753
26262
  const idNode = stripped.id;
26754
26263
  isExportedNodeIds.add(stripped);
26755
- if (isReactComponentName(idNode.name) && classExtendsReactComponent(stripped)) hasReactExport = true;
26264
+ if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
26756
26265
  else exports.push({
26757
26266
  kind: "non-component",
26758
26267
  reportNode: idNode
@@ -26812,7 +26321,7 @@ const onlyExportComponents = defineRule({
26812
26321
  exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
26813
26322
  } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
26814
26323
  isExportedNodeIds.add(declaration);
26815
- if (isReactComponentName(declaration.id.name) && classExtendsReactComponent(declaration)) exports.push({ kind: "react-component" });
26324
+ if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
26816
26325
  else exports.push({
26817
26326
  kind: "non-component",
26818
26327
  reportNode: declaration.id
@@ -27154,7 +26663,7 @@ const preactNoRenderArguments = defineRule({
27154
26663
  });
27155
26664
  //#endregion
27156
26665
  //#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
27157
- const MESSAGE$9 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
26666
+ const MESSAGE$8 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
27158
26667
  const preactPreferOndblclick = defineRule({
27159
26668
  id: "preact-prefer-ondblclick",
27160
26669
  title: "onDoubleClick instead of onDblClick",
@@ -27169,7 +26678,7 @@ const preactPreferOndblclick = defineRule({
27169
26678
  if (!onDoubleClickAttribute) return;
27170
26679
  context.report({
27171
26680
  node: onDoubleClickAttribute,
27172
- message: MESSAGE$9
26681
+ message: MESSAGE$8
27173
26682
  });
27174
26683
  } })
27175
26684
  });
@@ -27209,42 +26718,6 @@ const preactPreferOninput = defineRule({
27209
26718
  } })
27210
26719
  });
27211
26720
  //#endregion
27212
- //#region src/plugin/rules/design/prefer-dvh-over-vh.ts
27213
- const FULL_VIEWPORT_HEIGHT_CLASS = /(?:^|\s)(?:\w+:)*(?:min-)?h-(?:screen|\[100vh\])(?=$|[\s])/;
27214
- const HEIGHT_KEYS = new Set(["height", "minHeight"]);
27215
- const MESSAGE$8 = "`100vh` is taller than the visible viewport on mobile (it ignores the browser's dynamic toolbars), so full-height layouts get clipped. Use the dynamic-viewport unit: `h-dvh` / `min-h-dvh` (or `100dvh`).";
27216
- const preferDvhOverVh = defineRule({
27217
- id: "prefer-dvh-over-vh",
27218
- title: "Use dvh instead of vh for full height",
27219
- tags: ["design", "test-noise"],
27220
- severity: "warn",
27221
- requires: ["tailwind:3.4"],
27222
- recommendation: "Prefer `dvh` over `vh` for full-height elements. `100vh` overflows under mobile browser chrome; `100dvh` tracks the visible viewport. (`h-dvh`/`min-h-dvh` need Tailwind 3.4+.)",
27223
- create: (context) => ({
27224
- JSXAttribute(node) {
27225
- const expression = getInlineStyleExpression(node);
27226
- if (!expression) return;
27227
- for (const property of expression.properties ?? []) {
27228
- const key = getStylePropertyKey(property);
27229
- if (!key || !HEIGHT_KEYS.has(key)) continue;
27230
- const value = getStylePropertyStringValue(property);
27231
- if (value && value.trim().toLowerCase() === "100vh") context.report({
27232
- node: property,
27233
- message: MESSAGE$8
27234
- });
27235
- }
27236
- },
27237
- JSXOpeningElement(node) {
27238
- const classNameValue = getStringFromClassNameAttr(node);
27239
- if (!classNameValue) return;
27240
- if (FULL_VIEWPORT_HEIGHT_CLASS.test(classNameValue)) context.report({
27241
- node,
27242
- message: MESSAGE$8
27243
- });
27244
- }
27245
- })
27246
- });
27247
- //#endregion
27248
26721
  //#region src/plugin/rules/bundle-size/prefer-dynamic-import.ts
27249
26722
  const preferDynamicImport = defineRule({
27250
26723
  id: "prefer-dynamic-import",
@@ -27836,26 +27309,6 @@ const preferTagOverRole = defineRule({
27836
27309
  } })
27837
27310
  });
27838
27311
  //#endregion
27839
- //#region src/plugin/rules/design/prefer-truncate-shorthand.ts
27840
- const HAS_OVERFLOW_HIDDEN = /(?:^|\s)overflow-hidden(?:$|\s)/;
27841
- const HAS_TEXT_ELLIPSIS = /(?:^|\s)text-ellipsis(?:$|\s)/;
27842
- const HAS_WHITESPACE_NOWRAP = /(?:^|\s)whitespace-nowrap(?:$|\s)/;
27843
- const preferTruncateShorthand = defineRule({
27844
- id: "prefer-truncate-shorthand",
27845
- title: "Use truncate shorthand",
27846
- tags: ["design", "test-noise"],
27847
- severity: "warn",
27848
- recommendation: "Replace `overflow-hidden text-ellipsis whitespace-nowrap` with the single Tailwind `truncate` utility, which sets all three.",
27849
- create: (context) => ({ JSXOpeningElement(node) {
27850
- const classNameValue = getStringFromClassNameAttr(node);
27851
- if (!classNameValue) return;
27852
- if (HAS_OVERFLOW_HIDDEN.test(classNameValue) && HAS_TEXT_ELLIPSIS.test(classNameValue) && HAS_WHITESPACE_NOWRAP.test(classNameValue)) context.report({
27853
- node,
27854
- message: "`overflow-hidden text-ellipsis whitespace-nowrap` is exactly what the `truncate` utility does — collapse the three classes into `truncate`."
27855
- });
27856
- } })
27857
- });
27858
- //#endregion
27859
27312
  //#region src/plugin/rules/state-and-effects/prefer-use-effect-event.ts
27860
27313
  const collectFunctionTypedLocalBindings = (componentBody) => {
27861
27314
  const functionTypedLocals = /* @__PURE__ */ new Set();
@@ -35972,6 +35425,7 @@ const serverFetchWithoutRevalidate = defineRule({
35972
35425
  CallExpression(node) {
35973
35426
  if (!isServerSideFile) return;
35974
35427
  if (!isFetchCall(node)) return;
35428
+ if (isMutatingFetchCall(node)) return;
35975
35429
  const optionsArg = node.arguments?.[1];
35976
35430
  if (optionsArg && objectExpressionHasNextRevalidate(optionsArg)) return;
35977
35431
  const urlArg = node.arguments?.[0];
@@ -39530,17 +38984,6 @@ const reactDoctorRules = [
39530
38984
  requires: [...new Set(["react", ...noAdjustStateOnPropChange.requires ?? []])]
39531
38985
  }
39532
38986
  },
39533
- {
39534
- key: "react-doctor/no-arbitrary-px-font-size",
39535
- id: "no-arbitrary-px-font-size",
39536
- source: "react-doctor",
39537
- originallyExternal: false,
39538
- rule: {
39539
- ...noArbitraryPxFontSize,
39540
- framework: "global",
39541
- category: "Accessibility"
39542
- }
39543
- },
39544
38987
  {
39545
38988
  key: "react-doctor/no-aria-hidden-on-focusable",
39546
38989
  id: "no-aria-hidden-on-focusable",
@@ -39600,18 +39043,6 @@ const reactDoctorRules = [
39600
39043
  requires: [...new Set(["react", ...noAutofocus.requires ?? []])]
39601
39044
  }
39602
39045
  },
39603
- {
39604
- key: "react-doctor/no-autoplay-without-muted",
39605
- id: "no-autoplay-without-muted",
39606
- source: "react-doctor",
39607
- originallyExternal: false,
39608
- rule: {
39609
- ...noAutoplayWithoutMuted,
39610
- framework: "global",
39611
- category: "Accessibility",
39612
- requires: [...new Set(["react", ...noAutoplayWithoutMuted.requires ?? []])]
39613
- }
39614
- },
39615
39046
  {
39616
39047
  key: "react-doctor/no-barrel-import",
39617
39048
  id: "no-barrel-import",
@@ -39765,17 +39196,6 @@ const reactDoctorRules = [
39765
39196
  category: "Maintainability"
39766
39197
  }
39767
39198
  },
39768
- {
39769
- key: "react-doctor/no-deprecated-tailwind-class",
39770
- id: "no-deprecated-tailwind-class",
39771
- source: "react-doctor",
39772
- originallyExternal: false,
39773
- rule: {
39774
- ...noDeprecatedTailwindClass,
39775
- framework: "global",
39776
- category: "Maintainability"
39777
- }
39778
- },
39779
39199
  {
39780
39200
  key: "react-doctor/no-derived-state",
39781
39201
  id: "no-derived-state",
@@ -40047,17 +39467,6 @@ const reactDoctorRules = [
40047
39467
  category: "Performance"
40048
39468
  }
40049
39469
  },
40050
- {
40051
- key: "react-doctor/no-full-viewport-width",
40052
- id: "no-full-viewport-width",
40053
- source: "react-doctor",
40054
- originallyExternal: false,
40055
- rule: {
40056
- ...noFullViewportWidth,
40057
- framework: "global",
40058
- category: "Maintainability"
40059
- }
40060
- },
40061
39470
  {
40062
39471
  key: "react-doctor/no-generic-handler-names",
40063
39472
  id: "no-generic-handler-names",
@@ -40297,17 +39706,6 @@ const reactDoctorRules = [
40297
39706
  category: "Performance"
40298
39707
  }
40299
39708
  },
40300
- {
40301
- key: "react-doctor/no-low-contrast-inline-style",
40302
- id: "no-low-contrast-inline-style",
40303
- source: "react-doctor",
40304
- originallyExternal: false,
40305
- rule: {
40306
- ...noLowContrastInlineStyle,
40307
- framework: "global",
40308
- category: "Accessibility"
40309
- }
40310
- },
40311
39709
  {
40312
39710
  key: "react-doctor/no-many-boolean-props",
40313
39711
  id: "no-many-boolean-props",
@@ -40585,17 +39983,6 @@ const reactDoctorRules = [
40585
39983
  category: "Maintainability"
40586
39984
  }
40587
39985
  },
40588
- {
40589
- key: "react-doctor/no-redundant-display-class",
40590
- id: "no-redundant-display-class",
40591
- source: "react-doctor",
40592
- originallyExternal: false,
40593
- rule: {
40594
- ...noRedundantDisplayClass,
40595
- framework: "global",
40596
- category: "Maintainability"
40597
- }
40598
- },
40599
39986
  {
40600
39987
  key: "react-doctor/no-redundant-roles",
40601
39988
  id: "no-redundant-roles",
@@ -40772,17 +40159,6 @@ const reactDoctorRules = [
40772
40159
  requires: [...new Set(["react", ...noStringRefs.requires ?? []])]
40773
40160
  }
40774
40161
  },
40775
- {
40776
- key: "react-doctor/no-svg-currentcolor-with-fill-class",
40777
- id: "no-svg-currentcolor-with-fill-class",
40778
- source: "react-doctor",
40779
- originallyExternal: false,
40780
- rule: {
40781
- ...noSvgCurrentcolorWithFillClass,
40782
- framework: "global",
40783
- category: "Maintainability"
40784
- }
40785
- },
40786
40162
  {
40787
40163
  key: "react-doctor/no-sync-xhr",
40788
40164
  id: "no-sync-xhr",
@@ -40794,29 +40170,6 @@ const reactDoctorRules = [
40794
40170
  category: "Performance"
40795
40171
  }
40796
40172
  },
40797
- {
40798
- key: "react-doctor/no-tailwind-layout-transition",
40799
- id: "no-tailwind-layout-transition",
40800
- source: "react-doctor",
40801
- originallyExternal: false,
40802
- rule: {
40803
- ...noTailwindLayoutTransition,
40804
- framework: "global",
40805
- category: "Performance"
40806
- }
40807
- },
40808
- {
40809
- key: "react-doctor/no-target-blank-without-rel",
40810
- id: "no-target-blank-without-rel",
40811
- source: "react-doctor",
40812
- originallyExternal: false,
40813
- rule: {
40814
- ...noTargetBlankWithoutRel,
40815
- framework: "global",
40816
- category: "Accessibility",
40817
- requires: [...new Set(["react", ...noTargetBlankWithoutRel.requires ?? []])]
40818
- }
40819
- },
40820
40173
  {
40821
40174
  key: "react-doctor/no-this-in-sfc",
40822
40175
  id: "no-this-in-sfc",
@@ -40886,18 +40239,6 @@ const reactDoctorRules = [
40886
40239
  requires: [...new Set(["react", ...noUnescapedEntities.requires ?? []])]
40887
40240
  }
40888
40241
  },
40889
- {
40890
- key: "react-doctor/no-uninformative-aria-label",
40891
- id: "no-uninformative-aria-label",
40892
- source: "react-doctor",
40893
- originallyExternal: false,
40894
- rule: {
40895
- ...noUninformativeAriaLabel,
40896
- framework: "global",
40897
- category: "Accessibility",
40898
- requires: [...new Set(["react", ...noUninformativeAriaLabel.requires ?? []])]
40899
- }
40900
- },
40901
40242
  {
40902
40243
  key: "react-doctor/no-unknown-property",
40903
40244
  id: "no-unknown-property",
@@ -41107,17 +40448,6 @@ const reactDoctorRules = [
41107
40448
  category: "Bugs"
41108
40449
  }
41109
40450
  },
41110
- {
41111
- key: "react-doctor/prefer-dvh-over-vh",
41112
- id: "prefer-dvh-over-vh",
41113
- source: "react-doctor",
41114
- originallyExternal: false,
41115
- rule: {
41116
- ...preferDvhOverVh,
41117
- framework: "global",
41118
- category: "Maintainability"
41119
- }
41120
- },
41121
40451
  {
41122
40452
  key: "react-doctor/prefer-dynamic-import",
41123
40453
  id: "prefer-dynamic-import",
@@ -41222,17 +40552,6 @@ const reactDoctorRules = [
41222
40552
  requires: [...new Set(["react", ...preferTagOverRole.requires ?? []])]
41223
40553
  }
41224
40554
  },
41225
- {
41226
- key: "react-doctor/prefer-truncate-shorthand",
41227
- id: "prefer-truncate-shorthand",
41228
- source: "react-doctor",
41229
- originallyExternal: false,
41230
- rule: {
41231
- ...preferTruncateShorthand,
41232
- framework: "global",
41233
- category: "Maintainability"
41234
- }
41235
- },
41236
40555
  {
41237
40556
  key: "react-doctor/prefer-use-effect-event",
41238
40557
  id: "prefer-use-effect-event",
@@ -42959,32 +42278,6 @@ const computeUnconditionalSet = (cfg) => {
42959
42278
  }
42960
42279
  return unconditional;
42961
42280
  };
42962
- const computeDominatesExit = (cfg) => {
42963
- const reachableToExit = /* @__PURE__ */ new Set();
42964
- const queue = [cfg.exit];
42965
- while (queue.length > 0) {
42966
- const block = queue.shift();
42967
- if (reachableToExit.has(block)) continue;
42968
- reachableToExit.add(block);
42969
- for (const edge of block.predecessors) queue.push(edge.from);
42970
- }
42971
- const dominatesExit = /* @__PURE__ */ new Set();
42972
- const visit = (block) => {
42973
- if (block === cfg.exit) return true;
42974
- if (dominatesExit.has(block)) return true;
42975
- if (block.successors.length === 0) return false;
42976
- dominatesExit.add(block);
42977
- let allReach = true;
42978
- for (const edge of block.successors) if (!visit(edge.to)) {
42979
- allReach = false;
42980
- break;
42981
- }
42982
- if (!allReach) dominatesExit.delete(block);
42983
- return allReach;
42984
- };
42985
- for (const block of cfg.blocks) visit(block);
42986
- return dominatesExit;
42987
- };
42988
42281
  const analyzeControlFlow = (program) => {
42989
42282
  nextBlockId = 0;
42990
42283
  const functionCfgs = /* @__PURE__ */ new Map();
@@ -42992,8 +42285,7 @@ const analyzeControlFlow = (program) => {
42992
42285
  const cfg = buildFunctionCfg(functionNode, body);
42993
42286
  functionCfgs.set(functionNode, {
42994
42287
  cfg,
42995
- unconditionalSet: computeUnconditionalSet(cfg),
42996
- dominatesExitSet: computeDominatesExit(cfg)
42288
+ unconditionalSet: computeUnconditionalSet(cfg)
42997
42289
  });
42998
42290
  };
42999
42291
  if (isNodeOfType(program, "Program")) buildFor(program, {
@@ -43036,20 +42328,10 @@ const analyzeControlFlow = (program) => {
43036
42328
  if (!block) return true;
43037
42329
  return entry.unconditionalSet.has(block);
43038
42330
  };
43039
- const dominatesExit = (node) => {
43040
- const owner = enclosingFunction(node);
43041
- if (!owner) return true;
43042
- const entry = functionCfgs.get(owner);
43043
- if (!entry) return true;
43044
- const block = entry.cfg.blockOf(node);
43045
- if (!block) return true;
43046
- return entry.dominatesExitSet.has(block);
43047
- };
43048
42331
  return {
43049
42332
  cfgFor,
43050
42333
  enclosingFunction,
43051
- isUnconditionalFromEntry,
43052
- dominatesExit
42334
+ isUnconditionalFromEntry
43053
42335
  };
43054
42336
  };
43055
42337
  //#endregion
@@ -43074,8 +42356,7 @@ const buildFallbackScopes = () => ({
43074
42356
  const FALLBACK_CFG = {
43075
42357
  cfgFor: () => null,
43076
42358
  enclosingFunction: () => null,
43077
- isUnconditionalFromEntry: () => false,
43078
- dominatesExit: () => false
42359
+ isUnconditionalFromEntry: () => false
43079
42360
  };
43080
42361
  const wrapWithSemanticContext = (rule) => ({
43081
42362
  ...rule,