oxlint-plugin-react-doctor 0.4.0-dev.5448962 → 0.4.0-dev.73dcb20
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 +76 -0
- package/dist/index.js +174 -84
- package/package.json +1 -1
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
|
@@ -829,6 +829,9 @@ const isNextjsMetadataImageRouteFilename = (rawFilename) => {
|
|
|
829
829
|
return /^(opengraph-image|twitter-image|icon|apple-icon)\d*\.(jsx?|tsx?)$/.test(path.basename(rawFilename));
|
|
830
830
|
};
|
|
831
831
|
//#endregion
|
|
832
|
+
//#region src/plugin/utils/normalize-filename.ts
|
|
833
|
+
const normalizeFilename$1 = (filename) => filename.replaceAll("\\", "/");
|
|
834
|
+
//#endregion
|
|
832
835
|
//#region src/plugin/utils/is-hidden-from-screen-reader.ts
|
|
833
836
|
const isHiddenFromScreenReader = (openingElement, settings) => {
|
|
834
837
|
if (getElementType(openingElement, settings).toLowerCase() === "input") {
|
|
@@ -1000,7 +1003,7 @@ const altText = defineRule({
|
|
|
1000
1003
|
recommendation: "Give every meaningful image an `alt`, `aria-label`, or `aria-labelledby`.",
|
|
1001
1004
|
category: "Accessibility",
|
|
1002
1005
|
create: (context) => {
|
|
1003
|
-
if (isNextjsMetadataImageRouteFilename(context.filename)) return {};
|
|
1006
|
+
if (isNextjsMetadataImageRouteFilename(normalizeFilename$1(context.filename ?? ""))) return {};
|
|
1004
1007
|
const settings = resolveSettings$53(context.settings);
|
|
1005
1008
|
const checkImg = !settings.elements || settings.elements.includes("img");
|
|
1006
1009
|
const checkObject = !settings.elements || settings.elements.includes("object");
|
|
@@ -1102,7 +1105,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
1102
1105
|
});
|
|
1103
1106
|
//#endregion
|
|
1104
1107
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
1105
|
-
const MESSAGE$
|
|
1108
|
+
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
1109
|
const anchorHasContent = defineRule({
|
|
1107
1110
|
id: "anchor-has-content",
|
|
1108
1111
|
title: "Anchor has no content",
|
|
@@ -1118,7 +1121,7 @@ const anchorHasContent = defineRule({
|
|
|
1118
1121
|
for (const attribute of ["title", "aria-label"]) if (hasJsxPropIgnoreCase(opening.attributes, attribute)) return;
|
|
1119
1122
|
context.report({
|
|
1120
1123
|
node: opening.name,
|
|
1121
|
-
message: MESSAGE$
|
|
1124
|
+
message: MESSAGE$51
|
|
1122
1125
|
});
|
|
1123
1126
|
} })
|
|
1124
1127
|
});
|
|
@@ -1512,7 +1515,7 @@ const parseJsxValue = (value) => {
|
|
|
1512
1515
|
};
|
|
1513
1516
|
//#endregion
|
|
1514
1517
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
1515
|
-
const MESSAGE$
|
|
1518
|
+
const MESSAGE$50 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
1516
1519
|
const ariaActivedescendantHasTabindex = defineRule({
|
|
1517
1520
|
id: "aria-activedescendant-has-tabindex",
|
|
1518
1521
|
title: "aria-activedescendant missing tabindex",
|
|
@@ -1530,14 +1533,14 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
1530
1533
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
1531
1534
|
context.report({
|
|
1532
1535
|
node: node.name,
|
|
1533
|
-
message: MESSAGE$
|
|
1536
|
+
message: MESSAGE$50
|
|
1534
1537
|
});
|
|
1535
1538
|
return;
|
|
1536
1539
|
}
|
|
1537
1540
|
if (isInteractiveElement(tag, node)) return;
|
|
1538
1541
|
context.report({
|
|
1539
1542
|
node: node.name,
|
|
1540
|
-
message: MESSAGE$
|
|
1543
|
+
message: MESSAGE$50
|
|
1541
1544
|
});
|
|
1542
1545
|
} })
|
|
1543
1546
|
});
|
|
@@ -3073,9 +3076,6 @@ const asyncDeferAwait = defineRule({
|
|
|
3073
3076
|
}
|
|
3074
3077
|
});
|
|
3075
3078
|
//#endregion
|
|
3076
|
-
//#region src/plugin/utils/normalize-filename.ts
|
|
3077
|
-
const normalizeFilename$1 = (filename) => filename.replaceAll("\\", "/");
|
|
3078
|
-
//#endregion
|
|
3079
3079
|
//#region src/plugin/utils/get-callee-identifier-trail.ts
|
|
3080
3080
|
const getCalleeIdentifierTrail = (call) => {
|
|
3081
3081
|
let entry = call;
|
|
@@ -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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
12389
|
+
message: MESSAGE$32
|
|
12390
12390
|
});
|
|
12391
12391
|
} };
|
|
12392
12392
|
}
|
|
@@ -12954,8 +12954,9 @@ const nextjsNoImgElement = defineRule({
|
|
|
12954
12954
|
create: (context) => {
|
|
12955
12955
|
const filename = normalizeFilename$1(context.filename ?? "");
|
|
12956
12956
|
const isOgRoute = OG_ROUTE_PATTERN.test(filename);
|
|
12957
|
+
const isMetadataImageRoute = isNextjsMetadataImageRouteFilename(filename);
|
|
12957
12958
|
return { JSXOpeningElement(node) {
|
|
12958
|
-
if (isOgRoute) return;
|
|
12959
|
+
if (isOgRoute || isMetadataImageRoute) return;
|
|
12959
12960
|
if (isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "img") context.report({
|
|
12960
12961
|
node,
|
|
12961
12962
|
message: "Plain <img> ships unoptimized, oversized images to your users."
|
|
@@ -13427,7 +13428,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
13427
13428
|
});
|
|
13428
13429
|
//#endregion
|
|
13429
13430
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
13430
|
-
const MESSAGE$
|
|
13431
|
+
const MESSAGE$31 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
13431
13432
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
13432
13433
|
const noAccessKey = defineRule({
|
|
13433
13434
|
id: "no-access-key",
|
|
@@ -13444,7 +13445,7 @@ const noAccessKey = defineRule({
|
|
|
13444
13445
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
13445
13446
|
context.report({
|
|
13446
13447
|
node: accessKey,
|
|
13447
|
-
message: MESSAGE$
|
|
13448
|
+
message: MESSAGE$31
|
|
13448
13449
|
});
|
|
13449
13450
|
return;
|
|
13450
13451
|
}
|
|
@@ -13454,7 +13455,7 @@ const noAccessKey = defineRule({
|
|
|
13454
13455
|
if (isUndefinedIdentifier(expression)) return;
|
|
13455
13456
|
context.report({
|
|
13456
13457
|
node: accessKey,
|
|
13457
|
-
message: MESSAGE$
|
|
13458
|
+
message: MESSAGE$31
|
|
13458
13459
|
});
|
|
13459
13460
|
}
|
|
13460
13461
|
} })
|
|
@@ -13937,7 +13938,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
13937
13938
|
});
|
|
13938
13939
|
//#endregion
|
|
13939
13940
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
13940
|
-
const MESSAGE$
|
|
13941
|
+
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
13942
|
const noAriaHiddenOnFocusable = defineRule({
|
|
13942
13943
|
id: "no-aria-hidden-on-focusable",
|
|
13943
13944
|
title: "aria-hidden on focusable element",
|
|
@@ -13964,7 +13965,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
13964
13965
|
const isImplicitlyFocusable = isInteractiveElement(tag, node);
|
|
13965
13966
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
13966
13967
|
node: ariaHidden,
|
|
13967
|
-
message: MESSAGE$
|
|
13968
|
+
message: MESSAGE$30
|
|
13968
13969
|
});
|
|
13969
13970
|
} })
|
|
13970
13971
|
});
|
|
@@ -14332,7 +14333,7 @@ const noArrayIndexAsKey = defineRule({
|
|
|
14332
14333
|
});
|
|
14333
14334
|
//#endregion
|
|
14334
14335
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
14335
|
-
const MESSAGE$
|
|
14336
|
+
const MESSAGE$29 = "Your users can see & submit the wrong data when this list reorders.";
|
|
14336
14337
|
const SECOND_INDEX_METHODS = new Set([
|
|
14337
14338
|
"every",
|
|
14338
14339
|
"filter",
|
|
@@ -14536,7 +14537,7 @@ const noArrayIndexKey = defineRule({
|
|
|
14536
14537
|
}
|
|
14537
14538
|
context.report({
|
|
14538
14539
|
node: keyAttribute,
|
|
14539
|
-
message: MESSAGE$
|
|
14540
|
+
message: MESSAGE$29
|
|
14540
14541
|
});
|
|
14541
14542
|
},
|
|
14542
14543
|
CallExpression(node) {
|
|
@@ -14556,7 +14557,7 @@ const noArrayIndexKey = defineRule({
|
|
|
14556
14557
|
if (propName !== "key") continue;
|
|
14557
14558
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
14558
14559
|
node: property,
|
|
14559
|
-
message: MESSAGE$
|
|
14560
|
+
message: MESSAGE$29
|
|
14560
14561
|
});
|
|
14561
14562
|
}
|
|
14562
14563
|
}
|
|
@@ -14564,7 +14565,7 @@ const noArrayIndexKey = defineRule({
|
|
|
14564
14565
|
});
|
|
14565
14566
|
//#endregion
|
|
14566
14567
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
14567
|
-
const MESSAGE$
|
|
14568
|
+
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
14569
|
const resolveSettings$21 = (settings) => {
|
|
14569
14570
|
const reactDoctor = settings?.["react-doctor"];
|
|
14570
14571
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -14620,7 +14621,7 @@ const noAutofocus = defineRule({
|
|
|
14620
14621
|
}
|
|
14621
14622
|
context.report({
|
|
14622
14623
|
node: autoFocusAttribute,
|
|
14623
|
-
message: MESSAGE$
|
|
14624
|
+
message: MESSAGE$28
|
|
14624
14625
|
});
|
|
14625
14626
|
} };
|
|
14626
14627
|
}
|
|
@@ -15145,7 +15146,7 @@ const noChainStateUpdates = defineRule({
|
|
|
15145
15146
|
});
|
|
15146
15147
|
//#endregion
|
|
15147
15148
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
15148
|
-
const MESSAGE$
|
|
15149
|
+
const MESSAGE$27 = "Your component can render the wrong children when you pass them through a `children` prop.";
|
|
15149
15150
|
const noChildrenProp = defineRule({
|
|
15150
15151
|
id: "no-children-prop",
|
|
15151
15152
|
title: "Children passed as a prop",
|
|
@@ -15157,7 +15158,7 @@ const noChildrenProp = defineRule({
|
|
|
15157
15158
|
if (node.name.name !== "children") return;
|
|
15158
15159
|
context.report({
|
|
15159
15160
|
node: node.name,
|
|
15160
|
-
message: MESSAGE$
|
|
15161
|
+
message: MESSAGE$27
|
|
15161
15162
|
});
|
|
15162
15163
|
},
|
|
15163
15164
|
CallExpression(node) {
|
|
@@ -15170,7 +15171,7 @@ const noChildrenProp = defineRule({
|
|
|
15170
15171
|
const propertyKey = property.key;
|
|
15171
15172
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
15172
15173
|
node: propertyKey,
|
|
15173
|
-
message: MESSAGE$
|
|
15174
|
+
message: MESSAGE$27
|
|
15174
15175
|
});
|
|
15175
15176
|
}
|
|
15176
15177
|
}
|
|
@@ -15178,7 +15179,7 @@ const noChildrenProp = defineRule({
|
|
|
15178
15179
|
});
|
|
15179
15180
|
//#endregion
|
|
15180
15181
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
15181
|
-
const MESSAGE$
|
|
15182
|
+
const MESSAGE$26 = "`React.cloneElement` breaks easily when the cloned element's props change.";
|
|
15182
15183
|
const noCloneElement = defineRule({
|
|
15183
15184
|
id: "no-clone-element",
|
|
15184
15185
|
title: "Use of cloneElement",
|
|
@@ -15191,7 +15192,7 @@ const noCloneElement = defineRule({
|
|
|
15191
15192
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
15192
15193
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
15193
15194
|
node: callee,
|
|
15194
|
-
message: MESSAGE$
|
|
15195
|
+
message: MESSAGE$26
|
|
15195
15196
|
});
|
|
15196
15197
|
return;
|
|
15197
15198
|
}
|
|
@@ -15204,7 +15205,7 @@ const noCloneElement = defineRule({
|
|
|
15204
15205
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
15205
15206
|
context.report({
|
|
15206
15207
|
node: callee,
|
|
15207
|
-
message: MESSAGE$
|
|
15208
|
+
message: MESSAGE$26
|
|
15208
15209
|
});
|
|
15209
15210
|
}
|
|
15210
15211
|
} })
|
|
@@ -15253,7 +15254,7 @@ const enclosingComponentOrHookName = (node) => {
|
|
|
15253
15254
|
};
|
|
15254
15255
|
//#endregion
|
|
15255
15256
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
15256
|
-
const MESSAGE$
|
|
15257
|
+
const MESSAGE$25 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
15257
15258
|
const CONTEXT_MODULES = [
|
|
15258
15259
|
"react",
|
|
15259
15260
|
"use-context-selector",
|
|
@@ -15289,7 +15290,7 @@ const noCreateContextInRender = defineRule({
|
|
|
15289
15290
|
if (!componentOrHookName) return;
|
|
15290
15291
|
context.report({
|
|
15291
15292
|
node,
|
|
15292
|
-
message: `${MESSAGE$
|
|
15293
|
+
message: `${MESSAGE$25} (called inside "${componentOrHookName}")`
|
|
15293
15294
|
});
|
|
15294
15295
|
} })
|
|
15295
15296
|
});
|
|
@@ -15429,7 +15430,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
15429
15430
|
});
|
|
15430
15431
|
//#endregion
|
|
15431
15432
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
15432
|
-
const MESSAGE$
|
|
15433
|
+
const MESSAGE$24 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
15433
15434
|
const noDanger = defineRule({
|
|
15434
15435
|
id: "no-danger",
|
|
15435
15436
|
title: "Use of dangerouslySetInnerHTML",
|
|
@@ -15442,7 +15443,7 @@ const noDanger = defineRule({
|
|
|
15442
15443
|
if (!propAttribute) return;
|
|
15443
15444
|
context.report({
|
|
15444
15445
|
node: propAttribute.name,
|
|
15445
|
-
message: MESSAGE$
|
|
15446
|
+
message: MESSAGE$24
|
|
15446
15447
|
});
|
|
15447
15448
|
},
|
|
15448
15449
|
CallExpression(node) {
|
|
@@ -15454,7 +15455,7 @@ const noDanger = defineRule({
|
|
|
15454
15455
|
const propertyKey = property.key;
|
|
15455
15456
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
15456
15457
|
node: propertyKey,
|
|
15457
|
-
message: MESSAGE$
|
|
15458
|
+
message: MESSAGE$24
|
|
15458
15459
|
});
|
|
15459
15460
|
}
|
|
15460
15461
|
}
|
|
@@ -15462,7 +15463,7 @@ const noDanger = defineRule({
|
|
|
15462
15463
|
});
|
|
15463
15464
|
//#endregion
|
|
15464
15465
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
15465
|
-
const MESSAGE$
|
|
15466
|
+
const MESSAGE$23 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
15466
15467
|
const isLineBreak = (child) => {
|
|
15467
15468
|
if (!isNodeOfType(child, "JSXText")) return false;
|
|
15468
15469
|
return child.value.trim().length === 0 && child.value.includes("\n");
|
|
@@ -15532,7 +15533,7 @@ const noDangerWithChildren = defineRule({
|
|
|
15532
15533
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
15533
15534
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
15534
15535
|
node: opening,
|
|
15535
|
-
message: MESSAGE$
|
|
15536
|
+
message: MESSAGE$23
|
|
15536
15537
|
});
|
|
15537
15538
|
},
|
|
15538
15539
|
CallExpression(node) {
|
|
@@ -15544,7 +15545,7 @@ const noDangerWithChildren = defineRule({
|
|
|
15544
15545
|
if (!propsShape.hasDangerously) return;
|
|
15545
15546
|
if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
|
|
15546
15547
|
node,
|
|
15547
|
-
message: MESSAGE$
|
|
15548
|
+
message: MESSAGE$23
|
|
15548
15549
|
});
|
|
15549
15550
|
}
|
|
15550
15551
|
})
|
|
@@ -16122,7 +16123,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
16122
16123
|
//#endregion
|
|
16123
16124
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
16124
16125
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
16125
|
-
const MESSAGE$
|
|
16126
|
+
const MESSAGE$22 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
16126
16127
|
const resolveSettings$20 = (settings) => {
|
|
16127
16128
|
const reactDoctor = settings?.["react-doctor"];
|
|
16128
16129
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -16141,7 +16142,7 @@ const noDidMountSetState = defineRule({
|
|
|
16141
16142
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
16142
16143
|
context.report({
|
|
16143
16144
|
node: node.callee,
|
|
16144
|
-
message: MESSAGE$
|
|
16145
|
+
message: MESSAGE$22
|
|
16145
16146
|
});
|
|
16146
16147
|
} };
|
|
16147
16148
|
}
|
|
@@ -16149,7 +16150,7 @@ const noDidMountSetState = defineRule({
|
|
|
16149
16150
|
//#endregion
|
|
16150
16151
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
16151
16152
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
16152
|
-
const MESSAGE$
|
|
16153
|
+
const MESSAGE$21 = "This can loop forever & freeze the component.";
|
|
16153
16154
|
const resolveSettings$19 = (settings) => {
|
|
16154
16155
|
const reactDoctor = settings?.["react-doctor"];
|
|
16155
16156
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -16168,7 +16169,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
16168
16169
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
16169
16170
|
context.report({
|
|
16170
16171
|
node: node.callee,
|
|
16171
|
-
message: MESSAGE$
|
|
16172
|
+
message: MESSAGE$21
|
|
16172
16173
|
});
|
|
16173
16174
|
} };
|
|
16174
16175
|
}
|
|
@@ -16191,7 +16192,7 @@ const isStateMemberExpression = (node) => {
|
|
|
16191
16192
|
};
|
|
16192
16193
|
//#endregion
|
|
16193
16194
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
16194
|
-
const MESSAGE$
|
|
16195
|
+
const MESSAGE$20 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
|
|
16195
16196
|
const shouldIgnoreMutation = (node) => {
|
|
16196
16197
|
let isConstructor = false;
|
|
16197
16198
|
let isInsideCallExpression = false;
|
|
@@ -16213,7 +16214,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
16213
16214
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
16214
16215
|
context.report({
|
|
16215
16216
|
node: reportNode,
|
|
16216
|
-
message: MESSAGE$
|
|
16217
|
+
message: MESSAGE$20
|
|
16217
16218
|
});
|
|
16218
16219
|
};
|
|
16219
16220
|
const noDirectMutationState = defineRule({
|
|
@@ -17801,7 +17802,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
17801
17802
|
"ReactDOM",
|
|
17802
17803
|
"ReactDom"
|
|
17803
17804
|
]);
|
|
17804
|
-
const MESSAGE$
|
|
17805
|
+
const MESSAGE$19 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
17805
17806
|
const noFindDomNode = defineRule({
|
|
17806
17807
|
id: "no-find-dom-node",
|
|
17807
17808
|
title: "Use of findDOMNode",
|
|
@@ -17812,7 +17813,7 @@ const noFindDomNode = defineRule({
|
|
|
17812
17813
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
17813
17814
|
context.report({
|
|
17814
17815
|
node: callee,
|
|
17815
|
-
message: MESSAGE$
|
|
17816
|
+
message: MESSAGE$19
|
|
17816
17817
|
});
|
|
17817
17818
|
return;
|
|
17818
17819
|
}
|
|
@@ -17823,7 +17824,7 @@ const noFindDomNode = defineRule({
|
|
|
17823
17824
|
if (callee.property.name !== "findDOMNode") return;
|
|
17824
17825
|
context.report({
|
|
17825
17826
|
node: callee.property,
|
|
17826
|
-
message: MESSAGE$
|
|
17827
|
+
message: MESSAGE$19
|
|
17827
17828
|
});
|
|
17828
17829
|
}
|
|
17829
17830
|
} })
|
|
@@ -18352,6 +18353,51 @@ const noIsMounted = defineRule({
|
|
|
18352
18353
|
} })
|
|
18353
18354
|
});
|
|
18354
18355
|
//#endregion
|
|
18356
|
+
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
18357
|
+
const MESSAGE$18 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
18358
|
+
const isJsxElementTypeReference = (node) => {
|
|
18359
|
+
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
18360
|
+
const typeName = node.typeName;
|
|
18361
|
+
if (!isNodeOfType(typeName, "TSQualifiedName")) return false;
|
|
18362
|
+
return isNodeOfType(typeName.left, "Identifier") && typeName.left.name === "JSX" && isNodeOfType(typeName.right, "Identifier") && typeName.right.name === "Element";
|
|
18363
|
+
};
|
|
18364
|
+
const extractReturnTypeAnnotation = (returnType) => {
|
|
18365
|
+
if (!returnType) return null;
|
|
18366
|
+
if (!isNodeOfType(returnType, "TSTypeAnnotation")) return null;
|
|
18367
|
+
return returnType.typeAnnotation ?? null;
|
|
18368
|
+
};
|
|
18369
|
+
const checkReturnType = (context, returnType) => {
|
|
18370
|
+
const typeAnnotation = extractReturnTypeAnnotation(returnType);
|
|
18371
|
+
if (!typeAnnotation) return;
|
|
18372
|
+
if (isJsxElementTypeReference(typeAnnotation)) context.report({
|
|
18373
|
+
node: typeAnnotation,
|
|
18374
|
+
message: MESSAGE$18
|
|
18375
|
+
});
|
|
18376
|
+
};
|
|
18377
|
+
const noJsxElementType = defineRule({
|
|
18378
|
+
id: "no-jsx-element-type",
|
|
18379
|
+
title: "No JSX.Element",
|
|
18380
|
+
severity: "error",
|
|
18381
|
+
recommendation: "Replace `JSX.Element` with `React.ReactNode`. `JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return.",
|
|
18382
|
+
create: (context) => ({
|
|
18383
|
+
FunctionDeclaration(node) {
|
|
18384
|
+
checkReturnType(context, node.returnType);
|
|
18385
|
+
},
|
|
18386
|
+
ArrowFunctionExpression(node) {
|
|
18387
|
+
checkReturnType(context, node.returnType);
|
|
18388
|
+
},
|
|
18389
|
+
FunctionExpression(node) {
|
|
18390
|
+
checkReturnType(context, node.returnType);
|
|
18391
|
+
},
|
|
18392
|
+
TSDeclareFunction(node) {
|
|
18393
|
+
checkReturnType(context, node.returnType);
|
|
18394
|
+
},
|
|
18395
|
+
TSMethodSignature(node) {
|
|
18396
|
+
checkReturnType(context, node.returnType);
|
|
18397
|
+
}
|
|
18398
|
+
})
|
|
18399
|
+
});
|
|
18400
|
+
//#endregion
|
|
18355
18401
|
//#region src/plugin/rules/design/no-justified-text.ts
|
|
18356
18402
|
const noJustifiedText = defineRule({
|
|
18357
18403
|
id: "no-justified-text",
|
|
@@ -24166,6 +24212,8 @@ const ENTRY_POINT_BASENAMES = new Set([
|
|
|
24166
24212
|
"global-error.jsx",
|
|
24167
24213
|
"route.tsx",
|
|
24168
24214
|
"route.jsx",
|
|
24215
|
+
"_layout.tsx",
|
|
24216
|
+
"_layout.jsx",
|
|
24169
24217
|
"_app.tsx",
|
|
24170
24218
|
"_app.jsx",
|
|
24171
24219
|
"_document.tsx",
|
|
@@ -25588,6 +25636,26 @@ const preferUseReducer = defineRule({
|
|
|
25588
25636
|
}
|
|
25589
25637
|
});
|
|
25590
25638
|
//#endregion
|
|
25639
|
+
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
25640
|
+
const queryDestructureResult = defineRule({
|
|
25641
|
+
id: "query-destructure-result",
|
|
25642
|
+
title: "Destructure TanStack Query result",
|
|
25643
|
+
tags: ["test-noise"],
|
|
25644
|
+
requires: ["tanstack-query"],
|
|
25645
|
+
severity: "error",
|
|
25646
|
+
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.",
|
|
25647
|
+
create: (context) => ({ VariableDeclarator(node) {
|
|
25648
|
+
if (!isNodeOfType(node.id, "Identifier")) return;
|
|
25649
|
+
if (!node.init || !isNodeOfType(node.init, "CallExpression")) return;
|
|
25650
|
+
const calleeName = isNodeOfType(node.init.callee, "Identifier") ? node.init.callee.name : null;
|
|
25651
|
+
if (!calleeName || !TANSTACK_QUERY_HOOKS.has(calleeName)) return;
|
|
25652
|
+
context.report({
|
|
25653
|
+
node: node.id,
|
|
25654
|
+
message: `Destructure ${calleeName}() results instead of assigning the whole query object, so TanStack Query only subscribes to the fields you use.`
|
|
25655
|
+
});
|
|
25656
|
+
} })
|
|
25657
|
+
});
|
|
25658
|
+
//#endregion
|
|
25591
25659
|
//#region src/plugin/rules/tanstack-query/query-mutation-missing-invalidation.ts
|
|
25592
25660
|
const queryMutationMissingInvalidation = defineRule({
|
|
25593
25661
|
id: "query-mutation-missing-invalidation",
|
|
@@ -36436,6 +36504,17 @@ const reactDoctorRules = [
|
|
|
36436
36504
|
category: "Bugs"
|
|
36437
36505
|
}
|
|
36438
36506
|
},
|
|
36507
|
+
{
|
|
36508
|
+
key: "react-doctor/no-jsx-element-type",
|
|
36509
|
+
id: "no-jsx-element-type",
|
|
36510
|
+
source: "react-doctor",
|
|
36511
|
+
originallyExternal: false,
|
|
36512
|
+
rule: {
|
|
36513
|
+
...noJsxElementType,
|
|
36514
|
+
framework: "global",
|
|
36515
|
+
category: "Bugs"
|
|
36516
|
+
}
|
|
36517
|
+
},
|
|
36439
36518
|
{
|
|
36440
36519
|
key: "react-doctor/no-justified-text",
|
|
36441
36520
|
id: "no-justified-text",
|
|
@@ -37261,6 +37340,17 @@ const reactDoctorRules = [
|
|
|
37261
37340
|
category: "Bugs"
|
|
37262
37341
|
}
|
|
37263
37342
|
},
|
|
37343
|
+
{
|
|
37344
|
+
key: "react-doctor/query-destructure-result",
|
|
37345
|
+
id: "query-destructure-result",
|
|
37346
|
+
source: "react-doctor",
|
|
37347
|
+
originallyExternal: false,
|
|
37348
|
+
rule: {
|
|
37349
|
+
...queryDestructureResult,
|
|
37350
|
+
framework: "tanstack-query",
|
|
37351
|
+
category: "Bugs"
|
|
37352
|
+
}
|
|
37353
|
+
},
|
|
37264
37354
|
{
|
|
37265
37355
|
key: "react-doctor/query-mutation-missing-invalidation",
|
|
37266
37356
|
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.
|
|
3
|
+
"version": "0.4.0-dev.73dcb20",
|
|
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",
|