oxlint-plugin-react-doctor 0.4.0-dev.a5c6993 → 0.4.0-dev.dc35070

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3197,6 +3197,25 @@ declare const REACT_DOCTOR_RULES: readonly [{
3197
3197
  readonly recommendation?: string;
3198
3198
  readonly create: (context: RuleContext) => RuleVisitors;
3199
3199
  };
3200
+ }, {
3201
+ readonly key: "react-doctor/no-jsx-element-type";
3202
+ readonly id: "no-jsx-element-type";
3203
+ readonly source: "react-doctor";
3204
+ readonly originallyExternal: false;
3205
+ readonly rule: {
3206
+ readonly framework: "global";
3207
+ readonly category: "Bugs";
3208
+ readonly id: string;
3209
+ readonly title?: string;
3210
+ readonly severity: RuleSeverity;
3211
+ readonly requires?: ReadonlyArray<string>;
3212
+ readonly disabledBy?: ReadonlyArray<string>;
3213
+ readonly tags?: ReadonlyArray<string>;
3214
+ readonly defaultEnabled?: boolean;
3215
+ readonly lifecycle?: "retired";
3216
+ readonly recommendation?: string;
3217
+ readonly create: (context: RuleContext) => RuleVisitors;
3218
+ };
3200
3219
  }, {
3201
3220
  readonly key: "react-doctor/no-justified-text";
3202
3221
  readonly id: "no-justified-text";
@@ -4622,6 +4641,25 @@ declare const REACT_DOCTOR_RULES: readonly [{
4622
4641
  readonly recommendation?: string;
4623
4642
  readonly create: (context: RuleContext) => RuleVisitors;
4624
4643
  };
4644
+ }, {
4645
+ readonly key: "react-doctor/query-destructure-result";
4646
+ readonly id: "query-destructure-result";
4647
+ readonly source: "react-doctor";
4648
+ readonly originallyExternal: false;
4649
+ readonly rule: {
4650
+ readonly framework: "tanstack-query";
4651
+ readonly category: "Bugs";
4652
+ readonly id: string;
4653
+ readonly title?: string;
4654
+ readonly severity: RuleSeverity;
4655
+ readonly requires?: ReadonlyArray<string>;
4656
+ readonly disabledBy?: ReadonlyArray<string>;
4657
+ readonly tags?: ReadonlyArray<string>;
4658
+ readonly defaultEnabled?: boolean;
4659
+ readonly lifecycle?: "retired";
4660
+ readonly recommendation?: string;
4661
+ readonly create: (context: RuleContext) => RuleVisitors;
4662
+ };
4625
4663
  }, {
4626
4664
  readonly key: "react-doctor/query-mutation-missing-invalidation";
4627
4665
  readonly id: "query-mutation-missing-invalidation";
@@ -9628,6 +9666,25 @@ declare const RULES: readonly [{
9628
9666
  readonly recommendation?: string;
9629
9667
  readonly create: (context: RuleContext) => RuleVisitors;
9630
9668
  };
9669
+ }, {
9670
+ readonly key: "react-doctor/no-jsx-element-type";
9671
+ readonly id: "no-jsx-element-type";
9672
+ readonly source: "react-doctor";
9673
+ readonly originallyExternal: false;
9674
+ readonly rule: {
9675
+ readonly framework: "global";
9676
+ readonly category: "Bugs";
9677
+ readonly id: string;
9678
+ readonly title?: string;
9679
+ readonly severity: RuleSeverity;
9680
+ readonly requires?: ReadonlyArray<string>;
9681
+ readonly disabledBy?: ReadonlyArray<string>;
9682
+ readonly tags?: ReadonlyArray<string>;
9683
+ readonly defaultEnabled?: boolean;
9684
+ readonly lifecycle?: "retired";
9685
+ readonly recommendation?: string;
9686
+ readonly create: (context: RuleContext) => RuleVisitors;
9687
+ };
9631
9688
  }, {
9632
9689
  readonly key: "react-doctor/no-justified-text";
9633
9690
  readonly id: "no-justified-text";
@@ -11053,6 +11110,25 @@ declare const RULES: readonly [{
11053
11110
  readonly recommendation?: string;
11054
11111
  readonly create: (context: RuleContext) => RuleVisitors;
11055
11112
  };
11113
+ }, {
11114
+ readonly key: "react-doctor/query-destructure-result";
11115
+ readonly id: "query-destructure-result";
11116
+ readonly source: "react-doctor";
11117
+ readonly originallyExternal: false;
11118
+ readonly rule: {
11119
+ readonly framework: "tanstack-query";
11120
+ readonly category: "Bugs";
11121
+ readonly id: string;
11122
+ readonly title?: string;
11123
+ readonly severity: RuleSeverity;
11124
+ readonly requires?: ReadonlyArray<string>;
11125
+ readonly disabledBy?: ReadonlyArray<string>;
11126
+ readonly tags?: ReadonlyArray<string>;
11127
+ readonly defaultEnabled?: boolean;
11128
+ readonly lifecycle?: "retired";
11129
+ readonly recommendation?: string;
11130
+ readonly create: (context: RuleContext) => RuleVisitors;
11131
+ };
11056
11132
  }, {
11057
11133
  readonly key: "react-doctor/query-mutation-missing-invalidation";
11058
11134
  readonly id: "query-mutation-missing-invalidation";
package/dist/index.js CHANGED
@@ -1102,7 +1102,7 @@ const anchorAmbiguousText = defineRule({
1102
1102
  });
1103
1103
  //#endregion
1104
1104
  //#region src/plugin/rules/a11y/anchor-has-content.ts
1105
- const MESSAGE$50 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
1105
+ const MESSAGE$51 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
1106
1106
  const anchorHasContent = defineRule({
1107
1107
  id: "anchor-has-content",
1108
1108
  title: "Anchor has no content",
@@ -1118,7 +1118,7 @@ const anchorHasContent = defineRule({
1118
1118
  for (const attribute of ["title", "aria-label"]) if (hasJsxPropIgnoreCase(opening.attributes, attribute)) return;
1119
1119
  context.report({
1120
1120
  node: opening.name,
1121
- message: MESSAGE$50
1121
+ message: MESSAGE$51
1122
1122
  });
1123
1123
  } })
1124
1124
  });
@@ -1512,7 +1512,7 @@ const parseJsxValue = (value) => {
1512
1512
  };
1513
1513
  //#endregion
1514
1514
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
1515
- const MESSAGE$49 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
1515
+ const MESSAGE$50 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
1516
1516
  const ariaActivedescendantHasTabindex = defineRule({
1517
1517
  id: "aria-activedescendant-has-tabindex",
1518
1518
  title: "aria-activedescendant missing tabindex",
@@ -1530,14 +1530,14 @@ const ariaActivedescendantHasTabindex = defineRule({
1530
1530
  if (tabIndexValue === null || tabIndexValue >= -1) return;
1531
1531
  context.report({
1532
1532
  node: node.name,
1533
- message: MESSAGE$49
1533
+ message: MESSAGE$50
1534
1534
  });
1535
1535
  return;
1536
1536
  }
1537
1537
  if (isInteractiveElement(tag, node)) return;
1538
1538
  context.report({
1539
1539
  node: node.name,
1540
- message: MESSAGE$49
1540
+ message: MESSAGE$50
1541
1541
  });
1542
1542
  } })
1543
1543
  });
@@ -3551,7 +3551,7 @@ const isPureEventBlockerHandler = (attribute) => {
3551
3551
  //#endregion
3552
3552
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
3553
3553
  const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
3554
- const MESSAGE$48 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
3554
+ const MESSAGE$49 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
3555
3555
  const KEY_HANDLERS = [
3556
3556
  "onKeyUp",
3557
3557
  "onKeyDown",
@@ -3583,7 +3583,7 @@ const clickEventsHaveKeyEvents = defineRule({
3583
3583
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
3584
3584
  context.report({
3585
3585
  node: node.name,
3586
- message: MESSAGE$48
3586
+ message: MESSAGE$49
3587
3587
  });
3588
3588
  } };
3589
3589
  }
@@ -3696,7 +3696,7 @@ const stripParenExpression = (node) => {
3696
3696
  };
3697
3697
  //#endregion
3698
3698
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
3699
- const MESSAGE$47 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
3699
+ const MESSAGE$48 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
3700
3700
  const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
3701
3701
  const DEFAULT_LABELLING_PROPS = [
3702
3702
  "alt",
@@ -3857,7 +3857,7 @@ const controlHasAssociatedLabel = defineRule({
3857
3857
  for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
3858
3858
  context.report({
3859
3859
  node: opening,
3860
- message: MESSAGE$47
3860
+ message: MESSAGE$48
3861
3861
  });
3862
3862
  } };
3863
3863
  }
@@ -4193,7 +4193,7 @@ const isEs6Component = (node) => {
4193
4193
  };
4194
4194
  //#endregion
4195
4195
  //#region src/plugin/rules/react-builtins/display-name.ts
4196
- const MESSAGE$46 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
4196
+ const MESSAGE$47 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
4197
4197
  const DEFAULT_ADDITIONAL_HOCS = [
4198
4198
  "observer",
4199
4199
  "lazy",
@@ -4396,7 +4396,7 @@ const displayName = defineRule({
4396
4396
  const reportAt = (node) => {
4397
4397
  context.report({
4398
4398
  node,
4399
- message: MESSAGE$46
4399
+ message: MESSAGE$47
4400
4400
  });
4401
4401
  };
4402
4402
  return {
@@ -6507,7 +6507,7 @@ const forbidElements = defineRule({
6507
6507
  });
6508
6508
  //#endregion
6509
6509
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
6510
- const MESSAGE$45 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
6510
+ const MESSAGE$46 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
6511
6511
  const forwardRefUsesRef = defineRule({
6512
6512
  id: "forward-ref-uses-ref",
6513
6513
  title: "forwardRef without ref parameter",
@@ -6527,13 +6527,13 @@ const forwardRefUsesRef = defineRule({
6527
6527
  if (isNodeOfType(onlyParam, "RestElement")) return;
6528
6528
  context.report({
6529
6529
  node: inner,
6530
- message: MESSAGE$45
6530
+ message: MESSAGE$46
6531
6531
  });
6532
6532
  } })
6533
6533
  });
6534
6534
  //#endregion
6535
6535
  //#region src/plugin/rules/a11y/heading-has-content.ts
6536
- const MESSAGE$44 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
6536
+ const MESSAGE$45 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
6537
6537
  const DEFAULT_HEADING_TAGS = [
6538
6538
  "h1",
6539
6539
  "h2",
@@ -6566,7 +6566,7 @@ const headingHasContent = defineRule({
6566
6566
  if (isHiddenFromScreenReader(node, context.settings)) return;
6567
6567
  context.report({
6568
6568
  node,
6569
- message: MESSAGE$44
6569
+ message: MESSAGE$45
6570
6570
  });
6571
6571
  } };
6572
6572
  }
@@ -6704,7 +6704,7 @@ const hooksNoNanInDeps = defineRule({
6704
6704
  });
6705
6705
  //#endregion
6706
6706
  //#region src/plugin/rules/a11y/html-has-lang.ts
6707
- const MESSAGE$43 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
6707
+ const MESSAGE$44 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
6708
6708
  const resolveSettings$39 = (settings) => {
6709
6709
  const reactDoctor = settings?.["react-doctor"];
6710
6710
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -6752,7 +6752,7 @@ const htmlHasLang = defineRule({
6752
6752
  if (!lang) {
6753
6753
  context.report({
6754
6754
  node: node.name,
6755
- message: MESSAGE$43
6755
+ message: MESSAGE$44
6756
6756
  });
6757
6757
  return;
6758
6758
  }
@@ -6760,13 +6760,13 @@ const htmlHasLang = defineRule({
6760
6760
  if (verdict === "missing" || verdict === "empty") {
6761
6761
  context.report({
6762
6762
  node: lang,
6763
- message: MESSAGE$43
6763
+ message: MESSAGE$44
6764
6764
  });
6765
6765
  return;
6766
6766
  }
6767
6767
  if (hasSpread && !lang) context.report({
6768
6768
  node: node.name,
6769
- message: MESSAGE$43
6769
+ message: MESSAGE$44
6770
6770
  });
6771
6771
  } };
6772
6772
  }
@@ -6980,7 +6980,7 @@ const htmlNoNestedInteractive = defineRule({
6980
6980
  });
6981
6981
  //#endregion
6982
6982
  //#region src/plugin/rules/a11y/iframe-has-title.ts
6983
- const MESSAGE$42 = "Blind users can't tell what this `<iframe>` holds because screen readers have no title to read, so add a `title` describing its content.";
6983
+ const MESSAGE$43 = "Blind users can't tell what this `<iframe>` holds because screen readers have no title to read, so add a `title` describing its content.";
6984
6984
  const evaluateTitleValue = (value) => {
6985
6985
  if (!value) return "missing";
6986
6986
  if (isNodeOfType(value, "Literal")) {
@@ -7020,14 +7020,14 @@ const iframeHasTitle = defineRule({
7020
7020
  if (!titleAttr) {
7021
7021
  if (hasSpread || tag === "iframe") context.report({
7022
7022
  node: node.name,
7023
- message: MESSAGE$42
7023
+ message: MESSAGE$43
7024
7024
  });
7025
7025
  return;
7026
7026
  }
7027
7027
  const verdict = evaluateTitleValue(titleAttr.value);
7028
7028
  if (verdict === "missing" || verdict === "empty") context.report({
7029
7029
  node: titleAttr,
7030
- message: MESSAGE$42
7030
+ message: MESSAGE$43
7031
7031
  });
7032
7032
  } })
7033
7033
  });
@@ -7131,7 +7131,7 @@ const iframeMissingSandbox = defineRule({
7131
7131
  });
7132
7132
  //#endregion
7133
7133
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
7134
- const MESSAGE$41 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
7134
+ const MESSAGE$42 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
7135
7135
  const DEFAULT_COMPONENTS = ["img"];
7136
7136
  const DEFAULT_REDUNDANT_WORDS = [
7137
7137
  "image",
@@ -7194,7 +7194,7 @@ const imgRedundantAlt = defineRule({
7194
7194
  if (!altAttribute) return;
7195
7195
  if (altValueRedundant(altAttribute, settings.words)) context.report({
7196
7196
  node: altAttribute,
7197
- message: MESSAGE$41
7197
+ message: MESSAGE$42
7198
7198
  });
7199
7199
  } };
7200
7200
  }
@@ -9595,7 +9595,7 @@ const jsxMaxDepth = defineRule({
9595
9595
  });
9596
9596
  //#endregion
9597
9597
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
9598
- const MESSAGE$40 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
9598
+ const MESSAGE$41 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
9599
9599
  const LITERAL_TEXT_TAGS = new Set([
9600
9600
  "code",
9601
9601
  "pre",
@@ -9631,7 +9631,7 @@ const jsxNoCommentTextnodes = defineRule({
9631
9631
  if (isInsideLiteralTextTag(node)) return;
9632
9632
  context.report({
9633
9633
  node,
9634
- message: MESSAGE$40
9634
+ message: MESSAGE$41
9635
9635
  });
9636
9636
  } })
9637
9637
  });
@@ -9662,7 +9662,7 @@ const isInsideFunctionScope = (node) => {
9662
9662
  };
9663
9663
  //#endregion
9664
9664
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
9665
- const MESSAGE$39 = "Every reader of this context redraws on each render because you build its `value` inline.";
9665
+ const MESSAGE$40 = "Every reader of this context redraws on each render because you build its `value` inline.";
9666
9666
  const CONTEXT_MODULES$1 = [
9667
9667
  "react",
9668
9668
  "use-context-selector",
@@ -9760,7 +9760,7 @@ const jsxNoConstructedContextValues = defineRule({
9760
9760
  if (!isConstructedValue(innerExpression)) continue;
9761
9761
  context.report({
9762
9762
  node: attribute,
9763
- message: MESSAGE$39
9763
+ message: MESSAGE$40
9764
9764
  });
9765
9765
  }
9766
9766
  }
@@ -9846,7 +9846,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
9846
9846
  };
9847
9847
  //#endregion
9848
9848
  //#region src/plugin/rules/react-builtins/jsx-no-jsx-as-prop.ts
9849
- const MESSAGE$38 = "This child redraws every render because the prop gets brand new JSX each time.";
9849
+ const MESSAGE$39 = "This child redraws every render because the prop gets brand new JSX each time.";
9850
9850
  const KNOWN_SLOT_PROP_NAMES = new Set([
9851
9851
  "icon",
9852
9852
  "Icon",
@@ -10115,7 +10115,7 @@ const jsxNoJsxAsProp = defineRule({
10115
10115
  if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
10116
10116
  context.report({
10117
10117
  node,
10118
- message: MESSAGE$38
10118
+ message: MESSAGE$39
10119
10119
  });
10120
10120
  }
10121
10121
  };
@@ -10403,7 +10403,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
10403
10403
  ];
10404
10404
  //#endregion
10405
10405
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
10406
- const MESSAGE$37 = "This child redraws every render because the prop gets a brand new array each time.";
10406
+ const MESSAGE$38 = "This child redraws every render because the prop gets a brand new array each time.";
10407
10407
  const isDataArrayPropName = (propName) => {
10408
10408
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
10409
10409
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -10487,7 +10487,7 @@ const jsxNoNewArrayAsProp = defineRule({
10487
10487
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
10488
10488
  context.report({
10489
10489
  node,
10490
- message: MESSAGE$37
10490
+ message: MESSAGE$38
10491
10491
  });
10492
10492
  }
10493
10493
  };
@@ -10745,7 +10745,7 @@ const SAFE_RECEIVER_NAMES = new Set([
10745
10745
  ]);
10746
10746
  //#endregion
10747
10747
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
10748
- const MESSAGE$36 = "This child redraws every render because the prop gets a brand new function each time.";
10748
+ const MESSAGE$37 = "This child redraws every render because the prop gets a brand new function each time.";
10749
10749
  const isAccessorPredicateName = (propName) => {
10750
10750
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
10751
10751
  if (propName.length <= prefix.length) continue;
@@ -10951,7 +10951,7 @@ const jsxNoNewFunctionAsProp = defineRule({
10951
10951
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
10952
10952
  context.report({
10953
10953
  node,
10954
- message: MESSAGE$36
10954
+ message: MESSAGE$37
10955
10955
  });
10956
10956
  }
10957
10957
  };
@@ -11171,7 +11171,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
11171
11171
  ];
11172
11172
  //#endregion
11173
11173
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
11174
- const MESSAGE$35 = "This child redraws every render because the prop gets a brand new object each time.";
11174
+ const MESSAGE$36 = "This child redraws every render because the prop gets a brand new object each time.";
11175
11175
  const isConfigObjectPropName = (propName) => {
11176
11176
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
11177
11177
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -11259,7 +11259,7 @@ const jsxNoNewObjectAsProp = defineRule({
11259
11259
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
11260
11260
  context.report({
11261
11261
  node,
11262
- message: MESSAGE$35
11262
+ message: MESSAGE$36
11263
11263
  });
11264
11264
  }
11265
11265
  };
@@ -11267,7 +11267,7 @@ const jsxNoNewObjectAsProp = defineRule({
11267
11267
  });
11268
11268
  //#endregion
11269
11269
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
11270
- const MESSAGE$34 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
11270
+ const MESSAGE$35 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
11271
11271
  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;
11272
11272
  const resolveSettings$29 = (settings) => {
11273
11273
  const reactDoctor = settings?.["react-doctor"];
@@ -11308,7 +11308,7 @@ const jsxNoScriptUrl = defineRule({
11308
11308
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
11309
11309
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
11310
11310
  node: attribute,
11311
- message: MESSAGE$34
11311
+ message: MESSAGE$35
11312
11312
  });
11313
11313
  }
11314
11314
  } };
@@ -11904,7 +11904,7 @@ const jsxPropsNoSpreadMulti = defineRule({
11904
11904
  });
11905
11905
  //#endregion
11906
11906
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
11907
- const MESSAGE$33 = "You can't tell what props reach this element when you spread them.";
11907
+ const MESSAGE$34 = "You can't tell what props reach this element when you spread them.";
11908
11908
  const resolveSettings$25 = (settings) => {
11909
11909
  const reactDoctor = settings?.["react-doctor"];
11910
11910
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -11945,7 +11945,7 @@ const jsxPropsNoSpreading = defineRule({
11945
11945
  }
11946
11946
  context.report({
11947
11947
  node: attribute,
11948
- message: MESSAGE$33
11948
+ message: MESSAGE$34
11949
11949
  });
11950
11950
  }
11951
11951
  } };
@@ -12101,7 +12101,7 @@ const labelHasAssociatedControl = defineRule({
12101
12101
  });
12102
12102
  //#endregion
12103
12103
  //#region src/plugin/rules/a11y/lang.ts
12104
- const MESSAGE$32 = "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`.";
12104
+ const MESSAGE$33 = "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`.";
12105
12105
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
12106
12106
  "aa",
12107
12107
  "ab",
@@ -12313,7 +12313,7 @@ const lang = defineRule({
12313
12313
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
12314
12314
  context.report({
12315
12315
  node: langAttr,
12316
- message: MESSAGE$32
12316
+ message: MESSAGE$33
12317
12317
  });
12318
12318
  return;
12319
12319
  }
@@ -12322,13 +12322,13 @@ const lang = defineRule({
12322
12322
  if (value === null) return;
12323
12323
  if (!isValidLangTag(value)) context.report({
12324
12324
  node: langAttr,
12325
- message: MESSAGE$32
12325
+ message: MESSAGE$33
12326
12326
  });
12327
12327
  } })
12328
12328
  });
12329
12329
  //#endregion
12330
12330
  //#region src/plugin/rules/a11y/media-has-caption.ts
12331
- const MESSAGE$31 = "Deaf & hard-of-hearing users miss this media without captions, so add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
12331
+ const MESSAGE$32 = "Deaf & hard-of-hearing users miss this media without captions, so add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
12332
12332
  const DEFAULT_AUDIO = ["audio"];
12333
12333
  const DEFAULT_VIDEO = ["video"];
12334
12334
  const DEFAULT_TRACK = ["track"];
@@ -12369,7 +12369,7 @@ const mediaHasCaption = defineRule({
12369
12369
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
12370
12370
  context.report({
12371
12371
  node: node.name,
12372
- message: MESSAGE$31
12372
+ message: MESSAGE$32
12373
12373
  });
12374
12374
  return;
12375
12375
  }
@@ -12386,7 +12386,7 @@ const mediaHasCaption = defineRule({
12386
12386
  return kindValue.value.toLowerCase() === "captions";
12387
12387
  })) context.report({
12388
12388
  node: node.name,
12389
- message: MESSAGE$31
12389
+ message: MESSAGE$32
12390
12390
  });
12391
12391
  } };
12392
12392
  }
@@ -13427,7 +13427,7 @@ const nextjsNoVercelOgImport = defineRule({
13427
13427
  });
13428
13428
  //#endregion
13429
13429
  //#region src/plugin/rules/a11y/no-access-key.ts
13430
- const MESSAGE$30 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
13430
+ const MESSAGE$31 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
13431
13431
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
13432
13432
  const noAccessKey = defineRule({
13433
13433
  id: "no-access-key",
@@ -13444,7 +13444,7 @@ const noAccessKey = defineRule({
13444
13444
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
13445
13445
  context.report({
13446
13446
  node: accessKey,
13447
- message: MESSAGE$30
13447
+ message: MESSAGE$31
13448
13448
  });
13449
13449
  return;
13450
13450
  }
@@ -13454,7 +13454,7 @@ const noAccessKey = defineRule({
13454
13454
  if (isUndefinedIdentifier(expression)) return;
13455
13455
  context.report({
13456
13456
  node: accessKey,
13457
- message: MESSAGE$30
13457
+ message: MESSAGE$31
13458
13458
  });
13459
13459
  }
13460
13460
  } })
@@ -13937,7 +13937,7 @@ const noAdjustStateOnPropChange = defineRule({
13937
13937
  });
13938
13938
  //#endregion
13939
13939
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
13940
- const MESSAGE$29 = "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.";
13940
+ const MESSAGE$30 = "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.";
13941
13941
  const noAriaHiddenOnFocusable = defineRule({
13942
13942
  id: "no-aria-hidden-on-focusable",
13943
13943
  title: "aria-hidden on focusable element",
@@ -13964,7 +13964,7 @@ const noAriaHiddenOnFocusable = defineRule({
13964
13964
  const isImplicitlyFocusable = isInteractiveElement(tag, node);
13965
13965
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
13966
13966
  node: ariaHidden,
13967
- message: MESSAGE$29
13967
+ message: MESSAGE$30
13968
13968
  });
13969
13969
  } })
13970
13970
  });
@@ -14332,7 +14332,7 @@ const noArrayIndexAsKey = defineRule({
14332
14332
  });
14333
14333
  //#endregion
14334
14334
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
14335
- const MESSAGE$28 = "Your users can see & submit the wrong data when this list reorders.";
14335
+ const MESSAGE$29 = "Your users can see & submit the wrong data when this list reorders.";
14336
14336
  const SECOND_INDEX_METHODS = new Set([
14337
14337
  "every",
14338
14338
  "filter",
@@ -14536,7 +14536,7 @@ const noArrayIndexKey = defineRule({
14536
14536
  }
14537
14537
  context.report({
14538
14538
  node: keyAttribute,
14539
- message: MESSAGE$28
14539
+ message: MESSAGE$29
14540
14540
  });
14541
14541
  },
14542
14542
  CallExpression(node) {
@@ -14556,7 +14556,7 @@ const noArrayIndexKey = defineRule({
14556
14556
  if (propName !== "key") continue;
14557
14557
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
14558
14558
  node: property,
14559
- message: MESSAGE$28
14559
+ message: MESSAGE$29
14560
14560
  });
14561
14561
  }
14562
14562
  }
@@ -14564,7 +14564,7 @@ const noArrayIndexKey = defineRule({
14564
14564
  });
14565
14565
  //#endregion
14566
14566
  //#region src/plugin/rules/a11y/no-autofocus.ts
14567
- const MESSAGE$27 = "Screen reader & keyboard users get disoriented because `autoFocus` jumps focus on load, so remove it and let people choose where to focus.";
14567
+ const MESSAGE$28 = "Screen reader & keyboard users get disoriented because `autoFocus` jumps focus on load, so remove it and let people choose where to focus.";
14568
14568
  const resolveSettings$21 = (settings) => {
14569
14569
  const reactDoctor = settings?.["react-doctor"];
14570
14570
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -14620,7 +14620,7 @@ const noAutofocus = defineRule({
14620
14620
  }
14621
14621
  context.report({
14622
14622
  node: autoFocusAttribute,
14623
- message: MESSAGE$27
14623
+ message: MESSAGE$28
14624
14624
  });
14625
14625
  } };
14626
14626
  }
@@ -15145,7 +15145,7 @@ const noChainStateUpdates = defineRule({
15145
15145
  });
15146
15146
  //#endregion
15147
15147
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
15148
- const MESSAGE$26 = "Your component can render the wrong children when you pass them through a `children` prop.";
15148
+ const MESSAGE$27 = "Your component can render the wrong children when you pass them through a `children` prop.";
15149
15149
  const noChildrenProp = defineRule({
15150
15150
  id: "no-children-prop",
15151
15151
  title: "Children passed as a prop",
@@ -15157,7 +15157,7 @@ const noChildrenProp = defineRule({
15157
15157
  if (node.name.name !== "children") return;
15158
15158
  context.report({
15159
15159
  node: node.name,
15160
- message: MESSAGE$26
15160
+ message: MESSAGE$27
15161
15161
  });
15162
15162
  },
15163
15163
  CallExpression(node) {
@@ -15170,7 +15170,7 @@ const noChildrenProp = defineRule({
15170
15170
  const propertyKey = property.key;
15171
15171
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
15172
15172
  node: propertyKey,
15173
- message: MESSAGE$26
15173
+ message: MESSAGE$27
15174
15174
  });
15175
15175
  }
15176
15176
  }
@@ -15178,7 +15178,7 @@ const noChildrenProp = defineRule({
15178
15178
  });
15179
15179
  //#endregion
15180
15180
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
15181
- const MESSAGE$25 = "`React.cloneElement` breaks easily when the cloned element's props change.";
15181
+ const MESSAGE$26 = "`React.cloneElement` breaks easily when the cloned element's props change.";
15182
15182
  const noCloneElement = defineRule({
15183
15183
  id: "no-clone-element",
15184
15184
  title: "Use of cloneElement",
@@ -15191,7 +15191,7 @@ const noCloneElement = defineRule({
15191
15191
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
15192
15192
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
15193
15193
  node: callee,
15194
- message: MESSAGE$25
15194
+ message: MESSAGE$26
15195
15195
  });
15196
15196
  return;
15197
15197
  }
@@ -15204,7 +15204,7 @@ const noCloneElement = defineRule({
15204
15204
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
15205
15205
  context.report({
15206
15206
  node: callee,
15207
- message: MESSAGE$25
15207
+ message: MESSAGE$26
15208
15208
  });
15209
15209
  }
15210
15210
  } })
@@ -15253,7 +15253,7 @@ const enclosingComponentOrHookName = (node) => {
15253
15253
  };
15254
15254
  //#endregion
15255
15255
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
15256
- const MESSAGE$24 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
15256
+ const MESSAGE$25 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
15257
15257
  const CONTEXT_MODULES = [
15258
15258
  "react",
15259
15259
  "use-context-selector",
@@ -15289,7 +15289,7 @@ const noCreateContextInRender = defineRule({
15289
15289
  if (!componentOrHookName) return;
15290
15290
  context.report({
15291
15291
  node,
15292
- message: `${MESSAGE$24} (called inside "${componentOrHookName}")`
15292
+ message: `${MESSAGE$25} (called inside "${componentOrHookName}")`
15293
15293
  });
15294
15294
  } })
15295
15295
  });
@@ -15429,7 +15429,7 @@ const noCreateStoreInRender = defineRule({
15429
15429
  });
15430
15430
  //#endregion
15431
15431
  //#region src/plugin/rules/react-builtins/no-danger.ts
15432
- const MESSAGE$23 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
15432
+ const MESSAGE$24 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
15433
15433
  const noDanger = defineRule({
15434
15434
  id: "no-danger",
15435
15435
  title: "Use of dangerouslySetInnerHTML",
@@ -15442,7 +15442,7 @@ const noDanger = defineRule({
15442
15442
  if (!propAttribute) return;
15443
15443
  context.report({
15444
15444
  node: propAttribute.name,
15445
- message: MESSAGE$23
15445
+ message: MESSAGE$24
15446
15446
  });
15447
15447
  },
15448
15448
  CallExpression(node) {
@@ -15454,7 +15454,7 @@ const noDanger = defineRule({
15454
15454
  const propertyKey = property.key;
15455
15455
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
15456
15456
  node: propertyKey,
15457
- message: MESSAGE$23
15457
+ message: MESSAGE$24
15458
15458
  });
15459
15459
  }
15460
15460
  }
@@ -15462,7 +15462,7 @@ const noDanger = defineRule({
15462
15462
  });
15463
15463
  //#endregion
15464
15464
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
15465
- const MESSAGE$22 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
15465
+ const MESSAGE$23 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
15466
15466
  const isLineBreak = (child) => {
15467
15467
  if (!isNodeOfType(child, "JSXText")) return false;
15468
15468
  return child.value.trim().length === 0 && child.value.includes("\n");
@@ -15532,7 +15532,7 @@ const noDangerWithChildren = defineRule({
15532
15532
  if (!hasChildrenProp && !hasNestedChildren) return;
15533
15533
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
15534
15534
  node: opening,
15535
- message: MESSAGE$22
15535
+ message: MESSAGE$23
15536
15536
  });
15537
15537
  },
15538
15538
  CallExpression(node) {
@@ -15544,7 +15544,7 @@ const noDangerWithChildren = defineRule({
15544
15544
  if (!propsShape.hasDangerously) return;
15545
15545
  if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
15546
15546
  node,
15547
- message: MESSAGE$22
15547
+ message: MESSAGE$23
15548
15548
  });
15549
15549
  }
15550
15550
  })
@@ -16122,7 +16122,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
16122
16122
  //#endregion
16123
16123
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
16124
16124
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
16125
- const MESSAGE$21 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
16125
+ const MESSAGE$22 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
16126
16126
  const resolveSettings$20 = (settings) => {
16127
16127
  const reactDoctor = settings?.["react-doctor"];
16128
16128
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
@@ -16141,7 +16141,7 @@ const noDidMountSetState = defineRule({
16141
16141
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
16142
16142
  context.report({
16143
16143
  node: node.callee,
16144
- message: MESSAGE$21
16144
+ message: MESSAGE$22
16145
16145
  });
16146
16146
  } };
16147
16147
  }
@@ -16149,7 +16149,7 @@ const noDidMountSetState = defineRule({
16149
16149
  //#endregion
16150
16150
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
16151
16151
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
16152
- const MESSAGE$20 = "This can loop forever & freeze the component.";
16152
+ const MESSAGE$21 = "This can loop forever & freeze the component.";
16153
16153
  const resolveSettings$19 = (settings) => {
16154
16154
  const reactDoctor = settings?.["react-doctor"];
16155
16155
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -16168,7 +16168,7 @@ const noDidUpdateSetState = defineRule({
16168
16168
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
16169
16169
  context.report({
16170
16170
  node: node.callee,
16171
- message: MESSAGE$20
16171
+ message: MESSAGE$21
16172
16172
  });
16173
16173
  } };
16174
16174
  }
@@ -16191,7 +16191,7 @@ const isStateMemberExpression = (node) => {
16191
16191
  };
16192
16192
  //#endregion
16193
16193
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
16194
- const MESSAGE$19 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
16194
+ const MESSAGE$20 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
16195
16195
  const shouldIgnoreMutation = (node) => {
16196
16196
  let isConstructor = false;
16197
16197
  let isInsideCallExpression = false;
@@ -16213,7 +16213,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
16213
16213
  if (shouldIgnoreMutation(reportNode)) return;
16214
16214
  context.report({
16215
16215
  node: reportNode,
16216
- message: MESSAGE$19
16216
+ message: MESSAGE$20
16217
16217
  });
16218
16218
  };
16219
16219
  const noDirectMutationState = defineRule({
@@ -17801,7 +17801,7 @@ const ALLOWED_NAMESPACES = new Set([
17801
17801
  "ReactDOM",
17802
17802
  "ReactDom"
17803
17803
  ]);
17804
- const MESSAGE$18 = "`findDOMNode` crashes your app in React 19 because it was removed.";
17804
+ const MESSAGE$19 = "`findDOMNode` crashes your app in React 19 because it was removed.";
17805
17805
  const noFindDomNode = defineRule({
17806
17806
  id: "no-find-dom-node",
17807
17807
  title: "Use of findDOMNode",
@@ -17812,7 +17812,7 @@ const noFindDomNode = defineRule({
17812
17812
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
17813
17813
  context.report({
17814
17814
  node: callee,
17815
- message: MESSAGE$18
17815
+ message: MESSAGE$19
17816
17816
  });
17817
17817
  return;
17818
17818
  }
@@ -17823,7 +17823,7 @@ const noFindDomNode = defineRule({
17823
17823
  if (callee.property.name !== "findDOMNode") return;
17824
17824
  context.report({
17825
17825
  node: callee.property,
17826
- message: MESSAGE$18
17826
+ message: MESSAGE$19
17827
17827
  });
17828
17828
  }
17829
17829
  } })
@@ -18352,6 +18352,51 @@ const noIsMounted = defineRule({
18352
18352
  } })
18353
18353
  });
18354
18354
  //#endregion
18355
+ //#region src/plugin/rules/correctness/no-jsx-element-type.ts
18356
+ const MESSAGE$18 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
18357
+ const isJsxElementTypeReference = (node) => {
18358
+ if (!isNodeOfType(node, "TSTypeReference")) return false;
18359
+ const typeName = node.typeName;
18360
+ if (!isNodeOfType(typeName, "TSQualifiedName")) return false;
18361
+ return isNodeOfType(typeName.left, "Identifier") && typeName.left.name === "JSX" && isNodeOfType(typeName.right, "Identifier") && typeName.right.name === "Element";
18362
+ };
18363
+ const extractReturnTypeAnnotation = (returnType) => {
18364
+ if (!returnType) return null;
18365
+ if (!isNodeOfType(returnType, "TSTypeAnnotation")) return null;
18366
+ return returnType.typeAnnotation ?? null;
18367
+ };
18368
+ const checkReturnType = (context, returnType) => {
18369
+ const typeAnnotation = extractReturnTypeAnnotation(returnType);
18370
+ if (!typeAnnotation) return;
18371
+ if (isJsxElementTypeReference(typeAnnotation)) context.report({
18372
+ node: typeAnnotation,
18373
+ message: MESSAGE$18
18374
+ });
18375
+ };
18376
+ const noJsxElementType = defineRule({
18377
+ id: "no-jsx-element-type",
18378
+ title: "No JSX.Element",
18379
+ severity: "error",
18380
+ recommendation: "Replace `JSX.Element` with `React.ReactNode`. `JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return.",
18381
+ create: (context) => ({
18382
+ FunctionDeclaration(node) {
18383
+ checkReturnType(context, node.returnType);
18384
+ },
18385
+ ArrowFunctionExpression(node) {
18386
+ checkReturnType(context, node.returnType);
18387
+ },
18388
+ FunctionExpression(node) {
18389
+ checkReturnType(context, node.returnType);
18390
+ },
18391
+ TSDeclareFunction(node) {
18392
+ checkReturnType(context, node.returnType);
18393
+ },
18394
+ TSMethodSignature(node) {
18395
+ checkReturnType(context, node.returnType);
18396
+ }
18397
+ })
18398
+ });
18399
+ //#endregion
18355
18400
  //#region src/plugin/rules/design/no-justified-text.ts
18356
18401
  const noJustifiedText = defineRule({
18357
18402
  id: "no-justified-text",
@@ -24166,6 +24211,8 @@ const ENTRY_POINT_BASENAMES = new Set([
24166
24211
  "global-error.jsx",
24167
24212
  "route.tsx",
24168
24213
  "route.jsx",
24214
+ "_layout.tsx",
24215
+ "_layout.jsx",
24169
24216
  "_app.tsx",
24170
24217
  "_app.jsx",
24171
24218
  "_document.tsx",
@@ -25588,6 +25635,26 @@ const preferUseReducer = defineRule({
25588
25635
  }
25589
25636
  });
25590
25637
  //#endregion
25638
+ //#region src/plugin/rules/tanstack-query/query-destructure-result.ts
25639
+ const queryDestructureResult = defineRule({
25640
+ id: "query-destructure-result",
25641
+ title: "Destructure TanStack Query result",
25642
+ tags: ["test-noise"],
25643
+ requires: ["tanstack-query"],
25644
+ severity: "error",
25645
+ recommendation: "Destructure only the fields you need, like `const { data, isLoading } = useQuery(...)`. Assigning the whole object bypasses TanStack Query's tracked-property optimization and subscribes to every field.",
25646
+ create: (context) => ({ VariableDeclarator(node) {
25647
+ if (!isNodeOfType(node.id, "Identifier")) return;
25648
+ if (!node.init || !isNodeOfType(node.init, "CallExpression")) return;
25649
+ const calleeName = isNodeOfType(node.init.callee, "Identifier") ? node.init.callee.name : null;
25650
+ if (!calleeName || !TANSTACK_QUERY_HOOKS.has(calleeName)) return;
25651
+ context.report({
25652
+ node: node.id,
25653
+ message: `Destructure ${calleeName}() results instead of assigning the whole query object, so TanStack Query only subscribes to the fields you use.`
25654
+ });
25655
+ } })
25656
+ });
25657
+ //#endregion
25591
25658
  //#region src/plugin/rules/tanstack-query/query-mutation-missing-invalidation.ts
25592
25659
  const queryMutationMissingInvalidation = defineRule({
25593
25660
  id: "query-mutation-missing-invalidation",
@@ -36436,6 +36503,17 @@ const reactDoctorRules = [
36436
36503
  category: "Bugs"
36437
36504
  }
36438
36505
  },
36506
+ {
36507
+ key: "react-doctor/no-jsx-element-type",
36508
+ id: "no-jsx-element-type",
36509
+ source: "react-doctor",
36510
+ originallyExternal: false,
36511
+ rule: {
36512
+ ...noJsxElementType,
36513
+ framework: "global",
36514
+ category: "Bugs"
36515
+ }
36516
+ },
36439
36517
  {
36440
36518
  key: "react-doctor/no-justified-text",
36441
36519
  id: "no-justified-text",
@@ -37261,6 +37339,17 @@ const reactDoctorRules = [
37261
37339
  category: "Bugs"
37262
37340
  }
37263
37341
  },
37342
+ {
37343
+ key: "react-doctor/query-destructure-result",
37344
+ id: "query-destructure-result",
37345
+ source: "react-doctor",
37346
+ originallyExternal: false,
37347
+ rule: {
37348
+ ...queryDestructureResult,
37349
+ framework: "tanstack-query",
37350
+ category: "Bugs"
37351
+ }
37352
+ },
37264
37353
  {
37265
37354
  key: "react-doctor/query-mutation-missing-invalidation",
37266
37355
  id: "query-mutation-missing-invalidation",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.4.0-dev.a5c6993",
3
+ "version": "0.4.0-dev.dc35070",
4
4
  "description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",