oxlint-plugin-react-doctor 0.4.2-dev.727894a → 0.4.2-dev.829655c
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/LICENSE +1 -1
- package/dist/index.d.ts +0 -38
- package/dist/index.js +50 -342
- package/package.json +1 -1
package/LICENSE
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1620,25 +1620,6 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
1620
1620
|
readonly recommendation?: string;
|
|
1621
1621
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
1622
1622
|
};
|
|
1623
|
-
}, {
|
|
1624
|
-
readonly key: "react-doctor/jsx-no-target-blank";
|
|
1625
|
-
readonly id: "jsx-no-target-blank";
|
|
1626
|
-
readonly source: "react-doctor";
|
|
1627
|
-
readonly originallyExternal: true;
|
|
1628
|
-
readonly rule: {
|
|
1629
|
-
readonly framework: "global";
|
|
1630
|
-
readonly category: "Security";
|
|
1631
|
-
readonly id: string;
|
|
1632
|
-
readonly title?: string;
|
|
1633
|
-
readonly severity: RuleSeverity;
|
|
1634
|
-
readonly requires?: ReadonlyArray<string>;
|
|
1635
|
-
readonly disabledBy?: ReadonlyArray<string>;
|
|
1636
|
-
readonly tags?: ReadonlyArray<string>;
|
|
1637
|
-
readonly defaultEnabled?: boolean;
|
|
1638
|
-
readonly lifecycle?: "retired";
|
|
1639
|
-
readonly recommendation?: string;
|
|
1640
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
1641
|
-
};
|
|
1642
1623
|
}, {
|
|
1643
1624
|
readonly key: "react-doctor/jsx-no-undef";
|
|
1644
1625
|
readonly id: "jsx-no-undef";
|
|
@@ -8108,25 +8089,6 @@ declare const RULES: readonly [{
|
|
|
8108
8089
|
readonly recommendation?: string;
|
|
8109
8090
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
8110
8091
|
};
|
|
8111
|
-
}, {
|
|
8112
|
-
readonly key: "react-doctor/jsx-no-target-blank";
|
|
8113
|
-
readonly id: "jsx-no-target-blank";
|
|
8114
|
-
readonly source: "react-doctor";
|
|
8115
|
-
readonly originallyExternal: true;
|
|
8116
|
-
readonly rule: {
|
|
8117
|
-
readonly framework: "global";
|
|
8118
|
-
readonly category: "Security";
|
|
8119
|
-
readonly id: string;
|
|
8120
|
-
readonly title?: string;
|
|
8121
|
-
readonly severity: RuleSeverity;
|
|
8122
|
-
readonly requires?: ReadonlyArray<string>;
|
|
8123
|
-
readonly disabledBy?: ReadonlyArray<string>;
|
|
8124
|
-
readonly tags?: ReadonlyArray<string>;
|
|
8125
|
-
readonly defaultEnabled?: boolean;
|
|
8126
|
-
readonly lifecycle?: "retired";
|
|
8127
|
-
readonly recommendation?: string;
|
|
8128
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
8129
|
-
};
|
|
8130
8092
|
}, {
|
|
8131
8093
|
readonly key: "react-doctor/jsx-no-undef";
|
|
8132
8094
|
readonly id: "jsx-no-undef";
|
package/dist/index.js
CHANGED
|
@@ -887,7 +887,7 @@ const PREFER_ALT = "Screen readers skip a decorative image more reliably with `a
|
|
|
887
887
|
const MESSAGE_OBJECT = "Blind users can't use this `<object>` because screen readers can't describe it, so add `alt`, `aria-label`, `aria-labelledby`, `title`, or inner fallback text.";
|
|
888
888
|
const MESSAGE_AREA = "Blind users can't use this `<area>` of the image map because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
|
|
889
889
|
const MESSAGE_INPUT_IMAGE = "Blind users can't use this image button because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
|
|
890
|
-
const resolveSettings$
|
|
890
|
+
const resolveSettings$52 = (settings) => {
|
|
891
891
|
const reactDoctor = settings?.["react-doctor"];
|
|
892
892
|
return typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.altText ?? {} : {};
|
|
893
893
|
};
|
|
@@ -1004,7 +1004,7 @@ const altText = defineRule({
|
|
|
1004
1004
|
category: "Accessibility",
|
|
1005
1005
|
create: (context) => {
|
|
1006
1006
|
if (isNextjsMetadataImageRouteFilename(normalizeFilename$1(context.filename ?? ""))) return {};
|
|
1007
|
-
const settings = resolveSettings$
|
|
1007
|
+
const settings = resolveSettings$52(context.settings);
|
|
1008
1008
|
const checkImg = !settings.elements || settings.elements.includes("img");
|
|
1009
1009
|
const checkObject = !settings.elements || settings.elements.includes("object");
|
|
1010
1010
|
const checkArea = !settings.elements || settings.elements.includes("area");
|
|
@@ -1048,7 +1048,7 @@ const DEFAULT_AMBIGUOUS = [
|
|
|
1048
1048
|
"a link",
|
|
1049
1049
|
"learn more"
|
|
1050
1050
|
];
|
|
1051
|
-
const resolveSettings$
|
|
1051
|
+
const resolveSettings$51 = (settings) => {
|
|
1052
1052
|
const reactDoctor = settings?.["react-doctor"];
|
|
1053
1053
|
return { words: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorAmbiguousText ?? {} : {}).words ?? DEFAULT_AMBIGUOUS };
|
|
1054
1054
|
};
|
|
@@ -1089,7 +1089,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
1089
1089
|
recommendation: "Name where a link goes. Avoid 'click here', 'learn more', and 'link'.",
|
|
1090
1090
|
category: "Accessibility",
|
|
1091
1091
|
create: (context) => {
|
|
1092
|
-
const settings = resolveSettings$
|
|
1092
|
+
const settings = resolveSettings$51(context.settings);
|
|
1093
1093
|
const ambiguousSet = new Set(settings.words.map((word) => word.toLowerCase()));
|
|
1094
1094
|
return { JSXElement(node) {
|
|
1095
1095
|
if (getElementType(node.openingElement, context.settings) !== "a") return;
|
|
@@ -1139,7 +1139,7 @@ const getStaticTemplateLiteralValue = (templateLiteral) => {
|
|
|
1139
1139
|
const MESSAGE_MISSING_HREF = "Keyboard users can't reach this link because it has no `href`, so add a real `href` (or use `<button>` for actions).";
|
|
1140
1140
|
const MESSAGE_INCORRECT_HREF = "Keyboard users can't reach this link because its `href` goes nowhere (`#`, `javascript:`, or empty), so point it at a real destination.";
|
|
1141
1141
|
const MESSAGE_CANT_BE_ANCHOR = "Keyboard users can't trigger this link because it's a click handler with no real `href`, so use `<button>` instead.";
|
|
1142
|
-
const resolveSettings$
|
|
1142
|
+
const resolveSettings$50 = (settings) => {
|
|
1143
1143
|
const reactDoctor = settings?.["react-doctor"];
|
|
1144
1144
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorIsValid ?? {} : {};
|
|
1145
1145
|
const jsxA11y = settings?.["jsx-a11y"];
|
|
@@ -1178,7 +1178,7 @@ const anchorIsValid = defineRule({
|
|
|
1178
1178
|
recommendation: "Give links a real destination. Use `<button>` for in-page actions.",
|
|
1179
1179
|
category: "Accessibility",
|
|
1180
1180
|
create: (context) => {
|
|
1181
|
-
const settings = resolveSettings$
|
|
1181
|
+
const settings = resolveSettings$50(context.settings);
|
|
1182
1182
|
return { JSXOpeningElement(node) {
|
|
1183
1183
|
if (getElementType(node, context.settings) !== "a") return;
|
|
1184
1184
|
let hrefAttribute;
|
|
@@ -2213,7 +2213,7 @@ const PRESENTATION_ROLES$2 = new Set(["presentation", "none"]);
|
|
|
2213
2213
|
//#endregion
|
|
2214
2214
|
//#region src/plugin/rules/a11y/aria-role.ts
|
|
2215
2215
|
const buildBaseMessage = (suffix) => `Screen reader users get no help from this \`role\` because it isn't a valid ARIA role, so use a real, non-abstract role.${suffix}`;
|
|
2216
|
-
const resolveSettings$
|
|
2216
|
+
const resolveSettings$49 = (settings) => {
|
|
2217
2217
|
const reactDoctor = settings?.["react-doctor"];
|
|
2218
2218
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.ariaRole ?? {} : {};
|
|
2219
2219
|
return {
|
|
@@ -2229,7 +2229,7 @@ const ariaRole = defineRule({
|
|
|
2229
2229
|
recommendation: "Use a real, non-abstract ARIA role.",
|
|
2230
2230
|
category: "Accessibility",
|
|
2231
2231
|
create: (context) => {
|
|
2232
|
-
const settings = resolveSettings$
|
|
2232
|
+
const settings = resolveSettings$49(context.settings);
|
|
2233
2233
|
return { JSXOpeningElement(node) {
|
|
2234
2234
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
2235
2235
|
if (!roleAttribute) return;
|
|
@@ -3255,7 +3255,7 @@ const FORM_CONTROL_TAGS = new Set([
|
|
|
3255
3255
|
"select",
|
|
3256
3256
|
"form"
|
|
3257
3257
|
]);
|
|
3258
|
-
const resolveSettings$
|
|
3258
|
+
const resolveSettings$48 = (settings) => {
|
|
3259
3259
|
const reactDoctor = settings?.["react-doctor"];
|
|
3260
3260
|
return { inputComponents: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.autocompleteValid ?? {} : {}).inputComponents ?? [] };
|
|
3261
3261
|
};
|
|
@@ -3267,7 +3267,7 @@ const autocompleteValid = defineRule({
|
|
|
3267
3267
|
recommendation: "Use a valid autofill token in `autoComplete`.",
|
|
3268
3268
|
category: "Accessibility",
|
|
3269
3269
|
create: (context) => {
|
|
3270
|
-
const settings = resolveSettings$
|
|
3270
|
+
const settings = resolveSettings$48(context.settings);
|
|
3271
3271
|
return { JSXOpeningElement: (node) => {
|
|
3272
3272
|
const tag = getElementType(node, context.settings);
|
|
3273
3273
|
if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.includes(tag)) return;
|
|
@@ -3320,7 +3320,7 @@ const isCreateElementCall = (node) => {
|
|
|
3320
3320
|
//#region src/plugin/rules/react-builtins/button-has-type.ts
|
|
3321
3321
|
const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
|
|
3322
3322
|
const INVALID_MESSAGE = "This button's `type` is invalid.";
|
|
3323
|
-
const resolveSettings$
|
|
3323
|
+
const resolveSettings$47 = (settings) => {
|
|
3324
3324
|
const reactDoctor = settings?.["react-doctor"];
|
|
3325
3325
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.buttonHasType ?? {} : {};
|
|
3326
3326
|
return {
|
|
@@ -3362,7 +3362,7 @@ const buttonHasType = defineRule({
|
|
|
3362
3362
|
severity: "warn",
|
|
3363
3363
|
recommendation: "Always set a `type` on a `<button>`: `type=\"button\"`, `\"submit\"`, or `\"reset\"`.",
|
|
3364
3364
|
create: (context) => {
|
|
3365
|
-
const settings = resolveSettings$
|
|
3365
|
+
const settings = resolveSettings$47(context.settings);
|
|
3366
3366
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
3367
3367
|
return {
|
|
3368
3368
|
JSXOpeningElement(node) {
|
|
@@ -3431,7 +3431,7 @@ const buttonHasType = defineRule({
|
|
|
3431
3431
|
//#region src/plugin/rules/react-builtins/checked-requires-onchange-or-readonly.ts
|
|
3432
3432
|
const MISSING_MESSAGE$1 = "Your users can't toggle this input because `checked` has no `onChange`.";
|
|
3433
3433
|
const EXCLUSIVE_MESSAGE = "This input behaves unpredictably with both `checked` & `defaultChecked` set.";
|
|
3434
|
-
const resolveSettings$
|
|
3434
|
+
const resolveSettings$46 = (settings) => {
|
|
3435
3435
|
const reactDoctor = settings?.["react-doctor"];
|
|
3436
3436
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.checkedRequiresOnchangeOrReadonly ?? {} : {};
|
|
3437
3437
|
return {
|
|
@@ -3484,7 +3484,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
3484
3484
|
recommendation: "Add `onChange` (controlled) or `readOnly` (display-only), or use `defaultChecked` for an uncontrolled checkbox.",
|
|
3485
3485
|
category: "Correctness",
|
|
3486
3486
|
create: (context) => {
|
|
3487
|
-
const settings = resolveSettings$
|
|
3487
|
+
const settings = resolveSettings$46(context.settings);
|
|
3488
3488
|
const reportFromPresence = (presence) => {
|
|
3489
3489
|
if (presence.checkedNode && presence.defaultCheckedNode && !settings.ignoreExclusiveCheckedAttribute) context.report({
|
|
3490
3490
|
node: presence.checkedNode,
|
|
@@ -3708,7 +3708,7 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
|
|
|
3708
3708
|
const LABEL_ELEMENT = "label";
|
|
3709
3709
|
const DEFAULT_DEPTH = 5;
|
|
3710
3710
|
const MAX_DEPTH = 25;
|
|
3711
|
-
const resolveSettings$
|
|
3711
|
+
const resolveSettings$45 = (settings) => {
|
|
3712
3712
|
const reactDoctor = settings?.["react-doctor"];
|
|
3713
3713
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.controlHasAssociatedLabel ?? {} : {};
|
|
3714
3714
|
return {
|
|
@@ -3828,7 +3828,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
3828
3828
|
recommendation: "Give every interactive control a label screen readers can read.",
|
|
3829
3829
|
category: "Accessibility",
|
|
3830
3830
|
create: (context) => {
|
|
3831
|
-
const settings = resolveSettings$
|
|
3831
|
+
const settings = resolveSettings$45(context.settings);
|
|
3832
3832
|
const isTestlikeFile = isTestlikeFilename(context.filename);
|
|
3833
3833
|
return { JSXElement(node) {
|
|
3834
3834
|
if (isTestlikeFile) return;
|
|
@@ -4199,7 +4199,7 @@ const DEFAULT_ADDITIONAL_HOCS = [
|
|
|
4199
4199
|
"lazy",
|
|
4200
4200
|
"withTracking"
|
|
4201
4201
|
];
|
|
4202
|
-
const resolveSettings$
|
|
4202
|
+
const resolveSettings$44 = (settings) => {
|
|
4203
4203
|
const reactDoctor = settings?.["react-doctor"];
|
|
4204
4204
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
|
|
4205
4205
|
const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
|
|
@@ -4391,7 +4391,7 @@ const displayName = defineRule({
|
|
|
4391
4391
|
recommendation: "Give each component a `displayName` so DevTools shows a clear name.",
|
|
4392
4392
|
category: "Architecture",
|
|
4393
4393
|
create: (context) => {
|
|
4394
|
-
const settings = resolveSettings$
|
|
4394
|
+
const settings = resolveSettings$44(context.settings);
|
|
4395
4395
|
const ignoreNamed = settings.ignoreTranspilerName ? false : true;
|
|
4396
4396
|
const reportAt = (node) => {
|
|
4397
4397
|
context.report({
|
|
@@ -6263,7 +6263,7 @@ const compileGlob = (pattern) => {
|
|
|
6263
6263
|
//#endregion
|
|
6264
6264
|
//#region src/plugin/rules/react-builtins/forbid-component-props.ts
|
|
6265
6265
|
const DEFAULT_FORBID_PROPS = ["className", "style"];
|
|
6266
|
-
const resolveSettings$
|
|
6266
|
+
const resolveSettings$43 = (settings) => {
|
|
6267
6267
|
const reactDoctor = settings?.["react-doctor"];
|
|
6268
6268
|
const forbid = (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid;
|
|
6269
6269
|
if (!forbid || forbid.length === 0) return { forbid: DEFAULT_FORBID_PROPS };
|
|
@@ -6317,7 +6317,7 @@ const forbidComponentProps = defineRule({
|
|
|
6317
6317
|
recommendation: "List the props you want to block per component in the `forbidComponentProps.forbid` setting.",
|
|
6318
6318
|
category: "Architecture",
|
|
6319
6319
|
create: (context) => {
|
|
6320
|
-
const entries = resolveSettings$
|
|
6320
|
+
const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
|
|
6321
6321
|
return { JSXOpeningElement(node) {
|
|
6322
6322
|
if (entries.length === 0) return;
|
|
6323
6323
|
if (!isSupportedJsxName(node.name)) return;
|
|
@@ -6346,7 +6346,7 @@ const forbidComponentProps = defineRule({
|
|
|
6346
6346
|
//#endregion
|
|
6347
6347
|
//#region src/plugin/rules/react-builtins/forbid-dom-props.ts
|
|
6348
6348
|
const buildMessage$23 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags.`;
|
|
6349
|
-
const resolveSettings$
|
|
6349
|
+
const resolveSettings$42 = (settings) => {
|
|
6350
6350
|
const reactDoctor = settings?.["react-doctor"];
|
|
6351
6351
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
|
|
6352
6352
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -6368,7 +6368,7 @@ const forbidDomProps = defineRule({
|
|
|
6368
6368
|
recommendation: "List the HTML props you want to block in the `forbidDomProps.forbid` setting.",
|
|
6369
6369
|
category: "Architecture",
|
|
6370
6370
|
create: (context) => {
|
|
6371
|
-
const forbidMap = resolveSettings$
|
|
6371
|
+
const forbidMap = resolveSettings$42(context.settings);
|
|
6372
6372
|
return { JSXOpeningElement(node) {
|
|
6373
6373
|
if (forbidMap.size === 0) return;
|
|
6374
6374
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
@@ -6455,7 +6455,7 @@ const isReactFunctionCall = (node, expectedCall) => {
|
|
|
6455
6455
|
//#endregion
|
|
6456
6456
|
//#region src/plugin/rules/react-builtins/forbid-elements.ts
|
|
6457
6457
|
const buildMessage$22 = (element, customHelp) => customHelp ? `Your project blocks \`<${element}>\` here. ${customHelp}` : `Your project blocks \`<${element}>\` here.`;
|
|
6458
|
-
const resolveSettings$
|
|
6458
|
+
const resolveSettings$41 = (settings) => {
|
|
6459
6459
|
const reactDoctor = settings?.["react-doctor"];
|
|
6460
6460
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
|
|
6461
6461
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -6471,7 +6471,7 @@ const forbidElements = defineRule({
|
|
|
6471
6471
|
recommendation: "List the element names you want to block in the `forbidElements.forbid` setting.",
|
|
6472
6472
|
category: "Architecture",
|
|
6473
6473
|
create: (context) => {
|
|
6474
|
-
const forbidMap = resolveSettings$
|
|
6474
|
+
const forbidMap = resolveSettings$41(context.settings);
|
|
6475
6475
|
return {
|
|
6476
6476
|
JSXOpeningElement(node) {
|
|
6477
6477
|
if (forbidMap.size === 0) return;
|
|
@@ -6542,7 +6542,7 @@ const DEFAULT_HEADING_TAGS = [
|
|
|
6542
6542
|
"h5",
|
|
6543
6543
|
"h6"
|
|
6544
6544
|
];
|
|
6545
|
-
const resolveSettings$
|
|
6545
|
+
const resolveSettings$40 = (settings) => {
|
|
6546
6546
|
const reactDoctor = settings?.["react-doctor"];
|
|
6547
6547
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
|
|
6548
6548
|
return { headingTags: [...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []] };
|
|
@@ -6555,7 +6555,7 @@ const headingHasContent = defineRule({
|
|
|
6555
6555
|
recommendation: "Put readable text in every heading.",
|
|
6556
6556
|
category: "Accessibility",
|
|
6557
6557
|
create: (context) => {
|
|
6558
|
-
const settings = resolveSettings$
|
|
6558
|
+
const settings = resolveSettings$40(context.settings);
|
|
6559
6559
|
return { JSXOpeningElement(node) {
|
|
6560
6560
|
const elementType = getElementType(node, context.settings);
|
|
6561
6561
|
if (!settings.headingTags.includes(elementType)) return;
|
|
@@ -6575,7 +6575,7 @@ const headingHasContent = defineRule({
|
|
|
6575
6575
|
//#region src/plugin/rules/react-builtins/hook-use-state.ts
|
|
6576
6576
|
const REQUIRE_DESTRUCTURE_MESSAGE = "This `useState` result is hard to follow.";
|
|
6577
6577
|
const NAMING_CONVENTION_MESSAGE = "The setter here is hard to spot.";
|
|
6578
|
-
const resolveSettings$
|
|
6578
|
+
const resolveSettings$39 = (settings) => {
|
|
6579
6579
|
const reactDoctor = settings?.["react-doctor"];
|
|
6580
6580
|
return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
|
|
6581
6581
|
};
|
|
@@ -6602,7 +6602,7 @@ const hookUseState = defineRule({
|
|
|
6602
6602
|
recommendation: "Destructure useState as `const [thing, setThing] = useState(…)`.",
|
|
6603
6603
|
category: "Architecture",
|
|
6604
6604
|
create: (context) => {
|
|
6605
|
-
const { allowDestructuredState } = resolveSettings$
|
|
6605
|
+
const { allowDestructuredState } = resolveSettings$39(context.settings);
|
|
6606
6606
|
return { CallExpression(node) {
|
|
6607
6607
|
if (!isReactFunctionCall(node, "useState")) return;
|
|
6608
6608
|
const parent = node.parent;
|
|
@@ -6705,7 +6705,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
6705
6705
|
//#endregion
|
|
6706
6706
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
6707
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
|
-
const resolveSettings$
|
|
6708
|
+
const resolveSettings$38 = (settings) => {
|
|
6709
6709
|
const reactDoctor = settings?.["react-doctor"];
|
|
6710
6710
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
6711
6711
|
};
|
|
@@ -6742,7 +6742,7 @@ const htmlHasLang = defineRule({
|
|
|
6742
6742
|
recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
|
|
6743
6743
|
category: "Accessibility",
|
|
6744
6744
|
create: (context) => {
|
|
6745
|
-
const settings = resolveSettings$
|
|
6745
|
+
const settings = resolveSettings$38(context.settings);
|
|
6746
6746
|
const tagSet = new Set(settings.htmlTags);
|
|
6747
6747
|
return { JSXOpeningElement(node) {
|
|
6748
6748
|
const tag = getElementType(node, context.settings);
|
|
@@ -7138,7 +7138,7 @@ const DEFAULT_REDUNDANT_WORDS = [
|
|
|
7138
7138
|
"photo",
|
|
7139
7139
|
"picture"
|
|
7140
7140
|
];
|
|
7141
|
-
const resolveSettings$
|
|
7141
|
+
const resolveSettings$37 = (settings) => {
|
|
7142
7142
|
const reactDoctor = settings?.["react-doctor"];
|
|
7143
7143
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
|
|
7144
7144
|
return {
|
|
@@ -7185,7 +7185,7 @@ const imgRedundantAlt = defineRule({
|
|
|
7185
7185
|
recommendation: "Do not put 'image' or 'photo' in alt text. Describe what is shown.",
|
|
7186
7186
|
category: "Accessibility",
|
|
7187
7187
|
create: (context) => {
|
|
7188
|
-
const settings = resolveSettings$
|
|
7188
|
+
const settings = resolveSettings$37(context.settings);
|
|
7189
7189
|
return { JSXOpeningElement(node) {
|
|
7190
7190
|
const tag = getElementType(node, context.settings);
|
|
7191
7191
|
if (!settings.components.includes(tag)) return;
|
|
@@ -7275,7 +7275,7 @@ const DEFAULT_TABBABLE_ROLES = [
|
|
|
7275
7275
|
"switch",
|
|
7276
7276
|
"textbox"
|
|
7277
7277
|
];
|
|
7278
|
-
const resolveSettings$
|
|
7278
|
+
const resolveSettings$36 = (settings) => {
|
|
7279
7279
|
const reactDoctor = settings?.["react-doctor"];
|
|
7280
7280
|
return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
|
|
7281
7281
|
};
|
|
@@ -7287,7 +7287,7 @@ const interactiveSupportsFocus = defineRule({
|
|
|
7287
7287
|
recommendation: "Add `tabIndex` to elements with interactive roles and handlers.",
|
|
7288
7288
|
category: "Accessibility",
|
|
7289
7289
|
create: (context) => {
|
|
7290
|
-
const settings = resolveSettings$
|
|
7290
|
+
const settings = resolveSettings$36(context.settings);
|
|
7291
7291
|
const tabbableSet = new Set(settings.tabbable);
|
|
7292
7292
|
return { JSXOpeningElement(node) {
|
|
7293
7293
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
@@ -8585,7 +8585,7 @@ const jsTosortedImmutable = defineRule({
|
|
|
8585
8585
|
const NEVER_MESSAGE$2 = () => `This prop is written inconsistently.`;
|
|
8586
8586
|
const ALWAYS_MESSAGE$2 = () => `This prop is written inconsistently.`;
|
|
8587
8587
|
const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing.`;
|
|
8588
|
-
const resolveSettings$
|
|
8588
|
+
const resolveSettings$35 = (settings) => {
|
|
8589
8589
|
const reactDoctor = settings?.["react-doctor"];
|
|
8590
8590
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
|
|
8591
8591
|
return {
|
|
@@ -8603,7 +8603,7 @@ const jsxBooleanValue = defineRule({
|
|
|
8603
8603
|
recommendation: "Pick a single boolean-attribute style across the codebase (default: omit `={true}`).",
|
|
8604
8604
|
category: "Architecture",
|
|
8605
8605
|
create: (context) => {
|
|
8606
|
-
const settings = resolveSettings$
|
|
8606
|
+
const settings = resolveSettings$35(context.settings);
|
|
8607
8607
|
const alwaysSet = new Set(settings.always);
|
|
8608
8608
|
const neverSet = new Set(settings.never);
|
|
8609
8609
|
return { JSXAttribute(node) {
|
|
@@ -8657,7 +8657,7 @@ const jsxBooleanValue = defineRule({
|
|
|
8657
8657
|
const UNNECESSARY_BRACES_MESSAGE = "These curly braces do nothing here.";
|
|
8658
8658
|
const REQUIRED_BRACES_MESSAGE = "This value needs curly braces `{ }` to read as an expression.";
|
|
8659
8659
|
const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
|
|
8660
|
-
const resolveSettings$
|
|
8660
|
+
const resolveSettings$34 = (settings) => {
|
|
8661
8661
|
const reactDoctor = settings?.["react-doctor"];
|
|
8662
8662
|
const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
|
|
8663
8663
|
if (isAllowedMode(ruleSettingsRaw)) return {
|
|
@@ -8753,7 +8753,7 @@ const jsxCurlyBracePresence = defineRule({
|
|
|
8753
8753
|
recommendation: "Pick a consistent quoting style for JSX literal values.",
|
|
8754
8754
|
category: "Architecture",
|
|
8755
8755
|
create: (context) => {
|
|
8756
|
-
const settings = resolveSettings$
|
|
8756
|
+
const settings = resolveSettings$34(context.settings);
|
|
8757
8757
|
return {
|
|
8758
8758
|
JSXAttribute(node) {
|
|
8759
8759
|
const value = node.value;
|
|
@@ -8836,7 +8836,7 @@ const containsJsxElement = (root) => {
|
|
|
8836
8836
|
//#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
|
|
8837
8837
|
const JSX_NOT_ALLOWED = (extension) => `This file has JSX but a \`${extension}\` name.`;
|
|
8838
8838
|
const EXTENSION_ONLY_FOR_JSX = (extension) => `\`${extension}\` files are meant for JSX, but this one has none.`;
|
|
8839
|
-
const resolveSettings$
|
|
8839
|
+
const resolveSettings$33 = (settings) => {
|
|
8840
8840
|
const reactDoctor = settings?.["react-doctor"];
|
|
8841
8841
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
|
|
8842
8842
|
return {
|
|
@@ -8858,7 +8858,7 @@ const jsxFilenameExtension = defineRule({
|
|
|
8858
8858
|
recommendation: "Name files with JSX `.jsx` or `.tsx`, or whatever extension your project uses.",
|
|
8859
8859
|
category: "Architecture",
|
|
8860
8860
|
create: (context) => {
|
|
8861
|
-
const settings = resolveSettings$
|
|
8861
|
+
const settings = resolveSettings$33(context.settings);
|
|
8862
8862
|
const allowedExtensions = normalizeExtensions(settings.extensions);
|
|
8863
8863
|
const filename = normalizeFilename$1(context.filename ?? "fixture.tsx");
|
|
8864
8864
|
const extensionOnly = path.extname(filename).slice(1);
|
|
@@ -8912,7 +8912,7 @@ const isJsxFragmentElement = (node) => {
|
|
|
8912
8912
|
//#region src/plugin/rules/react-builtins/jsx-fragments.ts
|
|
8913
8913
|
const SYNTAX_MESSAGE = "This fragment is written inconsistently.";
|
|
8914
8914
|
const ELEMENT_MESSAGE = "This fragment is written inconsistently.";
|
|
8915
|
-
const resolveSettings$
|
|
8915
|
+
const resolveSettings$32 = (settings) => {
|
|
8916
8916
|
const reactDoctor = settings?.["react-doctor"];
|
|
8917
8917
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
|
|
8918
8918
|
};
|
|
@@ -8924,7 +8924,7 @@ const jsxFragments = defineRule({
|
|
|
8924
8924
|
recommendation: "Pick one fragment style across the codebase.",
|
|
8925
8925
|
category: "Architecture",
|
|
8926
8926
|
create: (context) => {
|
|
8927
|
-
const { mode } = resolveSettings$
|
|
8927
|
+
const { mode } = resolveSettings$32(context.settings);
|
|
8928
8928
|
return {
|
|
8929
8929
|
JSXElement(node) {
|
|
8930
8930
|
if (mode !== "syntax") return;
|
|
@@ -8968,7 +8968,7 @@ const buildPropRegex = (handlerPropPrefix) => {
|
|
|
8968
8968
|
const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
8969
8969
|
return new RegExp(`^(${escaped})[A-Z].*$`);
|
|
8970
8970
|
};
|
|
8971
|
-
const resolveSettings$
|
|
8971
|
+
const resolveSettings$31 = (settings) => {
|
|
8972
8972
|
const reactDoctor = settings?.["react-doctor"];
|
|
8973
8973
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
|
|
8974
8974
|
let handlerPrefix = DEFAULT_HANDLER_PREFIX;
|
|
@@ -9044,7 +9044,7 @@ const jsxHandlerNames = defineRule({
|
|
|
9044
9044
|
recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers.",
|
|
9045
9045
|
category: "Architecture",
|
|
9046
9046
|
create: (context) => {
|
|
9047
|
-
const settings = resolveSettings$
|
|
9047
|
+
const settings = resolveSettings$31(context.settings);
|
|
9048
9048
|
return { JSXAttribute(node) {
|
|
9049
9049
|
if (settings.ignoreComponentNames.length > 0) {
|
|
9050
9050
|
const opening = node.parent;
|
|
@@ -9129,7 +9129,7 @@ const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reo
|
|
|
9129
9129
|
const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
|
|
9130
9130
|
const KEY_BEFORE_SPREAD = "The `{...spread}` can overwrite this `key` & break React's tracking.";
|
|
9131
9131
|
const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
|
|
9132
|
-
const resolveSettings$
|
|
9132
|
+
const resolveSettings$30 = (settings) => {
|
|
9133
9133
|
const reactDoctor = settings?.["react-doctor"];
|
|
9134
9134
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
|
|
9135
9135
|
return {
|
|
@@ -9277,7 +9277,7 @@ const jsxKey = defineRule({
|
|
|
9277
9277
|
severity: "error",
|
|
9278
9278
|
recommendation: "Add a `key={...}` prop to each element produced inside `.map` / array literal.",
|
|
9279
9279
|
create: (context) => {
|
|
9280
|
-
const settings = resolveSettings$
|
|
9280
|
+
const settings = resolveSettings$30(context.settings);
|
|
9281
9281
|
return {
|
|
9282
9282
|
JSXElement(node) {
|
|
9283
9283
|
const openingElement = node.openingElement;
|
|
@@ -9514,7 +9514,7 @@ const findVariableInitializer = (referenceNode, bindingName) => {
|
|
|
9514
9514
|
//#region src/plugin/rules/react-builtins/jsx-max-depth.ts
|
|
9515
9515
|
const buildMessage$18 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
|
|
9516
9516
|
const DEFAULT_MAX_DEPTH = 14;
|
|
9517
|
-
const resolveSettings$
|
|
9517
|
+
const resolveSettings$29 = (settings) => {
|
|
9518
9518
|
const reactDoctor = settings?.["react-doctor"];
|
|
9519
9519
|
return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
|
|
9520
9520
|
};
|
|
@@ -9574,7 +9574,7 @@ const jsxMaxDepth = defineRule({
|
|
|
9574
9574
|
recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
|
|
9575
9575
|
category: "Architecture",
|
|
9576
9576
|
create: (context) => {
|
|
9577
|
-
const { max } = resolveSettings$
|
|
9577
|
+
const { max } = resolveSettings$29(context.settings);
|
|
9578
9578
|
const checkNode = (node) => {
|
|
9579
9579
|
if (!isLeafJsxNode(node)) return;
|
|
9580
9580
|
const total = computeJsxAncestorDepth(node) + computeChildrenDepth(node.children ?? [], /* @__PURE__ */ new Set());
|
|
@@ -11269,7 +11269,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
11269
11269
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
11270
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
|
-
const resolveSettings$
|
|
11272
|
+
const resolveSettings$28 = (settings) => {
|
|
11273
11273
|
const reactDoctor = settings?.["react-doctor"];
|
|
11274
11274
|
if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
|
|
11275
11275
|
return reactDoctor.jsxNoScriptUrl ?? {};
|
|
@@ -11295,7 +11295,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
11295
11295
|
recommendation: "Replace `javascript:` URLs with `onClick` or `onSubmit` handlers. React 19 blocks them anyway.",
|
|
11296
11296
|
category: "Security",
|
|
11297
11297
|
create: (context) => {
|
|
11298
|
-
const options = resolveSettings$
|
|
11298
|
+
const options = resolveSettings$28(context.settings);
|
|
11299
11299
|
return { JSXOpeningElement(node) {
|
|
11300
11300
|
const elementName = getElementName(node);
|
|
11301
11301
|
if (!elementName) return;
|
|
@@ -11315,287 +11315,6 @@ const jsxNoScriptUrl = defineRule({
|
|
|
11315
11315
|
}
|
|
11316
11316
|
});
|
|
11317
11317
|
//#endregion
|
|
11318
|
-
//#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
|
|
11319
|
-
const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
|
|
11320
|
-
const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
|
|
11321
|
-
const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
|
|
11322
|
-
const resolveSettings$28 = (settings) => {
|
|
11323
|
-
const reactDoctor = settings?.["react-doctor"];
|
|
11324
|
-
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
|
|
11325
|
-
const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
|
|
11326
|
-
const linkComponents = /* @__PURE__ */ new Map();
|
|
11327
|
-
for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
|
|
11328
|
-
else if (typeof entry === "object" && entry !== null) {
|
|
11329
|
-
const linkAttribute = entry.linkAttribute ?? "href";
|
|
11330
|
-
linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
|
|
11331
|
-
}
|
|
11332
|
-
const formComponents = /* @__PURE__ */ new Map();
|
|
11333
|
-
for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
|
|
11334
|
-
else if (typeof entry === "object" && entry !== null) {
|
|
11335
|
-
const formAttribute = entry.formAttribute ?? "action";
|
|
11336
|
-
formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
|
|
11337
|
-
}
|
|
11338
|
-
return {
|
|
11339
|
-
enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
|
|
11340
|
-
warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
|
|
11341
|
-
allowReferrer: ruleSettings.allowReferrer ?? false,
|
|
11342
|
-
links: ruleSettings.links ?? true,
|
|
11343
|
-
forms: ruleSettings.forms ?? false,
|
|
11344
|
-
linkComponents,
|
|
11345
|
-
formComponents
|
|
11346
|
-
};
|
|
11347
|
-
};
|
|
11348
|
-
const isExternalLink = (href) => href.includes("//");
|
|
11349
|
-
const matchHrefExpression = (expression, state) => {
|
|
11350
|
-
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
|
|
11351
|
-
if (isExternalLink(expression.value)) state.isExternal = true;
|
|
11352
|
-
return;
|
|
11353
|
-
}
|
|
11354
|
-
if (isNodeOfType(expression, "Identifier")) {
|
|
11355
|
-
state.isDynamic = true;
|
|
11356
|
-
return;
|
|
11357
|
-
}
|
|
11358
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
11359
|
-
matchHrefExpression(expression.consequent, state);
|
|
11360
|
-
matchHrefExpression(expression.alternate, state);
|
|
11361
|
-
}
|
|
11362
|
-
};
|
|
11363
|
-
const checkHref = (attributeValue, enforceDynamicLinks) => {
|
|
11364
|
-
const state = {
|
|
11365
|
-
isExternal: false,
|
|
11366
|
-
isDynamic: false
|
|
11367
|
-
};
|
|
11368
|
-
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
|
|
11369
|
-
else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
|
|
11370
|
-
if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
|
|
11371
|
-
return !(state.isExternal || state.isDynamic);
|
|
11372
|
-
};
|
|
11373
|
-
const checkRelValue = (text, allowReferrer) => {
|
|
11374
|
-
const tokens = text.split(/\s+/);
|
|
11375
|
-
if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
|
|
11376
|
-
return tokens.some((token) => token.toLowerCase() === "noreferrer");
|
|
11377
|
-
};
|
|
11378
|
-
const matchRelExpression = (expression, allowReferrer) => {
|
|
11379
|
-
const empty = {
|
|
11380
|
-
combined: false,
|
|
11381
|
-
testName: "",
|
|
11382
|
-
consequent: false,
|
|
11383
|
-
alternate: false
|
|
11384
|
-
};
|
|
11385
|
-
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
11386
|
-
combined: checkRelValue(expression.value, allowReferrer),
|
|
11387
|
-
testName: "",
|
|
11388
|
-
consequent: false,
|
|
11389
|
-
alternate: false
|
|
11390
|
-
};
|
|
11391
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
11392
|
-
const consequent = matchRelExpression(expression.consequent, allowReferrer);
|
|
11393
|
-
const alternate = matchRelExpression(expression.alternate, allowReferrer);
|
|
11394
|
-
const test = expression.test;
|
|
11395
|
-
if (isNodeOfType(test, "Identifier")) return {
|
|
11396
|
-
combined: consequent.combined && alternate.combined,
|
|
11397
|
-
testName: test.name,
|
|
11398
|
-
consequent: consequent.combined,
|
|
11399
|
-
alternate: alternate.combined
|
|
11400
|
-
};
|
|
11401
|
-
return {
|
|
11402
|
-
combined: consequent.combined && alternate.combined,
|
|
11403
|
-
testName: "",
|
|
11404
|
-
consequent: consequent.combined,
|
|
11405
|
-
alternate: alternate.combined
|
|
11406
|
-
};
|
|
11407
|
-
}
|
|
11408
|
-
return empty;
|
|
11409
|
-
};
|
|
11410
|
-
const checkRel = (attributeValue, allowReferrer) => {
|
|
11411
|
-
const empty = {
|
|
11412
|
-
combined: false,
|
|
11413
|
-
testName: "",
|
|
11414
|
-
consequent: false,
|
|
11415
|
-
alternate: false
|
|
11416
|
-
};
|
|
11417
|
-
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
11418
|
-
combined: checkRelValue(attributeValue.value, allowReferrer),
|
|
11419
|
-
testName: "",
|
|
11420
|
-
consequent: false,
|
|
11421
|
-
alternate: false
|
|
11422
|
-
};
|
|
11423
|
-
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
11424
|
-
const expression = attributeValue.expression;
|
|
11425
|
-
if (expression.type === "JSXEmptyExpression") return empty;
|
|
11426
|
-
return matchRelExpression(expression, allowReferrer);
|
|
11427
|
-
}
|
|
11428
|
-
return empty;
|
|
11429
|
-
};
|
|
11430
|
-
const matchTargetExpression = (expression) => {
|
|
11431
|
-
const empty = {
|
|
11432
|
-
combined: false,
|
|
11433
|
-
testName: "",
|
|
11434
|
-
consequent: false,
|
|
11435
|
-
alternate: false
|
|
11436
|
-
};
|
|
11437
|
-
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
|
|
11438
|
-
combined: expression.value.toLowerCase() === "_blank",
|
|
11439
|
-
testName: "",
|
|
11440
|
-
consequent: false,
|
|
11441
|
-
alternate: false
|
|
11442
|
-
};
|
|
11443
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
11444
|
-
const consequent = matchTargetExpression(expression.consequent);
|
|
11445
|
-
const alternate = matchTargetExpression(expression.alternate);
|
|
11446
|
-
const test = expression.test;
|
|
11447
|
-
const combined = consequent.combined || alternate.combined;
|
|
11448
|
-
if (isNodeOfType(test, "Identifier")) return {
|
|
11449
|
-
combined,
|
|
11450
|
-
testName: test.name,
|
|
11451
|
-
consequent: consequent.combined,
|
|
11452
|
-
alternate: alternate.combined
|
|
11453
|
-
};
|
|
11454
|
-
return {
|
|
11455
|
-
combined,
|
|
11456
|
-
testName: "",
|
|
11457
|
-
consequent: consequent.combined,
|
|
11458
|
-
alternate: alternate.combined
|
|
11459
|
-
};
|
|
11460
|
-
}
|
|
11461
|
-
return empty;
|
|
11462
|
-
};
|
|
11463
|
-
const checkTarget = (attributeValue) => {
|
|
11464
|
-
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
|
|
11465
|
-
combined: attributeValue.value.toLowerCase() === "_blank",
|
|
11466
|
-
testName: "",
|
|
11467
|
-
consequent: false,
|
|
11468
|
-
alternate: false
|
|
11469
|
-
};
|
|
11470
|
-
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
11471
|
-
const expression = attributeValue.expression;
|
|
11472
|
-
if (expression.type === "JSXEmptyExpression") return {
|
|
11473
|
-
combined: false,
|
|
11474
|
-
testName: "",
|
|
11475
|
-
consequent: false,
|
|
11476
|
-
alternate: false
|
|
11477
|
-
};
|
|
11478
|
-
return matchTargetExpression(expression);
|
|
11479
|
-
}
|
|
11480
|
-
return {
|
|
11481
|
-
combined: false,
|
|
11482
|
-
testName: "",
|
|
11483
|
-
consequent: false,
|
|
11484
|
-
alternate: false
|
|
11485
|
-
};
|
|
11486
|
-
};
|
|
11487
|
-
const getOpeningElementName = (node) => {
|
|
11488
|
-
const name = node.name;
|
|
11489
|
-
if (isNodeOfType(name, "JSXIdentifier")) return name.name;
|
|
11490
|
-
return null;
|
|
11491
|
-
};
|
|
11492
|
-
const jsxNoTargetBlank = defineRule({
|
|
11493
|
-
id: "jsx-no-target-blank",
|
|
11494
|
-
title: "Unsafe target=_blank link",
|
|
11495
|
-
severity: "warn",
|
|
11496
|
-
recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
|
|
11497
|
-
category: "Security",
|
|
11498
|
-
create: (context) => {
|
|
11499
|
-
const settings = resolveSettings$28(context.settings);
|
|
11500
|
-
const isLink = (tagName) => {
|
|
11501
|
-
if (!settings.links) return false;
|
|
11502
|
-
if (tagName === "a") return true;
|
|
11503
|
-
return settings.linkComponents.has(tagName);
|
|
11504
|
-
};
|
|
11505
|
-
const isForm = (tagName) => {
|
|
11506
|
-
if (!settings.forms) return false;
|
|
11507
|
-
if (tagName === "form") return true;
|
|
11508
|
-
return settings.formComponents.has(tagName);
|
|
11509
|
-
};
|
|
11510
|
-
return { JSXOpeningElement(node) {
|
|
11511
|
-
const tagName = getOpeningElementName(node);
|
|
11512
|
-
if (!tagName) return;
|
|
11513
|
-
if (!isLink(tagName) && !isForm(tagName)) return;
|
|
11514
|
-
const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
|
|
11515
|
-
const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
|
|
11516
|
-
let targetTuple = {
|
|
11517
|
-
combined: false,
|
|
11518
|
-
testName: "",
|
|
11519
|
-
consequent: false,
|
|
11520
|
-
alternate: false
|
|
11521
|
-
};
|
|
11522
|
-
let relTuple = {
|
|
11523
|
-
combined: false,
|
|
11524
|
-
testName: "",
|
|
11525
|
-
consequent: false,
|
|
11526
|
-
alternate: false
|
|
11527
|
-
};
|
|
11528
|
-
let isHrefValid = true;
|
|
11529
|
-
let hasHrefValue = false;
|
|
11530
|
-
let warnSpread = false;
|
|
11531
|
-
let targetReportNode = node.name;
|
|
11532
|
-
let spreadReportNode = null;
|
|
11533
|
-
for (const attribute of node.attributes) {
|
|
11534
|
-
if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
|
|
11535
|
-
if (settings.warnOnSpreadAttributes) {
|
|
11536
|
-
warnSpread = true;
|
|
11537
|
-
spreadReportNode = attribute;
|
|
11538
|
-
targetTuple = {
|
|
11539
|
-
combined: false,
|
|
11540
|
-
testName: "",
|
|
11541
|
-
consequent: false,
|
|
11542
|
-
alternate: false
|
|
11543
|
-
};
|
|
11544
|
-
relTuple = {
|
|
11545
|
-
combined: false,
|
|
11546
|
-
testName: "",
|
|
11547
|
-
consequent: false,
|
|
11548
|
-
alternate: false
|
|
11549
|
-
};
|
|
11550
|
-
isHrefValid = false;
|
|
11551
|
-
hasHrefValue = true;
|
|
11552
|
-
}
|
|
11553
|
-
continue;
|
|
11554
|
-
}
|
|
11555
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
11556
|
-
const attributeName = attribute.name;
|
|
11557
|
-
if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
|
|
11558
|
-
const propName = attributeName.name;
|
|
11559
|
-
const value = attribute.value;
|
|
11560
|
-
if (propName === "target") {
|
|
11561
|
-
if (value) {
|
|
11562
|
-
targetTuple = checkTarget(value);
|
|
11563
|
-
targetReportNode = value;
|
|
11564
|
-
}
|
|
11565
|
-
} else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
|
|
11566
|
-
if (value) {
|
|
11567
|
-
hasHrefValue = true;
|
|
11568
|
-
isHrefValid = checkHref(value, settings.enforceDynamicLinks);
|
|
11569
|
-
}
|
|
11570
|
-
} else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
|
|
11571
|
-
}
|
|
11572
|
-
if (warnSpread) {
|
|
11573
|
-
if (hasHrefValue && isHrefValid || relTuple.combined) return;
|
|
11574
|
-
context.report({
|
|
11575
|
-
node: spreadReportNode ?? node,
|
|
11576
|
-
message: SPREAD_MESSAGE
|
|
11577
|
-
});
|
|
11578
|
-
return;
|
|
11579
|
-
}
|
|
11580
|
-
if (!isHrefValid) {
|
|
11581
|
-
if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
|
|
11582
|
-
const consequentBad = targetTuple.consequent && !relTuple.consequent;
|
|
11583
|
-
const alternateBad = targetTuple.alternate && !relTuple.alternate;
|
|
11584
|
-
if (consequentBad || alternateBad) context.report({
|
|
11585
|
-
node: targetReportNode,
|
|
11586
|
-
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
11587
|
-
});
|
|
11588
|
-
return;
|
|
11589
|
-
}
|
|
11590
|
-
if (targetTuple.combined && !relTuple.combined) context.report({
|
|
11591
|
-
node: targetReportNode,
|
|
11592
|
-
message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
|
|
11593
|
-
});
|
|
11594
|
-
}
|
|
11595
|
-
} };
|
|
11596
|
-
}
|
|
11597
|
-
});
|
|
11598
|
-
//#endregion
|
|
11599
11318
|
//#region src/plugin/rules/react-builtins/jsx-no-undef.ts
|
|
11600
11319
|
const buildMessage$17 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
|
|
11601
11320
|
const KNOWN_GLOBALS = new Set([
|
|
@@ -35999,17 +35718,6 @@ const reactDoctorRules = [
|
|
|
35999
35718
|
category: "Security"
|
|
36000
35719
|
}
|
|
36001
35720
|
},
|
|
36002
|
-
{
|
|
36003
|
-
key: "react-doctor/jsx-no-target-blank",
|
|
36004
|
-
id: "jsx-no-target-blank",
|
|
36005
|
-
source: "react-doctor",
|
|
36006
|
-
originallyExternal: true,
|
|
36007
|
-
rule: {
|
|
36008
|
-
...jsxNoTargetBlank,
|
|
36009
|
-
framework: "global",
|
|
36010
|
-
category: "Security"
|
|
36011
|
-
}
|
|
36012
|
-
},
|
|
36013
35721
|
{
|
|
36014
35722
|
key: "react-doctor/jsx-no-undef",
|
|
36015
35723
|
id: "jsx-no-undef",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-react-doctor",
|
|
3
|
-
"version": "0.4.2-dev.
|
|
3
|
+
"version": "0.4.2-dev.829655c",
|
|
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",
|