oxlint-plugin-react-doctor 0.5.6-dev.0a7edbd → 0.5.6-dev.451beeb
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 +126 -0
- package/dist/index.js +236 -114
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -3603,6 +3603,27 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
3603
3603
|
readonly recommendation?: string;
|
|
3604
3604
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
3605
3605
|
};
|
|
3606
|
+
}, {
|
|
3607
|
+
readonly key: "react-doctor/no-document-write";
|
|
3608
|
+
readonly id: "no-document-write";
|
|
3609
|
+
readonly source: "react-doctor";
|
|
3610
|
+
readonly originallyExternal: false;
|
|
3611
|
+
readonly rule: {
|
|
3612
|
+
readonly framework: "global";
|
|
3613
|
+
readonly category: "Performance";
|
|
3614
|
+
readonly id: string;
|
|
3615
|
+
readonly title?: string;
|
|
3616
|
+
readonly severity: RuleSeverity;
|
|
3617
|
+
readonly requires?: ReadonlyArray<string>;
|
|
3618
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
3619
|
+
readonly tags?: ReadonlyArray<string>;
|
|
3620
|
+
readonly defaultEnabled?: boolean;
|
|
3621
|
+
readonly lifecycle?: "retired";
|
|
3622
|
+
readonly scan?: FileScan;
|
|
3623
|
+
readonly committedFilesOnly?: boolean;
|
|
3624
|
+
readonly recommendation?: string;
|
|
3625
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
3626
|
+
};
|
|
3606
3627
|
}, {
|
|
3607
3628
|
readonly key: "react-doctor/no-dynamic-import-path";
|
|
3608
3629
|
readonly id: "no-dynamic-import-path";
|
|
@@ -5073,6 +5094,27 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
5073
5094
|
readonly recommendation?: string;
|
|
5074
5095
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5075
5096
|
};
|
|
5097
|
+
}, {
|
|
5098
|
+
readonly key: "react-doctor/no-string-false-on-boolean-attribute";
|
|
5099
|
+
readonly id: "no-string-false-on-boolean-attribute";
|
|
5100
|
+
readonly source: "react-doctor";
|
|
5101
|
+
readonly originallyExternal: false;
|
|
5102
|
+
readonly rule: {
|
|
5103
|
+
readonly framework: "global";
|
|
5104
|
+
readonly category: "Bugs";
|
|
5105
|
+
readonly requires: readonly string[];
|
|
5106
|
+
readonly id: string;
|
|
5107
|
+
readonly title?: string;
|
|
5108
|
+
readonly severity: RuleSeverity;
|
|
5109
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5110
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5111
|
+
readonly defaultEnabled?: boolean;
|
|
5112
|
+
readonly lifecycle?: "retired";
|
|
5113
|
+
readonly scan?: FileScan;
|
|
5114
|
+
readonly committedFilesOnly?: boolean;
|
|
5115
|
+
readonly recommendation?: string;
|
|
5116
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5117
|
+
};
|
|
5076
5118
|
}, {
|
|
5077
5119
|
readonly key: "react-doctor/no-string-refs";
|
|
5078
5120
|
readonly id: "no-string-refs";
|
|
@@ -5094,6 +5136,27 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
5094
5136
|
readonly recommendation?: string;
|
|
5095
5137
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5096
5138
|
};
|
|
5139
|
+
}, {
|
|
5140
|
+
readonly key: "react-doctor/no-sync-xhr";
|
|
5141
|
+
readonly id: "no-sync-xhr";
|
|
5142
|
+
readonly source: "react-doctor";
|
|
5143
|
+
readonly originallyExternal: false;
|
|
5144
|
+
readonly rule: {
|
|
5145
|
+
readonly framework: "global";
|
|
5146
|
+
readonly category: "Performance";
|
|
5147
|
+
readonly id: string;
|
|
5148
|
+
readonly title?: string;
|
|
5149
|
+
readonly severity: RuleSeverity;
|
|
5150
|
+
readonly requires?: ReadonlyArray<string>;
|
|
5151
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5152
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5153
|
+
readonly defaultEnabled?: boolean;
|
|
5154
|
+
readonly lifecycle?: "retired";
|
|
5155
|
+
readonly scan?: FileScan;
|
|
5156
|
+
readonly committedFilesOnly?: boolean;
|
|
5157
|
+
readonly recommendation?: string;
|
|
5158
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5159
|
+
};
|
|
5097
5160
|
}, {
|
|
5098
5161
|
readonly key: "react-doctor/no-this-in-sfc";
|
|
5099
5162
|
readonly id: "no-this-in-sfc";
|
|
@@ -11775,6 +11838,27 @@ declare const RULES: readonly [{
|
|
|
11775
11838
|
readonly recommendation?: string;
|
|
11776
11839
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11777
11840
|
};
|
|
11841
|
+
}, {
|
|
11842
|
+
readonly key: "react-doctor/no-document-write";
|
|
11843
|
+
readonly id: "no-document-write";
|
|
11844
|
+
readonly source: "react-doctor";
|
|
11845
|
+
readonly originallyExternal: false;
|
|
11846
|
+
readonly rule: {
|
|
11847
|
+
readonly framework: "global";
|
|
11848
|
+
readonly category: "Performance";
|
|
11849
|
+
readonly id: string;
|
|
11850
|
+
readonly title?: string;
|
|
11851
|
+
readonly severity: RuleSeverity;
|
|
11852
|
+
readonly requires?: ReadonlyArray<string>;
|
|
11853
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
11854
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11855
|
+
readonly defaultEnabled?: boolean;
|
|
11856
|
+
readonly lifecycle?: "retired";
|
|
11857
|
+
readonly scan?: FileScan;
|
|
11858
|
+
readonly committedFilesOnly?: boolean;
|
|
11859
|
+
readonly recommendation?: string;
|
|
11860
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11861
|
+
};
|
|
11778
11862
|
}, {
|
|
11779
11863
|
readonly key: "react-doctor/no-dynamic-import-path";
|
|
11780
11864
|
readonly id: "no-dynamic-import-path";
|
|
@@ -13245,6 +13329,27 @@ declare const RULES: readonly [{
|
|
|
13245
13329
|
readonly recommendation?: string;
|
|
13246
13330
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
13247
13331
|
};
|
|
13332
|
+
}, {
|
|
13333
|
+
readonly key: "react-doctor/no-string-false-on-boolean-attribute";
|
|
13334
|
+
readonly id: "no-string-false-on-boolean-attribute";
|
|
13335
|
+
readonly source: "react-doctor";
|
|
13336
|
+
readonly originallyExternal: false;
|
|
13337
|
+
readonly rule: {
|
|
13338
|
+
readonly framework: "global";
|
|
13339
|
+
readonly category: "Bugs";
|
|
13340
|
+
readonly requires: readonly string[];
|
|
13341
|
+
readonly id: string;
|
|
13342
|
+
readonly title?: string;
|
|
13343
|
+
readonly severity: RuleSeverity;
|
|
13344
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
13345
|
+
readonly tags?: ReadonlyArray<string>;
|
|
13346
|
+
readonly defaultEnabled?: boolean;
|
|
13347
|
+
readonly lifecycle?: "retired";
|
|
13348
|
+
readonly scan?: FileScan;
|
|
13349
|
+
readonly committedFilesOnly?: boolean;
|
|
13350
|
+
readonly recommendation?: string;
|
|
13351
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
13352
|
+
};
|
|
13248
13353
|
}, {
|
|
13249
13354
|
readonly key: "react-doctor/no-string-refs";
|
|
13250
13355
|
readonly id: "no-string-refs";
|
|
@@ -13266,6 +13371,27 @@ declare const RULES: readonly [{
|
|
|
13266
13371
|
readonly recommendation?: string;
|
|
13267
13372
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
13268
13373
|
};
|
|
13374
|
+
}, {
|
|
13375
|
+
readonly key: "react-doctor/no-sync-xhr";
|
|
13376
|
+
readonly id: "no-sync-xhr";
|
|
13377
|
+
readonly source: "react-doctor";
|
|
13378
|
+
readonly originallyExternal: false;
|
|
13379
|
+
readonly rule: {
|
|
13380
|
+
readonly framework: "global";
|
|
13381
|
+
readonly category: "Performance";
|
|
13382
|
+
readonly id: string;
|
|
13383
|
+
readonly title?: string;
|
|
13384
|
+
readonly severity: RuleSeverity;
|
|
13385
|
+
readonly requires?: ReadonlyArray<string>;
|
|
13386
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
13387
|
+
readonly tags?: ReadonlyArray<string>;
|
|
13388
|
+
readonly defaultEnabled?: boolean;
|
|
13389
|
+
readonly lifecycle?: "retired";
|
|
13390
|
+
readonly scan?: FileScan;
|
|
13391
|
+
readonly committedFilesOnly?: boolean;
|
|
13392
|
+
readonly recommendation?: string;
|
|
13393
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
13394
|
+
};
|
|
13269
13395
|
}, {
|
|
13270
13396
|
readonly key: "react-doctor/no-this-in-sfc";
|
|
13271
13397
|
readonly id: "no-this-in-sfc";
|
package/dist/index.js
CHANGED
|
@@ -1861,7 +1861,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
1861
1861
|
});
|
|
1862
1862
|
//#endregion
|
|
1863
1863
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
1864
|
-
const MESSAGE$
|
|
1864
|
+
const MESSAGE$59 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
1865
1865
|
const anchorHasContent = defineRule({
|
|
1866
1866
|
id: "anchor-has-content",
|
|
1867
1867
|
title: "Anchor has no content",
|
|
@@ -1877,7 +1877,7 @@ const anchorHasContent = defineRule({
|
|
|
1877
1877
|
for (const attribute of ["title", "aria-label"]) if (hasJsxPropIgnoreCase(opening.attributes, attribute)) return;
|
|
1878
1878
|
context.report({
|
|
1879
1879
|
node: opening.name,
|
|
1880
|
-
message: MESSAGE$
|
|
1880
|
+
message: MESSAGE$59
|
|
1881
1881
|
});
|
|
1882
1882
|
} })
|
|
1883
1883
|
});
|
|
@@ -2271,7 +2271,7 @@ const parseJsxValue = (value) => {
|
|
|
2271
2271
|
};
|
|
2272
2272
|
//#endregion
|
|
2273
2273
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2274
|
-
const MESSAGE$
|
|
2274
|
+
const MESSAGE$58 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2275
2275
|
const ariaActivedescendantHasTabindex = defineRule({
|
|
2276
2276
|
id: "aria-activedescendant-has-tabindex",
|
|
2277
2277
|
title: "aria-activedescendant missing tabindex",
|
|
@@ -2289,14 +2289,14 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2289
2289
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2290
2290
|
context.report({
|
|
2291
2291
|
node: node.name,
|
|
2292
|
-
message: MESSAGE$
|
|
2292
|
+
message: MESSAGE$58
|
|
2293
2293
|
});
|
|
2294
2294
|
return;
|
|
2295
2295
|
}
|
|
2296
2296
|
if (isInteractiveElement(tag, node)) return;
|
|
2297
2297
|
context.report({
|
|
2298
2298
|
node: node.name,
|
|
2299
|
-
message: MESSAGE$
|
|
2299
|
+
message: MESSAGE$58
|
|
2300
2300
|
});
|
|
2301
2301
|
} })
|
|
2302
2302
|
});
|
|
@@ -4272,7 +4272,7 @@ const asyncParallel = defineRule({
|
|
|
4272
4272
|
});
|
|
4273
4273
|
//#endregion
|
|
4274
4274
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
4275
|
-
const MESSAGE$
|
|
4275
|
+
const MESSAGE$57 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
|
|
4276
4276
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
4277
4277
|
const STORAGE_GLOBALS = new Set([
|
|
4278
4278
|
"window",
|
|
@@ -4306,7 +4306,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
4306
4306
|
if (!SENSITIVE_KEY_PATTERN.test(keyArgument.value)) return;
|
|
4307
4307
|
context.report({
|
|
4308
4308
|
node,
|
|
4309
|
-
message: MESSAGE$
|
|
4309
|
+
message: MESSAGE$57
|
|
4310
4310
|
});
|
|
4311
4311
|
},
|
|
4312
4312
|
AssignmentExpression(node) {
|
|
@@ -4317,7 +4317,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
4317
4317
|
if (!propertyName || !SENSITIVE_KEY_PATTERN.test(propertyName)) return;
|
|
4318
4318
|
context.report({
|
|
4319
4319
|
node: target,
|
|
4320
|
-
message: MESSAGE$
|
|
4320
|
+
message: MESSAGE$57
|
|
4321
4321
|
});
|
|
4322
4322
|
}
|
|
4323
4323
|
})
|
|
@@ -4694,7 +4694,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
4694
4694
|
//#endregion
|
|
4695
4695
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
4696
4696
|
const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
|
|
4697
|
-
const MESSAGE$
|
|
4697
|
+
const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
4698
4698
|
const KEY_HANDLERS = [
|
|
4699
4699
|
"onKeyUp",
|
|
4700
4700
|
"onKeyDown",
|
|
@@ -4726,7 +4726,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
4726
4726
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
4727
4727
|
context.report({
|
|
4728
4728
|
node: node.name,
|
|
4729
|
-
message: MESSAGE$
|
|
4729
|
+
message: MESSAGE$56
|
|
4730
4730
|
});
|
|
4731
4731
|
} };
|
|
4732
4732
|
}
|
|
@@ -4841,7 +4841,7 @@ const isReactComponentName = (name) => {
|
|
|
4841
4841
|
};
|
|
4842
4842
|
//#endregion
|
|
4843
4843
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
4844
|
-
const MESSAGE$
|
|
4844
|
+
const MESSAGE$55 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
4845
4845
|
const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
|
|
4846
4846
|
const DEFAULT_LABELLING_PROPS = [
|
|
4847
4847
|
"alt",
|
|
@@ -5002,7 +5002,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
5002
5002
|
for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
|
|
5003
5003
|
context.report({
|
|
5004
5004
|
node: opening,
|
|
5005
|
-
message: MESSAGE$
|
|
5005
|
+
message: MESSAGE$55
|
|
5006
5006
|
});
|
|
5007
5007
|
} };
|
|
5008
5008
|
}
|
|
@@ -5432,7 +5432,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
5432
5432
|
const hasJsxSpreadAttribute$1 = (attributes) => attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
|
|
5433
5433
|
//#endregion
|
|
5434
5434
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
5435
|
-
const MESSAGE$
|
|
5435
|
+
const MESSAGE$54 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
5436
5436
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
5437
5437
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
5438
5438
|
"aria-label",
|
|
@@ -5455,7 +5455,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
5455
5455
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
5456
5456
|
context.report({
|
|
5457
5457
|
node: node.name,
|
|
5458
|
-
message: MESSAGE$
|
|
5458
|
+
message: MESSAGE$54
|
|
5459
5459
|
});
|
|
5460
5460
|
} })
|
|
5461
5461
|
});
|
|
@@ -5494,7 +5494,7 @@ const isEs6Component = (node) => {
|
|
|
5494
5494
|
};
|
|
5495
5495
|
//#endregion
|
|
5496
5496
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
5497
|
-
const MESSAGE$
|
|
5497
|
+
const MESSAGE$53 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
5498
5498
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
5499
5499
|
"observer",
|
|
5500
5500
|
"lazy",
|
|
@@ -5697,7 +5697,7 @@ const displayName = defineRule({
|
|
|
5697
5697
|
const reportAt = (node) => {
|
|
5698
5698
|
context.report({
|
|
5699
5699
|
node,
|
|
5700
|
-
message: MESSAGE$
|
|
5700
|
+
message: MESSAGE$53
|
|
5701
5701
|
});
|
|
5702
5702
|
};
|
|
5703
5703
|
return {
|
|
@@ -7845,7 +7845,7 @@ const forbidElements = defineRule({
|
|
|
7845
7845
|
});
|
|
7846
7846
|
//#endregion
|
|
7847
7847
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
7848
|
-
const MESSAGE$
|
|
7848
|
+
const MESSAGE$52 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
7849
7849
|
const forwardRefUsesRef = defineRule({
|
|
7850
7850
|
id: "forward-ref-uses-ref",
|
|
7851
7851
|
title: "forwardRef without ref parameter",
|
|
@@ -7865,7 +7865,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
7865
7865
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
7866
7866
|
context.report({
|
|
7867
7867
|
node: inner,
|
|
7868
|
-
message: MESSAGE$
|
|
7868
|
+
message: MESSAGE$52
|
|
7869
7869
|
});
|
|
7870
7870
|
} })
|
|
7871
7871
|
});
|
|
@@ -7902,7 +7902,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
7902
7902
|
});
|
|
7903
7903
|
//#endregion
|
|
7904
7904
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
7905
|
-
const MESSAGE$
|
|
7905
|
+
const MESSAGE$51 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
7906
7906
|
const DEFAULT_HEADING_TAGS = [
|
|
7907
7907
|
"h1",
|
|
7908
7908
|
"h2",
|
|
@@ -7935,7 +7935,7 @@ const headingHasContent = defineRule({
|
|
|
7935
7935
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
7936
7936
|
context.report({
|
|
7937
7937
|
node,
|
|
7938
|
-
message: MESSAGE$
|
|
7938
|
+
message: MESSAGE$51
|
|
7939
7939
|
});
|
|
7940
7940
|
} };
|
|
7941
7941
|
}
|
|
@@ -8073,7 +8073,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
8073
8073
|
});
|
|
8074
8074
|
//#endregion
|
|
8075
8075
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
8076
|
-
const MESSAGE$
|
|
8076
|
+
const MESSAGE$50 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
8077
8077
|
const resolveSettings$38 = (settings) => {
|
|
8078
8078
|
const reactDoctor = settings?.["react-doctor"];
|
|
8079
8079
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -8121,7 +8121,7 @@ const htmlHasLang = defineRule({
|
|
|
8121
8121
|
if (!lang) {
|
|
8122
8122
|
context.report({
|
|
8123
8123
|
node: node.name,
|
|
8124
|
-
message: MESSAGE$
|
|
8124
|
+
message: MESSAGE$50
|
|
8125
8125
|
});
|
|
8126
8126
|
return;
|
|
8127
8127
|
}
|
|
@@ -8129,13 +8129,13 @@ const htmlHasLang = defineRule({
|
|
|
8129
8129
|
if (verdict === "missing" || verdict === "empty") {
|
|
8130
8130
|
context.report({
|
|
8131
8131
|
node: lang,
|
|
8132
|
-
message: MESSAGE$
|
|
8132
|
+
message: MESSAGE$50
|
|
8133
8133
|
});
|
|
8134
8134
|
return;
|
|
8135
8135
|
}
|
|
8136
8136
|
if (hasSpread && !lang) context.report({
|
|
8137
8137
|
node: node.name,
|
|
8138
|
-
message: MESSAGE$
|
|
8138
|
+
message: MESSAGE$50
|
|
8139
8139
|
});
|
|
8140
8140
|
} };
|
|
8141
8141
|
}
|
|
@@ -8349,7 +8349,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
8349
8349
|
});
|
|
8350
8350
|
//#endregion
|
|
8351
8351
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
8352
|
-
const MESSAGE$
|
|
8352
|
+
const MESSAGE$49 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
8353
8353
|
const evaluateTitleValue = (value) => {
|
|
8354
8354
|
if (!value) return "missing";
|
|
8355
8355
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -8389,14 +8389,14 @@ const iframeHasTitle = defineRule({
|
|
|
8389
8389
|
if (!titleAttr) {
|
|
8390
8390
|
if (hasSpread || tag === "iframe") context.report({
|
|
8391
8391
|
node: node.name,
|
|
8392
|
-
message: MESSAGE$
|
|
8392
|
+
message: MESSAGE$49
|
|
8393
8393
|
});
|
|
8394
8394
|
return;
|
|
8395
8395
|
}
|
|
8396
8396
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
8397
8397
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
8398
8398
|
node: titleAttr,
|
|
8399
|
-
message: MESSAGE$
|
|
8399
|
+
message: MESSAGE$49
|
|
8400
8400
|
});
|
|
8401
8401
|
} })
|
|
8402
8402
|
});
|
|
@@ -8500,7 +8500,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
8500
8500
|
});
|
|
8501
8501
|
//#endregion
|
|
8502
8502
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
8503
|
-
const MESSAGE$
|
|
8503
|
+
const MESSAGE$48 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
8504
8504
|
const DEFAULT_COMPONENTS = ["img"];
|
|
8505
8505
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
8506
8506
|
"image",
|
|
@@ -8565,7 +8565,7 @@ const imgRedundantAlt = defineRule({
|
|
|
8565
8565
|
if (!altAttribute) return;
|
|
8566
8566
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
8567
8567
|
node: altAttribute,
|
|
8568
|
-
message: MESSAGE$
|
|
8568
|
+
message: MESSAGE$48
|
|
8569
8569
|
});
|
|
8570
8570
|
} };
|
|
8571
8571
|
}
|
|
@@ -10922,7 +10922,7 @@ const jsxMaxDepth = defineRule({
|
|
|
10922
10922
|
});
|
|
10923
10923
|
//#endregion
|
|
10924
10924
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
10925
|
-
const MESSAGE$
|
|
10925
|
+
const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
10926
10926
|
const LITERAL_TEXT_TAGS = new Set([
|
|
10927
10927
|
"code",
|
|
10928
10928
|
"pre",
|
|
@@ -10958,7 +10958,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
10958
10958
|
if (isInsideLiteralTextTag(node)) return;
|
|
10959
10959
|
context.report({
|
|
10960
10960
|
node,
|
|
10961
|
-
message: MESSAGE$
|
|
10961
|
+
message: MESSAGE$47
|
|
10962
10962
|
});
|
|
10963
10963
|
} })
|
|
10964
10964
|
});
|
|
@@ -10989,7 +10989,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
10989
10989
|
};
|
|
10990
10990
|
//#endregion
|
|
10991
10991
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
10992
|
-
const MESSAGE$
|
|
10992
|
+
const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
10993
10993
|
const CONTEXT_MODULES$1 = [
|
|
10994
10994
|
"react",
|
|
10995
10995
|
"use-context-selector",
|
|
@@ -11087,7 +11087,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
11087
11087
|
if (!isConstructedValue(innerExpression)) continue;
|
|
11088
11088
|
context.report({
|
|
11089
11089
|
node: attribute,
|
|
11090
|
-
message: MESSAGE$
|
|
11090
|
+
message: MESSAGE$46
|
|
11091
11091
|
});
|
|
11092
11092
|
}
|
|
11093
11093
|
}
|
|
@@ -11173,7 +11173,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
|
|
|
11173
11173
|
};
|
|
11174
11174
|
//#endregion
|
|
11175
11175
|
//#region src/plugin/rules/react-builtins/jsx-no-jsx-as-prop.ts
|
|
11176
|
-
const MESSAGE$
|
|
11176
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets brand new JSX each time.";
|
|
11177
11177
|
const KNOWN_SLOT_PROP_NAMES = new Set([
|
|
11178
11178
|
"icon",
|
|
11179
11179
|
"Icon",
|
|
@@ -11442,7 +11442,7 @@ const jsxNoJsxAsProp = defineRule({
|
|
|
11442
11442
|
if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
|
|
11443
11443
|
context.report({
|
|
11444
11444
|
node,
|
|
11445
|
-
message: MESSAGE$
|
|
11445
|
+
message: MESSAGE$45
|
|
11446
11446
|
});
|
|
11447
11447
|
}
|
|
11448
11448
|
};
|
|
@@ -11730,7 +11730,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
11730
11730
|
];
|
|
11731
11731
|
//#endregion
|
|
11732
11732
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
11733
|
-
const MESSAGE$
|
|
11733
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
11734
11734
|
const isDataArrayPropName = (propName) => {
|
|
11735
11735
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
11736
11736
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -11814,7 +11814,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
11814
11814
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
11815
11815
|
context.report({
|
|
11816
11816
|
node,
|
|
11817
|
-
message: MESSAGE$
|
|
11817
|
+
message: MESSAGE$44
|
|
11818
11818
|
});
|
|
11819
11819
|
}
|
|
11820
11820
|
};
|
|
@@ -12072,7 +12072,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
12072
12072
|
]);
|
|
12073
12073
|
//#endregion
|
|
12074
12074
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
12075
|
-
const MESSAGE$
|
|
12075
|
+
const MESSAGE$43 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
12076
12076
|
const isAccessorPredicateName = (propName) => {
|
|
12077
12077
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
12078
12078
|
if (propName.length <= prefix.length) continue;
|
|
@@ -12278,7 +12278,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
12278
12278
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
12279
12279
|
context.report({
|
|
12280
12280
|
node,
|
|
12281
|
-
message: MESSAGE$
|
|
12281
|
+
message: MESSAGE$43
|
|
12282
12282
|
});
|
|
12283
12283
|
}
|
|
12284
12284
|
};
|
|
@@ -12498,7 +12498,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
12498
12498
|
];
|
|
12499
12499
|
//#endregion
|
|
12500
12500
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
12501
|
-
const MESSAGE$
|
|
12501
|
+
const MESSAGE$42 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
12502
12502
|
const isConfigObjectPropName = (propName) => {
|
|
12503
12503
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
12504
12504
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -12586,7 +12586,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
12586
12586
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
12587
12587
|
context.report({
|
|
12588
12588
|
node,
|
|
12589
|
-
message: MESSAGE$
|
|
12589
|
+
message: MESSAGE$42
|
|
12590
12590
|
});
|
|
12591
12591
|
}
|
|
12592
12592
|
};
|
|
@@ -12594,7 +12594,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
12594
12594
|
});
|
|
12595
12595
|
//#endregion
|
|
12596
12596
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
12597
|
-
const MESSAGE$
|
|
12597
|
+
const MESSAGE$41 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
12598
12598
|
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;
|
|
12599
12599
|
const resolveSettings$28 = (settings) => {
|
|
12600
12600
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -12635,7 +12635,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
12635
12635
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
12636
12636
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
12637
12637
|
node: attribute,
|
|
12638
|
-
message: MESSAGE$
|
|
12638
|
+
message: MESSAGE$41
|
|
12639
12639
|
});
|
|
12640
12640
|
}
|
|
12641
12641
|
} };
|
|
@@ -12950,7 +12950,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
12950
12950
|
});
|
|
12951
12951
|
//#endregion
|
|
12952
12952
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
12953
|
-
const MESSAGE$
|
|
12953
|
+
const MESSAGE$40 = "You can't tell what props reach this element when you spread them.";
|
|
12954
12954
|
const resolveSettings$25 = (settings) => {
|
|
12955
12955
|
const reactDoctor = settings?.["react-doctor"];
|
|
12956
12956
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -12991,7 +12991,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
12991
12991
|
}
|
|
12992
12992
|
context.report({
|
|
12993
12993
|
node: attribute,
|
|
12994
|
-
message: MESSAGE$
|
|
12994
|
+
message: MESSAGE$40
|
|
12995
12995
|
});
|
|
12996
12996
|
}
|
|
12997
12997
|
} };
|
|
@@ -13219,7 +13219,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
13219
13219
|
});
|
|
13220
13220
|
//#endregion
|
|
13221
13221
|
//#region src/plugin/rules/a11y/lang.ts
|
|
13222
|
-
const MESSAGE$
|
|
13222
|
+
const MESSAGE$39 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
|
|
13223
13223
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
13224
13224
|
"aa",
|
|
13225
13225
|
"ab",
|
|
@@ -13431,7 +13431,7 @@ const lang = defineRule({
|
|
|
13431
13431
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
13432
13432
|
context.report({
|
|
13433
13433
|
node: langAttr,
|
|
13434
|
-
message: MESSAGE$
|
|
13434
|
+
message: MESSAGE$39
|
|
13435
13435
|
});
|
|
13436
13436
|
return;
|
|
13437
13437
|
}
|
|
@@ -13440,7 +13440,7 @@ const lang = defineRule({
|
|
|
13440
13440
|
if (value === null) return;
|
|
13441
13441
|
if (!isValidLangTag(value)) context.report({
|
|
13442
13442
|
node: langAttr,
|
|
13443
|
-
message: MESSAGE$
|
|
13443
|
+
message: MESSAGE$39
|
|
13444
13444
|
});
|
|
13445
13445
|
} })
|
|
13446
13446
|
});
|
|
@@ -13484,7 +13484,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
13484
13484
|
});
|
|
13485
13485
|
//#endregion
|
|
13486
13486
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
13487
|
-
const MESSAGE$
|
|
13487
|
+
const MESSAGE$38 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
13488
13488
|
const DEFAULT_AUDIO = ["audio"];
|
|
13489
13489
|
const DEFAULT_VIDEO = ["video"];
|
|
13490
13490
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -13525,7 +13525,7 @@ const mediaHasCaption = defineRule({
|
|
|
13525
13525
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
13526
13526
|
context.report({
|
|
13527
13527
|
node: node.name,
|
|
13528
|
-
message: MESSAGE$
|
|
13528
|
+
message: MESSAGE$38
|
|
13529
13529
|
});
|
|
13530
13530
|
return;
|
|
13531
13531
|
}
|
|
@@ -13542,7 +13542,7 @@ const mediaHasCaption = defineRule({
|
|
|
13542
13542
|
return kindValue.value.toLowerCase() === "captions";
|
|
13543
13543
|
})) context.report({
|
|
13544
13544
|
node: node.name,
|
|
13545
|
-
message: MESSAGE$
|
|
13545
|
+
message: MESSAGE$38
|
|
13546
13546
|
});
|
|
13547
13547
|
} };
|
|
13548
13548
|
}
|
|
@@ -15343,7 +15343,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
15343
15343
|
});
|
|
15344
15344
|
//#endregion
|
|
15345
15345
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
15346
|
-
const MESSAGE$
|
|
15346
|
+
const MESSAGE$37 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
15347
15347
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
15348
15348
|
const noAccessKey = defineRule({
|
|
15349
15349
|
id: "no-access-key",
|
|
@@ -15360,7 +15360,7 @@ const noAccessKey = defineRule({
|
|
|
15360
15360
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
15361
15361
|
context.report({
|
|
15362
15362
|
node: accessKey,
|
|
15363
|
-
message: MESSAGE$
|
|
15363
|
+
message: MESSAGE$37
|
|
15364
15364
|
});
|
|
15365
15365
|
return;
|
|
15366
15366
|
}
|
|
@@ -15370,7 +15370,7 @@ const noAccessKey = defineRule({
|
|
|
15370
15370
|
if (isUndefinedIdentifier(expression)) return;
|
|
15371
15371
|
context.report({
|
|
15372
15372
|
node: accessKey,
|
|
15373
|
-
message: MESSAGE$
|
|
15373
|
+
message: MESSAGE$37
|
|
15374
15374
|
});
|
|
15375
15375
|
}
|
|
15376
15376
|
} })
|
|
@@ -15853,7 +15853,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
15853
15853
|
});
|
|
15854
15854
|
//#endregion
|
|
15855
15855
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
15856
|
-
const MESSAGE$
|
|
15856
|
+
const MESSAGE$36 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
|
|
15857
15857
|
const noAriaHiddenOnFocusable = defineRule({
|
|
15858
15858
|
id: "no-aria-hidden-on-focusable",
|
|
15859
15859
|
title: "aria-hidden on focusable element",
|
|
@@ -15880,7 +15880,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
15880
15880
|
const isImplicitlyFocusable = isInteractiveElement(tag, node);
|
|
15881
15881
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
15882
15882
|
node: ariaHidden,
|
|
15883
|
-
message: MESSAGE$
|
|
15883
|
+
message: MESSAGE$36
|
|
15884
15884
|
});
|
|
15885
15885
|
} })
|
|
15886
15886
|
});
|
|
@@ -16248,7 +16248,7 @@ const noArrayIndexAsKey = defineRule({
|
|
|
16248
16248
|
});
|
|
16249
16249
|
//#endregion
|
|
16250
16250
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
16251
|
-
const MESSAGE$
|
|
16251
|
+
const MESSAGE$35 = "Your users can see & submit the wrong data when this list reorders.";
|
|
16252
16252
|
const SECOND_INDEX_METHODS = new Set([
|
|
16253
16253
|
"every",
|
|
16254
16254
|
"filter",
|
|
@@ -16452,7 +16452,7 @@ const noArrayIndexKey = defineRule({
|
|
|
16452
16452
|
}
|
|
16453
16453
|
context.report({
|
|
16454
16454
|
node: keyAttribute,
|
|
16455
|
-
message: MESSAGE$
|
|
16455
|
+
message: MESSAGE$35
|
|
16456
16456
|
});
|
|
16457
16457
|
},
|
|
16458
16458
|
CallExpression(node) {
|
|
@@ -16472,7 +16472,7 @@ const noArrayIndexKey = defineRule({
|
|
|
16472
16472
|
if (propName !== "key") continue;
|
|
16473
16473
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
16474
16474
|
node: property,
|
|
16475
|
-
message: MESSAGE$
|
|
16475
|
+
message: MESSAGE$35
|
|
16476
16476
|
});
|
|
16477
16477
|
}
|
|
16478
16478
|
}
|
|
@@ -16480,7 +16480,7 @@ const noArrayIndexKey = defineRule({
|
|
|
16480
16480
|
});
|
|
16481
16481
|
//#endregion
|
|
16482
16482
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
16483
|
-
const MESSAGE$
|
|
16483
|
+
const MESSAGE$34 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
|
|
16484
16484
|
const noAsyncEffectCallback = defineRule({
|
|
16485
16485
|
id: "no-async-effect-callback",
|
|
16486
16486
|
title: "Async effect callback",
|
|
@@ -16494,13 +16494,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
16494
16494
|
if (!callback.async) return;
|
|
16495
16495
|
context.report({
|
|
16496
16496
|
node: callback,
|
|
16497
|
-
message: MESSAGE$
|
|
16497
|
+
message: MESSAGE$34
|
|
16498
16498
|
});
|
|
16499
16499
|
} })
|
|
16500
16500
|
});
|
|
16501
16501
|
//#endregion
|
|
16502
16502
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
16503
|
-
const MESSAGE$
|
|
16503
|
+
const MESSAGE$33 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
16504
16504
|
const resolveSettings$21 = (settings) => {
|
|
16505
16505
|
const reactDoctor = settings?.["react-doctor"];
|
|
16506
16506
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -16556,7 +16556,7 @@ const noAutofocus = defineRule({
|
|
|
16556
16556
|
}
|
|
16557
16557
|
context.report({
|
|
16558
16558
|
node: autoFocusAttribute,
|
|
16559
|
-
message: MESSAGE$
|
|
16559
|
+
message: MESSAGE$33
|
|
16560
16560
|
});
|
|
16561
16561
|
} };
|
|
16562
16562
|
}
|
|
@@ -17060,7 +17060,7 @@ const noChainStateUpdates = defineRule({
|
|
|
17060
17060
|
});
|
|
17061
17061
|
//#endregion
|
|
17062
17062
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
17063
|
-
const MESSAGE$
|
|
17063
|
+
const MESSAGE$32 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
17064
17064
|
const noChildrenProp = defineRule({
|
|
17065
17065
|
id: "no-children-prop",
|
|
17066
17066
|
title: "Children passed as a prop",
|
|
@@ -17072,7 +17072,7 @@ const noChildrenProp = defineRule({
|
|
|
17072
17072
|
if (node.name.name !== "children") return;
|
|
17073
17073
|
context.report({
|
|
17074
17074
|
node: node.name,
|
|
17075
|
-
message: MESSAGE$
|
|
17075
|
+
message: MESSAGE$32
|
|
17076
17076
|
});
|
|
17077
17077
|
},
|
|
17078
17078
|
CallExpression(node) {
|
|
@@ -17085,7 +17085,7 @@ const noChildrenProp = defineRule({
|
|
|
17085
17085
|
const propertyKey = property.key;
|
|
17086
17086
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
17087
17087
|
node: propertyKey,
|
|
17088
|
-
message: MESSAGE$
|
|
17088
|
+
message: MESSAGE$32
|
|
17089
17089
|
});
|
|
17090
17090
|
}
|
|
17091
17091
|
}
|
|
@@ -17093,7 +17093,7 @@ const noChildrenProp = defineRule({
|
|
|
17093
17093
|
});
|
|
17094
17094
|
//#endregion
|
|
17095
17095
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
17096
|
-
const MESSAGE$
|
|
17096
|
+
const MESSAGE$31 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
17097
17097
|
const noCloneElement = defineRule({
|
|
17098
17098
|
id: "no-clone-element",
|
|
17099
17099
|
title: "cloneElement makes child props fragile",
|
|
@@ -17106,7 +17106,7 @@ const noCloneElement = defineRule({
|
|
|
17106
17106
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
17107
17107
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
17108
17108
|
node: callee,
|
|
17109
|
-
message: MESSAGE$
|
|
17109
|
+
message: MESSAGE$31
|
|
17110
17110
|
});
|
|
17111
17111
|
return;
|
|
17112
17112
|
}
|
|
@@ -17119,7 +17119,7 @@ const noCloneElement = defineRule({
|
|
|
17119
17119
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
17120
17120
|
context.report({
|
|
17121
17121
|
node: callee,
|
|
17122
|
-
message: MESSAGE$
|
|
17122
|
+
message: MESSAGE$31
|
|
17123
17123
|
});
|
|
17124
17124
|
}
|
|
17125
17125
|
} })
|
|
@@ -17168,7 +17168,7 @@ const enclosingComponentOrHookName = (node) => {
|
|
|
17168
17168
|
};
|
|
17169
17169
|
//#endregion
|
|
17170
17170
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
17171
|
-
const MESSAGE$
|
|
17171
|
+
const MESSAGE$30 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
17172
17172
|
const CONTEXT_MODULES = [
|
|
17173
17173
|
"react",
|
|
17174
17174
|
"use-context-selector",
|
|
@@ -17204,13 +17204,13 @@ const noCreateContextInRender = defineRule({
|
|
|
17204
17204
|
if (!componentOrHookName) return;
|
|
17205
17205
|
context.report({
|
|
17206
17206
|
node,
|
|
17207
|
-
message: `${MESSAGE$
|
|
17207
|
+
message: `${MESSAGE$30} (called inside "${componentOrHookName}")`
|
|
17208
17208
|
});
|
|
17209
17209
|
} })
|
|
17210
17210
|
});
|
|
17211
17211
|
//#endregion
|
|
17212
17212
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
17213
|
-
const MESSAGE$
|
|
17213
|
+
const MESSAGE$29 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
|
|
17214
17214
|
const noCreateRefInFunctionComponent = defineRule({
|
|
17215
17215
|
id: "no-create-ref-in-function-component",
|
|
17216
17216
|
title: "createRef in function component",
|
|
@@ -17229,7 +17229,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
17229
17229
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
|
|
17230
17230
|
context.report({
|
|
17231
17231
|
node,
|
|
17232
|
-
message: MESSAGE$
|
|
17232
|
+
message: MESSAGE$29
|
|
17233
17233
|
});
|
|
17234
17234
|
} })
|
|
17235
17235
|
});
|
|
@@ -17369,7 +17369,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
17369
17369
|
});
|
|
17370
17370
|
//#endregion
|
|
17371
17371
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
17372
|
-
const MESSAGE$
|
|
17372
|
+
const MESSAGE$28 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
17373
17373
|
const noDanger = defineRule({
|
|
17374
17374
|
id: "no-danger",
|
|
17375
17375
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -17382,7 +17382,7 @@ const noDanger = defineRule({
|
|
|
17382
17382
|
if (!propAttribute) return;
|
|
17383
17383
|
context.report({
|
|
17384
17384
|
node: propAttribute.name,
|
|
17385
|
-
message: MESSAGE$
|
|
17385
|
+
message: MESSAGE$28
|
|
17386
17386
|
});
|
|
17387
17387
|
},
|
|
17388
17388
|
CallExpression(node) {
|
|
@@ -17394,7 +17394,7 @@ const noDanger = defineRule({
|
|
|
17394
17394
|
const propertyKey = property.key;
|
|
17395
17395
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
17396
17396
|
node: propertyKey,
|
|
17397
|
-
message: MESSAGE$
|
|
17397
|
+
message: MESSAGE$28
|
|
17398
17398
|
});
|
|
17399
17399
|
}
|
|
17400
17400
|
}
|
|
@@ -17402,7 +17402,7 @@ const noDanger = defineRule({
|
|
|
17402
17402
|
});
|
|
17403
17403
|
//#endregion
|
|
17404
17404
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
17405
|
-
const MESSAGE$
|
|
17405
|
+
const MESSAGE$27 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
17406
17406
|
const isLineBreak = (child) => {
|
|
17407
17407
|
if (!isNodeOfType(child, "JSXText")) return false;
|
|
17408
17408
|
return child.value.trim().length === 0 && child.value.includes("\n");
|
|
@@ -17472,7 +17472,7 @@ const noDangerWithChildren = defineRule({
|
|
|
17472
17472
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
17473
17473
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
17474
17474
|
node: opening,
|
|
17475
|
-
message: MESSAGE$
|
|
17475
|
+
message: MESSAGE$27
|
|
17476
17476
|
});
|
|
17477
17477
|
},
|
|
17478
17478
|
CallExpression(node) {
|
|
@@ -17484,7 +17484,7 @@ const noDangerWithChildren = defineRule({
|
|
|
17484
17484
|
if (!propsShape.hasDangerously) return;
|
|
17485
17485
|
if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
|
|
17486
17486
|
node,
|
|
17487
|
-
message: MESSAGE$
|
|
17487
|
+
message: MESSAGE$27
|
|
17488
17488
|
});
|
|
17489
17489
|
}
|
|
17490
17490
|
})
|
|
@@ -18061,7 +18061,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
18061
18061
|
//#endregion
|
|
18062
18062
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
18063
18063
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
18064
|
-
const MESSAGE$
|
|
18064
|
+
const MESSAGE$26 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
18065
18065
|
const resolveSettings$20 = (settings) => {
|
|
18066
18066
|
const reactDoctor = settings?.["react-doctor"];
|
|
18067
18067
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -18080,7 +18080,7 @@ const noDidMountSetState = defineRule({
|
|
|
18080
18080
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
18081
18081
|
context.report({
|
|
18082
18082
|
node: node.callee,
|
|
18083
|
-
message: MESSAGE$
|
|
18083
|
+
message: MESSAGE$26
|
|
18084
18084
|
});
|
|
18085
18085
|
} };
|
|
18086
18086
|
}
|
|
@@ -18088,7 +18088,7 @@ const noDidMountSetState = defineRule({
|
|
|
18088
18088
|
//#endregion
|
|
18089
18089
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
18090
18090
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
18091
|
-
const MESSAGE$
|
|
18091
|
+
const MESSAGE$25 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
18092
18092
|
const resolveSettings$19 = (settings) => {
|
|
18093
18093
|
const reactDoctor = settings?.["react-doctor"];
|
|
18094
18094
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -18107,7 +18107,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
18107
18107
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
18108
18108
|
context.report({
|
|
18109
18109
|
node: node.callee,
|
|
18110
|
-
message: MESSAGE$
|
|
18110
|
+
message: MESSAGE$25
|
|
18111
18111
|
});
|
|
18112
18112
|
} };
|
|
18113
18113
|
}
|
|
@@ -18130,7 +18130,7 @@ const isStateMemberExpression = (node) => {
|
|
|
18130
18130
|
};
|
|
18131
18131
|
//#endregion
|
|
18132
18132
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
18133
|
-
const MESSAGE$
|
|
18133
|
+
const MESSAGE$24 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
|
|
18134
18134
|
const shouldIgnoreMutation = (node) => {
|
|
18135
18135
|
let isConstructor = false;
|
|
18136
18136
|
let isInsideCallExpression = false;
|
|
@@ -18152,7 +18152,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
18152
18152
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
18153
18153
|
context.report({
|
|
18154
18154
|
node: reportNode,
|
|
18155
|
-
message: MESSAGE$
|
|
18155
|
+
message: MESSAGE$24
|
|
18156
18156
|
});
|
|
18157
18157
|
};
|
|
18158
18158
|
const noDirectMutationState = defineRule({
|
|
@@ -18362,6 +18362,26 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
18362
18362
|
} })
|
|
18363
18363
|
});
|
|
18364
18364
|
//#endregion
|
|
18365
|
+
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
18366
|
+
const MESSAGE$23 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
18367
|
+
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
18368
|
+
const noDocumentWrite = defineRule({
|
|
18369
|
+
id: "no-document-write",
|
|
18370
|
+
title: "document.write/writeln",
|
|
18371
|
+
severity: "warn",
|
|
18372
|
+
recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
|
|
18373
|
+
create: (context) => ({ CallExpression(node) {
|
|
18374
|
+
const callee = node.callee;
|
|
18375
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
18376
|
+
if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "document") return;
|
|
18377
|
+
if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
|
|
18378
|
+
context.report({
|
|
18379
|
+
node,
|
|
18380
|
+
message: MESSAGE$23
|
|
18381
|
+
});
|
|
18382
|
+
} })
|
|
18383
|
+
});
|
|
18384
|
+
//#endregion
|
|
18365
18385
|
//#region src/plugin/rules/bundle-size/no-dynamic-import-path.ts
|
|
18366
18386
|
const noDynamicImportPath = defineRule({
|
|
18367
18387
|
id: "no-dynamic-import-path",
|
|
@@ -19740,7 +19760,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
19740
19760
|
"ReactDOM",
|
|
19741
19761
|
"ReactDom"
|
|
19742
19762
|
]);
|
|
19743
|
-
const MESSAGE$
|
|
19763
|
+
const MESSAGE$22 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
19744
19764
|
const noFindDomNode = defineRule({
|
|
19745
19765
|
id: "no-find-dom-node",
|
|
19746
19766
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -19751,7 +19771,7 @@ const noFindDomNode = defineRule({
|
|
|
19751
19771
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
19752
19772
|
context.report({
|
|
19753
19773
|
node: callee,
|
|
19754
|
-
message: MESSAGE$
|
|
19774
|
+
message: MESSAGE$22
|
|
19755
19775
|
});
|
|
19756
19776
|
return;
|
|
19757
19777
|
}
|
|
@@ -19762,7 +19782,7 @@ const noFindDomNode = defineRule({
|
|
|
19762
19782
|
if (callee.property.name !== "findDOMNode") return;
|
|
19763
19783
|
context.report({
|
|
19764
19784
|
node: callee.property,
|
|
19765
|
-
message: MESSAGE$
|
|
19785
|
+
message: MESSAGE$22
|
|
19766
19786
|
});
|
|
19767
19787
|
}
|
|
19768
19788
|
} })
|
|
@@ -20004,7 +20024,7 @@ const noGrayOnColoredBackground = defineRule({
|
|
|
20004
20024
|
});
|
|
20005
20025
|
//#endregion
|
|
20006
20026
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
20007
|
-
const MESSAGE$
|
|
20027
|
+
const MESSAGE$21 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
|
|
20008
20028
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
20009
20029
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
20010
20030
|
title: "Lazy image with high fetchPriority",
|
|
@@ -20018,7 +20038,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
20018
20038
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
20019
20039
|
context.report({
|
|
20020
20040
|
node: node.name,
|
|
20021
|
-
message: MESSAGE$
|
|
20041
|
+
message: MESSAGE$21
|
|
20022
20042
|
});
|
|
20023
20043
|
} })
|
|
20024
20044
|
});
|
|
@@ -20253,7 +20273,7 @@ const noIsMounted = defineRule({
|
|
|
20253
20273
|
});
|
|
20254
20274
|
//#endregion
|
|
20255
20275
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
20256
|
-
const MESSAGE$
|
|
20276
|
+
const MESSAGE$20 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
|
|
20257
20277
|
const isJsonMethodCall = (node, method) => {
|
|
20258
20278
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
20259
20279
|
const callee = node.callee;
|
|
@@ -20270,13 +20290,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
20270
20290
|
if (!firstArgument || !isJsonMethodCall(firstArgument, "stringify")) return;
|
|
20271
20291
|
context.report({
|
|
20272
20292
|
node,
|
|
20273
|
-
message: MESSAGE$
|
|
20293
|
+
message: MESSAGE$20
|
|
20274
20294
|
});
|
|
20275
20295
|
} })
|
|
20276
20296
|
});
|
|
20277
20297
|
//#endregion
|
|
20278
20298
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
20279
|
-
const MESSAGE$
|
|
20299
|
+
const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
20280
20300
|
const isJsxElementTypeReference = (node) => {
|
|
20281
20301
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
20282
20302
|
const typeName = node.typeName;
|
|
@@ -20293,7 +20313,7 @@ const checkReturnType = (context, returnType) => {
|
|
|
20293
20313
|
if (!typeAnnotation) return;
|
|
20294
20314
|
if (isJsxElementTypeReference(typeAnnotation)) context.report({
|
|
20295
20315
|
node: typeAnnotation,
|
|
20296
|
-
message: MESSAGE$
|
|
20316
|
+
message: MESSAGE$19
|
|
20297
20317
|
});
|
|
20298
20318
|
};
|
|
20299
20319
|
const noJsxElementType = defineRule({
|
|
@@ -20748,7 +20768,7 @@ const noMoment = defineRule({
|
|
|
20748
20768
|
});
|
|
20749
20769
|
//#endregion
|
|
20750
20770
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
20751
|
-
const MESSAGE$
|
|
20771
|
+
const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
20752
20772
|
const resolveSettings$16 = (settings) => {
|
|
20753
20773
|
const reactDoctor = settings?.["react-doctor"];
|
|
20754
20774
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -21070,7 +21090,7 @@ const noMultiComp = defineRule({
|
|
|
21070
21090
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
21071
21091
|
for (const component of flagged.slice(1)) context.report({
|
|
21072
21092
|
node: component.reportNode,
|
|
21073
|
-
message: MESSAGE$
|
|
21093
|
+
message: MESSAGE$18
|
|
21074
21094
|
});
|
|
21075
21095
|
} };
|
|
21076
21096
|
}
|
|
@@ -21238,7 +21258,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
21238
21258
|
};
|
|
21239
21259
|
//#endregion
|
|
21240
21260
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
21241
|
-
const MESSAGE$
|
|
21261
|
+
const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
|
|
21242
21262
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
21243
21263
|
"copyWithin",
|
|
21244
21264
|
"fill",
|
|
@@ -21448,7 +21468,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
21448
21468
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
21449
21469
|
context.report({
|
|
21450
21470
|
node: options.crossFileConsumerCallSite,
|
|
21451
|
-
message: `${MESSAGE$
|
|
21471
|
+
message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
21452
21472
|
});
|
|
21453
21473
|
return;
|
|
21454
21474
|
}
|
|
@@ -21457,7 +21477,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
21457
21477
|
reportedNodes.add(mutation.node);
|
|
21458
21478
|
context.report({
|
|
21459
21479
|
node: mutation.node,
|
|
21460
|
-
message: MESSAGE$
|
|
21480
|
+
message: MESSAGE$17
|
|
21461
21481
|
});
|
|
21462
21482
|
}
|
|
21463
21483
|
};
|
|
@@ -21729,7 +21749,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
21729
21749
|
});
|
|
21730
21750
|
//#endregion
|
|
21731
21751
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
21732
|
-
const MESSAGE$
|
|
21752
|
+
const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
21733
21753
|
const resolveSettings$14 = (settings) => {
|
|
21734
21754
|
const reactDoctor = settings?.["react-doctor"];
|
|
21735
21755
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noNoninteractiveTabindex ?? {} : {};
|
|
@@ -21757,7 +21777,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
21757
21777
|
if (numeric === null) {
|
|
21758
21778
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues) context.report({
|
|
21759
21779
|
node: tabIndex,
|
|
21760
|
-
message: MESSAGE$
|
|
21780
|
+
message: MESSAGE$16
|
|
21761
21781
|
});
|
|
21762
21782
|
return;
|
|
21763
21783
|
}
|
|
@@ -21770,7 +21790,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
21770
21790
|
if (!roleAttribute) {
|
|
21771
21791
|
context.report({
|
|
21772
21792
|
node: tabIndex,
|
|
21773
|
-
message: MESSAGE$
|
|
21793
|
+
message: MESSAGE$16
|
|
21774
21794
|
});
|
|
21775
21795
|
return;
|
|
21776
21796
|
}
|
|
@@ -21784,7 +21804,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
21784
21804
|
}
|
|
21785
21805
|
context.report({
|
|
21786
21806
|
node: tabIndex,
|
|
21787
|
-
message: MESSAGE$
|
|
21807
|
+
message: MESSAGE$16
|
|
21788
21808
|
});
|
|
21789
21809
|
} };
|
|
21790
21810
|
}
|
|
@@ -22475,7 +22495,7 @@ const noRandomKey = defineRule({
|
|
|
22475
22495
|
});
|
|
22476
22496
|
//#endregion
|
|
22477
22497
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
22478
|
-
const MESSAGE$
|
|
22498
|
+
const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
22479
22499
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
22480
22500
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
22481
22501
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -22501,13 +22521,13 @@ const noReactChildren = defineRule({
|
|
|
22501
22521
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
22502
22522
|
context.report({
|
|
22503
22523
|
node: calleeOuter,
|
|
22504
|
-
message: MESSAGE$
|
|
22524
|
+
message: MESSAGE$15
|
|
22505
22525
|
});
|
|
22506
22526
|
return;
|
|
22507
22527
|
}
|
|
22508
22528
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
22509
22529
|
node: calleeOuter,
|
|
22510
|
-
message: MESSAGE$
|
|
22530
|
+
message: MESSAGE$15
|
|
22511
22531
|
});
|
|
22512
22532
|
} })
|
|
22513
22533
|
});
|
|
@@ -22830,7 +22850,7 @@ const noRenderPropChildren = defineRule({
|
|
|
22830
22850
|
});
|
|
22831
22851
|
//#endregion
|
|
22832
22852
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
22833
|
-
const MESSAGE$
|
|
22853
|
+
const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
22834
22854
|
const isReactDomRenderCall = (node) => {
|
|
22835
22855
|
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
22836
22856
|
if (!isNodeOfType(node.callee.object, "Identifier")) return false;
|
|
@@ -22854,7 +22874,7 @@ const noRenderReturnValue = defineRule({
|
|
|
22854
22874
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
22855
22875
|
context.report({
|
|
22856
22876
|
node: node.callee,
|
|
22857
|
-
message: MESSAGE$
|
|
22877
|
+
message: MESSAGE$14
|
|
22858
22878
|
});
|
|
22859
22879
|
} })
|
|
22860
22880
|
});
|
|
@@ -23552,7 +23572,7 @@ const getParentComponent = (node) => {
|
|
|
23552
23572
|
};
|
|
23553
23573
|
//#endregion
|
|
23554
23574
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
23555
|
-
const MESSAGE$
|
|
23575
|
+
const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
23556
23576
|
const noSetState = defineRule({
|
|
23557
23577
|
id: "no-set-state",
|
|
23558
23578
|
title: "Local class state forbidden",
|
|
@@ -23567,7 +23587,7 @@ const noSetState = defineRule({
|
|
|
23567
23587
|
if (!getParentComponent(node)) return;
|
|
23568
23588
|
context.report({
|
|
23569
23589
|
node: node.callee,
|
|
23570
|
-
message: MESSAGE$
|
|
23590
|
+
message: MESSAGE$13
|
|
23571
23591
|
});
|
|
23572
23592
|
} })
|
|
23573
23593
|
});
|
|
@@ -23729,7 +23749,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
23729
23749
|
};
|
|
23730
23750
|
//#endregion
|
|
23731
23751
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
23732
|
-
const MESSAGE$
|
|
23752
|
+
const MESSAGE$12 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
|
|
23733
23753
|
const DEFAULT_HANDLERS = [
|
|
23734
23754
|
"onClick",
|
|
23735
23755
|
"onMouseDown",
|
|
@@ -23789,7 +23809,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
23789
23809
|
if (!roleAttribute || !roleAttribute.value) {
|
|
23790
23810
|
context.report({
|
|
23791
23811
|
node: node.name,
|
|
23792
|
-
message: MESSAGE$
|
|
23812
|
+
message: MESSAGE$12
|
|
23793
23813
|
});
|
|
23794
23814
|
return;
|
|
23795
23815
|
}
|
|
@@ -23799,19 +23819,66 @@ const noStaticElementInteractions = defineRule({
|
|
|
23799
23819
|
if (firstRole && (isInteractiveRole(firstRole) || isNonInteractiveRole(firstRole))) return;
|
|
23800
23820
|
context.report({
|
|
23801
23821
|
node: node.name,
|
|
23802
|
-
message: MESSAGE$
|
|
23822
|
+
message: MESSAGE$12
|
|
23803
23823
|
});
|
|
23804
23824
|
return;
|
|
23805
23825
|
}
|
|
23806
23826
|
if (isNodeOfType(attributeValue, "JSXExpressionContainer") && settings.allowExpressionValues) return;
|
|
23807
23827
|
context.report({
|
|
23808
23828
|
node: node.name,
|
|
23809
|
-
message: MESSAGE$
|
|
23829
|
+
message: MESSAGE$12
|
|
23810
23830
|
});
|
|
23811
23831
|
} };
|
|
23812
23832
|
}
|
|
23813
23833
|
});
|
|
23814
23834
|
//#endregion
|
|
23835
|
+
//#region src/plugin/rules/react-builtins/no-string-false-on-boolean-attribute.ts
|
|
23836
|
+
const BOOLEAN_ATTRIBUTES = new Set([
|
|
23837
|
+
"disabled",
|
|
23838
|
+
"checked",
|
|
23839
|
+
"readonly",
|
|
23840
|
+
"required",
|
|
23841
|
+
"selected",
|
|
23842
|
+
"multiple",
|
|
23843
|
+
"autofocus",
|
|
23844
|
+
"autoplay",
|
|
23845
|
+
"controls",
|
|
23846
|
+
"loop",
|
|
23847
|
+
"muted",
|
|
23848
|
+
"open",
|
|
23849
|
+
"reversed",
|
|
23850
|
+
"default",
|
|
23851
|
+
"novalidate",
|
|
23852
|
+
"formnovalidate",
|
|
23853
|
+
"playsinline",
|
|
23854
|
+
"itemscope",
|
|
23855
|
+
"allowfullscreen"
|
|
23856
|
+
]);
|
|
23857
|
+
const noStringFalseOnBooleanAttribute = defineRule({
|
|
23858
|
+
id: "no-string-false-on-boolean-attribute",
|
|
23859
|
+
title: "String true/false on a boolean attribute",
|
|
23860
|
+
severity: "warn",
|
|
23861
|
+
recommendation: "Use the boolean form on boolean attributes: `disabled` / `disabled={true}` / `disabled={false}`, not `disabled=\"false\"`. A non-empty string is truthy, so `=\"false\"` actually turns the attribute ON.",
|
|
23862
|
+
create: (context) => ({ JSXOpeningElement(node) {
|
|
23863
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
23864
|
+
const firstCharacter = node.name.name.charCodeAt(0);
|
|
23865
|
+
if (firstCharacter < 97 || firstCharacter > 122) return;
|
|
23866
|
+
for (const attribute of node.attributes) {
|
|
23867
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
23868
|
+
if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
|
|
23869
|
+
if (!BOOLEAN_ATTRIBUTES.has(attribute.name.name.toLowerCase())) continue;
|
|
23870
|
+
const value = getJsxPropStringValue(attribute);
|
|
23871
|
+
if (value !== "false" && value !== "true") continue;
|
|
23872
|
+
const attributeName = attribute.name.name;
|
|
23873
|
+
const guidance = value === "false" ? `which React treats as truthy, so the attribute is applied even though you wrote "false". Use \`${attributeName}={false}\` (or omit the attribute) to keep it off` : `but a boolean attribute takes a boolean, not the string "true". Use \`${attributeName}\` or \`${attributeName}={true}\``;
|
|
23874
|
+
context.report({
|
|
23875
|
+
node: attribute,
|
|
23876
|
+
message: `\`${attributeName}="${value}"\` passes the string "${value}", ${guidance}.`
|
|
23877
|
+
});
|
|
23878
|
+
}
|
|
23879
|
+
} })
|
|
23880
|
+
});
|
|
23881
|
+
//#endregion
|
|
23815
23882
|
//#region src/plugin/rules/react-builtins/no-string-refs.ts
|
|
23816
23883
|
const STRING_IN_REF_MESSAGE = "Your component can't reach this node because string refs don't work in modern React.";
|
|
23817
23884
|
const THIS_REFS_MESSAGE = "Your component can't reach its nodes because `this.refs` is empty in modern React.";
|
|
@@ -23862,6 +23929,27 @@ const noStringRefs = defineRule({
|
|
|
23862
23929
|
}
|
|
23863
23930
|
});
|
|
23864
23931
|
//#endregion
|
|
23932
|
+
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
23933
|
+
const MESSAGE$11 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
|
|
23934
|
+
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
23935
|
+
const noSyncXhr = defineRule({
|
|
23936
|
+
id: "no-sync-xhr",
|
|
23937
|
+
title: "Synchronous XMLHttpRequest",
|
|
23938
|
+
severity: "warn",
|
|
23939
|
+
recommendation: "Never open an XMLHttpRequest synchronously (`async` = `false`). It blocks the main thread. Use `fetch()` or pass `true` and handle the response asynchronously.",
|
|
23940
|
+
create: (context) => ({ CallExpression(node) {
|
|
23941
|
+
const callee = node.callee;
|
|
23942
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
23943
|
+
if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "open") return;
|
|
23944
|
+
const asyncArgument = node.arguments?.[2];
|
|
23945
|
+
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
23946
|
+
context.report({
|
|
23947
|
+
node,
|
|
23948
|
+
message: MESSAGE$11
|
|
23949
|
+
});
|
|
23950
|
+
} })
|
|
23951
|
+
});
|
|
23952
|
+
//#endregion
|
|
23865
23953
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
23866
23954
|
const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
|
|
23867
23955
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
@@ -39173,6 +39261,17 @@ const reactDoctorRules = [
|
|
|
39173
39261
|
requires: [...new Set(["react", ...noDocumentStartViewTransition.requires ?? []])]
|
|
39174
39262
|
}
|
|
39175
39263
|
},
|
|
39264
|
+
{
|
|
39265
|
+
key: "react-doctor/no-document-write",
|
|
39266
|
+
id: "no-document-write",
|
|
39267
|
+
source: "react-doctor",
|
|
39268
|
+
originallyExternal: false,
|
|
39269
|
+
rule: {
|
|
39270
|
+
...noDocumentWrite,
|
|
39271
|
+
framework: "global",
|
|
39272
|
+
category: "Performance"
|
|
39273
|
+
}
|
|
39274
|
+
},
|
|
39176
39275
|
{
|
|
39177
39276
|
key: "react-doctor/no-dynamic-import-path",
|
|
39178
39277
|
id: "no-dynamic-import-path",
|
|
@@ -39982,6 +40081,18 @@ const reactDoctorRules = [
|
|
|
39982
40081
|
requires: [...new Set(["react", ...noStaticElementInteractions.requires ?? []])]
|
|
39983
40082
|
}
|
|
39984
40083
|
},
|
|
40084
|
+
{
|
|
40085
|
+
key: "react-doctor/no-string-false-on-boolean-attribute",
|
|
40086
|
+
id: "no-string-false-on-boolean-attribute",
|
|
40087
|
+
source: "react-doctor",
|
|
40088
|
+
originallyExternal: false,
|
|
40089
|
+
rule: {
|
|
40090
|
+
...noStringFalseOnBooleanAttribute,
|
|
40091
|
+
framework: "global",
|
|
40092
|
+
category: "Bugs",
|
|
40093
|
+
requires: [...new Set(["react", ...noStringFalseOnBooleanAttribute.requires ?? []])]
|
|
40094
|
+
}
|
|
40095
|
+
},
|
|
39985
40096
|
{
|
|
39986
40097
|
key: "react-doctor/no-string-refs",
|
|
39987
40098
|
id: "no-string-refs",
|
|
@@ -39994,6 +40105,17 @@ const reactDoctorRules = [
|
|
|
39994
40105
|
requires: [...new Set(["react", ...noStringRefs.requires ?? []])]
|
|
39995
40106
|
}
|
|
39996
40107
|
},
|
|
40108
|
+
{
|
|
40109
|
+
key: "react-doctor/no-sync-xhr",
|
|
40110
|
+
id: "no-sync-xhr",
|
|
40111
|
+
source: "react-doctor",
|
|
40112
|
+
originallyExternal: false,
|
|
40113
|
+
rule: {
|
|
40114
|
+
...noSyncXhr,
|
|
40115
|
+
framework: "global",
|
|
40116
|
+
category: "Performance"
|
|
40117
|
+
}
|
|
40118
|
+
},
|
|
39997
40119
|
{
|
|
39998
40120
|
key: "react-doctor/no-this-in-sfc",
|
|
39999
40121
|
id: "no-this-in-sfc",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-react-doctor",
|
|
3
|
-
"version": "0.5.6-dev.
|
|
3
|
+
"version": "0.5.6-dev.451beeb",
|
|
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",
|