oxlint-plugin-react-doctor 0.7.3-dev.6b70b32 → 0.7.3-dev.f91ede7
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 +92 -0
- package/dist/index.js +326 -94
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4422,6 +4422,29 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
4422
4422
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
4423
4423
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
4424
4424
|
};
|
|
4425
|
+
}, {
|
|
4426
|
+
readonly key: "react-doctor/no-indeterminate-attribute";
|
|
4427
|
+
readonly id: "no-indeterminate-attribute";
|
|
4428
|
+
readonly source: "react-doctor";
|
|
4429
|
+
readonly originallyExternal: false;
|
|
4430
|
+
readonly rule: {
|
|
4431
|
+
readonly framework: "global";
|
|
4432
|
+
readonly category: "Bugs";
|
|
4433
|
+
readonly id: string;
|
|
4434
|
+
readonly title?: string;
|
|
4435
|
+
readonly severity: RuleSeverity;
|
|
4436
|
+
readonly requires?: ReadonlyArray<Capability>;
|
|
4437
|
+
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
4438
|
+
readonly tags?: ReadonlyArray<string>;
|
|
4439
|
+
readonly matchByOccurrence?: boolean;
|
|
4440
|
+
readonly defaultEnabled?: boolean;
|
|
4441
|
+
readonly lifecycle?: "retired";
|
|
4442
|
+
readonly scan?: FileScan;
|
|
4443
|
+
readonly committedFilesOnly?: boolean;
|
|
4444
|
+
readonly recommendation?: string;
|
|
4445
|
+
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
4446
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
4447
|
+
};
|
|
4425
4448
|
}, {
|
|
4426
4449
|
readonly key: "react-doctor/no-initialize-state";
|
|
4427
4450
|
readonly id: "no-initialize-state";
|
|
@@ -4813,6 +4836,29 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
4813
4836
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
4814
4837
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
4815
4838
|
};
|
|
4839
|
+
}, {
|
|
4840
|
+
readonly key: "react-doctor/no-match-media-in-state-initializer";
|
|
4841
|
+
readonly id: "no-match-media-in-state-initializer";
|
|
4842
|
+
readonly source: "react-doctor";
|
|
4843
|
+
readonly originallyExternal: false;
|
|
4844
|
+
readonly rule: {
|
|
4845
|
+
readonly framework: "global";
|
|
4846
|
+
readonly category: "Bugs";
|
|
4847
|
+
readonly requires: readonly Capability[];
|
|
4848
|
+
readonly id: string;
|
|
4849
|
+
readonly title?: string;
|
|
4850
|
+
readonly severity: RuleSeverity;
|
|
4851
|
+
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
4852
|
+
readonly tags?: ReadonlyArray<string>;
|
|
4853
|
+
readonly matchByOccurrence?: boolean;
|
|
4854
|
+
readonly defaultEnabled?: boolean;
|
|
4855
|
+
readonly lifecycle?: "retired";
|
|
4856
|
+
readonly scan?: FileScan;
|
|
4857
|
+
readonly committedFilesOnly?: boolean;
|
|
4858
|
+
readonly recommendation?: string;
|
|
4859
|
+
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
4860
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
4861
|
+
};
|
|
4816
4862
|
}, {
|
|
4817
4863
|
readonly key: "react-doctor/no-mirror-prop-effect";
|
|
4818
4864
|
readonly id: "no-mirror-prop-effect";
|
|
@@ -13527,6 +13573,29 @@ declare const RULES: readonly [{
|
|
|
13527
13573
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
13528
13574
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
13529
13575
|
};
|
|
13576
|
+
}, {
|
|
13577
|
+
readonly key: "react-doctor/no-indeterminate-attribute";
|
|
13578
|
+
readonly id: "no-indeterminate-attribute";
|
|
13579
|
+
readonly source: "react-doctor";
|
|
13580
|
+
readonly originallyExternal: false;
|
|
13581
|
+
readonly rule: {
|
|
13582
|
+
readonly framework: "global";
|
|
13583
|
+
readonly category: "Bugs";
|
|
13584
|
+
readonly id: string;
|
|
13585
|
+
readonly title?: string;
|
|
13586
|
+
readonly severity: RuleSeverity;
|
|
13587
|
+
readonly requires?: ReadonlyArray<Capability>;
|
|
13588
|
+
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
13589
|
+
readonly tags?: ReadonlyArray<string>;
|
|
13590
|
+
readonly matchByOccurrence?: boolean;
|
|
13591
|
+
readonly defaultEnabled?: boolean;
|
|
13592
|
+
readonly lifecycle?: "retired";
|
|
13593
|
+
readonly scan?: FileScan;
|
|
13594
|
+
readonly committedFilesOnly?: boolean;
|
|
13595
|
+
readonly recommendation?: string;
|
|
13596
|
+
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
13597
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
13598
|
+
};
|
|
13530
13599
|
}, {
|
|
13531
13600
|
readonly key: "react-doctor/no-initialize-state";
|
|
13532
13601
|
readonly id: "no-initialize-state";
|
|
@@ -13918,6 +13987,29 @@ declare const RULES: readonly [{
|
|
|
13918
13987
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
13919
13988
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
13920
13989
|
};
|
|
13990
|
+
}, {
|
|
13991
|
+
readonly key: "react-doctor/no-match-media-in-state-initializer";
|
|
13992
|
+
readonly id: "no-match-media-in-state-initializer";
|
|
13993
|
+
readonly source: "react-doctor";
|
|
13994
|
+
readonly originallyExternal: false;
|
|
13995
|
+
readonly rule: {
|
|
13996
|
+
readonly framework: "global";
|
|
13997
|
+
readonly category: "Bugs";
|
|
13998
|
+
readonly requires: readonly Capability[];
|
|
13999
|
+
readonly id: string;
|
|
14000
|
+
readonly title?: string;
|
|
14001
|
+
readonly severity: RuleSeverity;
|
|
14002
|
+
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
14003
|
+
readonly tags?: ReadonlyArray<string>;
|
|
14004
|
+
readonly matchByOccurrence?: boolean;
|
|
14005
|
+
readonly defaultEnabled?: boolean;
|
|
14006
|
+
readonly lifecycle?: "retired";
|
|
14007
|
+
readonly scan?: FileScan;
|
|
14008
|
+
readonly committedFilesOnly?: boolean;
|
|
14009
|
+
readonly recommendation?: string;
|
|
14010
|
+
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
14011
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
14012
|
+
};
|
|
13921
14013
|
}, {
|
|
13922
14014
|
readonly key: "react-doctor/no-mirror-prop-effect";
|
|
13923
14015
|
readonly id: "no-mirror-prop-effect";
|
package/dist/index.js
CHANGED
|
@@ -2215,7 +2215,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2215
2215
|
});
|
|
2216
2216
|
//#endregion
|
|
2217
2217
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
2218
|
-
const MESSAGE$
|
|
2218
|
+
const MESSAGE$61 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
2219
2219
|
const isTransComponentsTemplate = (node) => {
|
|
2220
2220
|
let current = node.parent;
|
|
2221
2221
|
while (current) {
|
|
@@ -2251,7 +2251,7 @@ const anchorHasContent = defineRule({
|
|
|
2251
2251
|
if (isTransComponentsTemplate(node)) return;
|
|
2252
2252
|
context.report({
|
|
2253
2253
|
node: opening.name,
|
|
2254
|
-
message: MESSAGE$
|
|
2254
|
+
message: MESSAGE$61
|
|
2255
2255
|
});
|
|
2256
2256
|
} };
|
|
2257
2257
|
}
|
|
@@ -2682,7 +2682,7 @@ const parseJsxValue = (value) => {
|
|
|
2682
2682
|
};
|
|
2683
2683
|
//#endregion
|
|
2684
2684
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2685
|
-
const MESSAGE$
|
|
2685
|
+
const MESSAGE$60 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2686
2686
|
const mayBeContentEditable = (node) => {
|
|
2687
2687
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
|
|
2688
2688
|
if (!attribute) return false;
|
|
@@ -2713,7 +2713,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2713
2713
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2714
2714
|
context.report({
|
|
2715
2715
|
node: node.name,
|
|
2716
|
-
message: MESSAGE$
|
|
2716
|
+
message: MESSAGE$60
|
|
2717
2717
|
});
|
|
2718
2718
|
return;
|
|
2719
2719
|
}
|
|
@@ -2721,7 +2721,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2721
2721
|
if (mayBeContentEditable(node)) return;
|
|
2722
2722
|
context.report({
|
|
2723
2723
|
node: node.name,
|
|
2724
|
-
message: MESSAGE$
|
|
2724
|
+
message: MESSAGE$60
|
|
2725
2725
|
});
|
|
2726
2726
|
} })
|
|
2727
2727
|
});
|
|
@@ -5139,7 +5139,7 @@ const asyncParallel = defineRule({
|
|
|
5139
5139
|
});
|
|
5140
5140
|
//#endregion
|
|
5141
5141
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
5142
|
-
const MESSAGE$
|
|
5142
|
+
const MESSAGE$59 = "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.";
|
|
5143
5143
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
5144
5144
|
const STORAGE_GLOBALS = new Set([
|
|
5145
5145
|
"window",
|
|
@@ -5200,7 +5200,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5200
5200
|
if (keyString === null || !isAuthCredentialKey(keyString)) return;
|
|
5201
5201
|
context.report({
|
|
5202
5202
|
node,
|
|
5203
|
-
message: MESSAGE$
|
|
5203
|
+
message: MESSAGE$59
|
|
5204
5204
|
});
|
|
5205
5205
|
},
|
|
5206
5206
|
AssignmentExpression(node) {
|
|
@@ -5211,7 +5211,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5211
5211
|
if (!propertyName || !isAuthCredentialKey(propertyName)) return;
|
|
5212
5212
|
context.report({
|
|
5213
5213
|
node: target,
|
|
5214
|
-
message: MESSAGE$
|
|
5214
|
+
message: MESSAGE$59
|
|
5215
5215
|
});
|
|
5216
5216
|
}
|
|
5217
5217
|
}))
|
|
@@ -5350,7 +5350,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
|
|
|
5350
5350
|
const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
|
|
5351
5351
|
const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
|
|
5352
5352
|
const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
|
|
5353
|
-
const MESSAGE$
|
|
5353
|
+
const MESSAGE$58 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
|
|
5354
5354
|
const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
|
|
5355
5355
|
const scanWorkflowContent = (content) => {
|
|
5356
5356
|
const lines = content.split("\n");
|
|
@@ -5395,7 +5395,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5395
5395
|
const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
|
|
5396
5396
|
const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
|
|
5397
5397
|
return [{
|
|
5398
|
-
message: MESSAGE$
|
|
5398
|
+
message: MESSAGE$58,
|
|
5399
5399
|
line: step.startLineIndex + installLineOffset + 1,
|
|
5400
5400
|
column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
|
|
5401
5401
|
}];
|
|
@@ -5405,7 +5405,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5405
5405
|
const scanNonWorkflowConfig = scanByPattern({
|
|
5406
5406
|
shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
|
|
5407
5407
|
pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
|
|
5408
|
-
message: MESSAGE$
|
|
5408
|
+
message: MESSAGE$58
|
|
5409
5409
|
});
|
|
5410
5410
|
const scan = (file) => {
|
|
5411
5411
|
if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
|
|
@@ -5945,7 +5945,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
5945
5945
|
};
|
|
5946
5946
|
//#endregion
|
|
5947
5947
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
5948
|
-
const MESSAGE$
|
|
5948
|
+
const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
5949
5949
|
const KEY_HANDLERS = [
|
|
5950
5950
|
"onKeyUp",
|
|
5951
5951
|
"onKeyDown",
|
|
@@ -6114,7 +6114,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6114
6114
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
6115
6115
|
context.report({
|
|
6116
6116
|
node: node.name,
|
|
6117
|
-
message: MESSAGE$
|
|
6117
|
+
message: MESSAGE$57
|
|
6118
6118
|
});
|
|
6119
6119
|
} };
|
|
6120
6120
|
}
|
|
@@ -6343,7 +6343,7 @@ const getClassNameLiteral = (classAttribute) => {
|
|
|
6343
6343
|
};
|
|
6344
6344
|
//#endregion
|
|
6345
6345
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
6346
|
-
const MESSAGE$
|
|
6346
|
+
const MESSAGE$56 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
6347
6347
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
6348
6348
|
"td",
|
|
6349
6349
|
"th",
|
|
@@ -6781,7 +6781,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
6781
6781
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
6782
6782
|
context.report({
|
|
6783
6783
|
node: candidate.opening,
|
|
6784
|
-
message: MESSAGE$
|
|
6784
|
+
message: MESSAGE$56
|
|
6785
6785
|
});
|
|
6786
6786
|
}
|
|
6787
6787
|
}
|
|
@@ -7288,7 +7288,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
7288
7288
|
});
|
|
7289
7289
|
//#endregion
|
|
7290
7290
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
7291
|
-
const MESSAGE$
|
|
7291
|
+
const MESSAGE$55 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
7292
7292
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
7293
7293
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
7294
7294
|
"aria-label",
|
|
@@ -7313,7 +7313,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
7313
7313
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
7314
7314
|
context.report({
|
|
7315
7315
|
node: node.name,
|
|
7316
|
-
message: MESSAGE$
|
|
7316
|
+
message: MESSAGE$55
|
|
7317
7317
|
});
|
|
7318
7318
|
} };
|
|
7319
7319
|
}
|
|
@@ -7353,7 +7353,7 @@ const isEs6Component = (node) => {
|
|
|
7353
7353
|
};
|
|
7354
7354
|
//#endregion
|
|
7355
7355
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
7356
|
-
const MESSAGE$
|
|
7356
|
+
const MESSAGE$54 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
7357
7357
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
7358
7358
|
"observer",
|
|
7359
7359
|
"lazy",
|
|
@@ -7546,7 +7546,7 @@ const displayName = defineRule({
|
|
|
7546
7546
|
const reportAt = (node) => {
|
|
7547
7547
|
context.report({
|
|
7548
7548
|
node,
|
|
7549
|
-
message: MESSAGE$
|
|
7549
|
+
message: MESSAGE$54
|
|
7550
7550
|
});
|
|
7551
7551
|
};
|
|
7552
7552
|
return {
|
|
@@ -11037,7 +11037,7 @@ const forbidElements = defineRule({
|
|
|
11037
11037
|
});
|
|
11038
11038
|
//#endregion
|
|
11039
11039
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
11040
|
-
const MESSAGE$
|
|
11040
|
+
const MESSAGE$53 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
11041
11041
|
const forwardRefUsesRef = defineRule({
|
|
11042
11042
|
id: "forward-ref-uses-ref",
|
|
11043
11043
|
title: "forwardRef without ref parameter",
|
|
@@ -11057,7 +11057,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
11057
11057
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
11058
11058
|
context.report({
|
|
11059
11059
|
node: inner,
|
|
11060
|
-
message: MESSAGE$
|
|
11060
|
+
message: MESSAGE$53
|
|
11061
11061
|
});
|
|
11062
11062
|
} })
|
|
11063
11063
|
});
|
|
@@ -11098,7 +11098,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
11098
11098
|
});
|
|
11099
11099
|
//#endregion
|
|
11100
11100
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
11101
|
-
const MESSAGE$
|
|
11101
|
+
const MESSAGE$52 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
11102
11102
|
const DEFAULT_HEADING_TAGS = [
|
|
11103
11103
|
"h1",
|
|
11104
11104
|
"h2",
|
|
@@ -11132,7 +11132,7 @@ const headingHasContent = defineRule({
|
|
|
11132
11132
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
11133
11133
|
context.report({
|
|
11134
11134
|
node,
|
|
11135
|
-
message: MESSAGE$
|
|
11135
|
+
message: MESSAGE$52
|
|
11136
11136
|
});
|
|
11137
11137
|
} };
|
|
11138
11138
|
}
|
|
@@ -11270,7 +11270,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
11270
11270
|
});
|
|
11271
11271
|
//#endregion
|
|
11272
11272
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
11273
|
-
const MESSAGE$
|
|
11273
|
+
const MESSAGE$51 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
11274
11274
|
const resolveSettings$39 = (settings) => {
|
|
11275
11275
|
const reactDoctor = settings?.["react-doctor"];
|
|
11276
11276
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -11317,13 +11317,13 @@ const htmlHasLang = defineRule({
|
|
|
11317
11317
|
if (!lang) {
|
|
11318
11318
|
context.report({
|
|
11319
11319
|
node: node.name,
|
|
11320
|
-
message: MESSAGE$
|
|
11320
|
+
message: MESSAGE$51
|
|
11321
11321
|
});
|
|
11322
11322
|
return;
|
|
11323
11323
|
}
|
|
11324
11324
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
11325
11325
|
node: lang,
|
|
11326
|
-
message: MESSAGE$
|
|
11326
|
+
message: MESSAGE$51
|
|
11327
11327
|
});
|
|
11328
11328
|
} };
|
|
11329
11329
|
}
|
|
@@ -11567,7 +11567,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
11567
11567
|
});
|
|
11568
11568
|
//#endregion
|
|
11569
11569
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
11570
|
-
const MESSAGE$
|
|
11570
|
+
const MESSAGE$50 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
11571
11571
|
const evaluateTitleValue = (value) => {
|
|
11572
11572
|
if (!value) return "missing";
|
|
11573
11573
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -11607,14 +11607,14 @@ const iframeHasTitle = defineRule({
|
|
|
11607
11607
|
if (!titleAttr) {
|
|
11608
11608
|
if (hasSpread || tag === "iframe") context.report({
|
|
11609
11609
|
node: node.name,
|
|
11610
|
-
message: MESSAGE$
|
|
11610
|
+
message: MESSAGE$50
|
|
11611
11611
|
});
|
|
11612
11612
|
return;
|
|
11613
11613
|
}
|
|
11614
11614
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
11615
11615
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
11616
11616
|
node: titleAttr,
|
|
11617
|
-
message: MESSAGE$
|
|
11617
|
+
message: MESSAGE$50
|
|
11618
11618
|
});
|
|
11619
11619
|
} })
|
|
11620
11620
|
});
|
|
@@ -11739,7 +11739,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
11739
11739
|
});
|
|
11740
11740
|
//#endregion
|
|
11741
11741
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
11742
|
-
const MESSAGE$
|
|
11742
|
+
const MESSAGE$49 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
11743
11743
|
const DEFAULT_COMPONENTS = ["img"];
|
|
11744
11744
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
11745
11745
|
"image",
|
|
@@ -11804,7 +11804,7 @@ const imgRedundantAlt = defineRule({
|
|
|
11804
11804
|
if (!altAttribute) return;
|
|
11805
11805
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
11806
11806
|
node: altAttribute,
|
|
11807
|
-
message: MESSAGE$
|
|
11807
|
+
message: MESSAGE$49
|
|
11808
11808
|
});
|
|
11809
11809
|
} };
|
|
11810
11810
|
}
|
|
@@ -13574,7 +13574,7 @@ const isStaticRegExpConstruction = (node) => {
|
|
|
13574
13574
|
const flagsArgument = node.arguments?.[1];
|
|
13575
13575
|
return isNodeOfType(node.callee, "Identifier") && node.callee.name === "RegExp" && isStaticPattern(patternArgument) && (flagsArgument === void 0 || isStaticPattern(flagsArgument));
|
|
13576
13576
|
};
|
|
13577
|
-
const MESSAGE$
|
|
13577
|
+
const MESSAGE$48 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
13578
13578
|
const jsHoistRegexp = defineRule({
|
|
13579
13579
|
id: "js-hoist-regexp",
|
|
13580
13580
|
title: "RegExp built inside a loop",
|
|
@@ -13585,13 +13585,13 @@ const jsHoistRegexp = defineRule({
|
|
|
13585
13585
|
NewExpression(node) {
|
|
13586
13586
|
if (isStaticRegExpConstruction(node)) context.report({
|
|
13587
13587
|
node,
|
|
13588
|
-
message: MESSAGE$
|
|
13588
|
+
message: MESSAGE$48
|
|
13589
13589
|
});
|
|
13590
13590
|
},
|
|
13591
13591
|
CallExpression(node) {
|
|
13592
13592
|
if (isStaticRegExpConstruction(node)) context.report({
|
|
13593
13593
|
node,
|
|
13594
|
-
message: MESSAGE$
|
|
13594
|
+
message: MESSAGE$48
|
|
13595
13595
|
});
|
|
13596
13596
|
}
|
|
13597
13597
|
}, { treatIteratorCallbacksAsLoops: true })
|
|
@@ -15673,7 +15673,7 @@ const jsxMaxDepth = defineRule({
|
|
|
15673
15673
|
});
|
|
15674
15674
|
//#endregion
|
|
15675
15675
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
15676
|
-
const MESSAGE$
|
|
15676
|
+
const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
15677
15677
|
const LITERAL_TEXT_TAGS = new Set([
|
|
15678
15678
|
"code",
|
|
15679
15679
|
"pre",
|
|
@@ -15743,7 +15743,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
15743
15743
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
15744
15744
|
context.report({
|
|
15745
15745
|
node,
|
|
15746
|
-
message: MESSAGE$
|
|
15746
|
+
message: MESSAGE$47
|
|
15747
15747
|
});
|
|
15748
15748
|
} })
|
|
15749
15749
|
});
|
|
@@ -15774,7 +15774,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
15774
15774
|
};
|
|
15775
15775
|
//#endregion
|
|
15776
15776
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
15777
|
-
const MESSAGE$
|
|
15777
|
+
const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
15778
15778
|
const CONTEXT_MODULES$1 = [
|
|
15779
15779
|
"react",
|
|
15780
15780
|
"use-context-selector",
|
|
@@ -15872,7 +15872,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
15872
15872
|
if (!isConstructedValue(innerExpression)) continue;
|
|
15873
15873
|
context.report({
|
|
15874
15874
|
node: attribute,
|
|
15875
|
-
message: MESSAGE$
|
|
15875
|
+
message: MESSAGE$46
|
|
15876
15876
|
});
|
|
15877
15877
|
}
|
|
15878
15878
|
}
|
|
@@ -16539,7 +16539,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
16539
16539
|
];
|
|
16540
16540
|
//#endregion
|
|
16541
16541
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
16542
|
-
const MESSAGE$
|
|
16542
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
16543
16543
|
const isDataArrayPropName = (propName) => {
|
|
16544
16544
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
16545
16545
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -16626,7 +16626,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
16626
16626
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
16627
16627
|
context.report({
|
|
16628
16628
|
node,
|
|
16629
|
-
message: MESSAGE$
|
|
16629
|
+
message: MESSAGE$45
|
|
16630
16630
|
});
|
|
16631
16631
|
}
|
|
16632
16632
|
};
|
|
@@ -16884,7 +16884,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
16884
16884
|
]);
|
|
16885
16885
|
//#endregion
|
|
16886
16886
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
16887
|
-
const MESSAGE$
|
|
16887
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
16888
16888
|
const isAccessorPredicateName = (propName) => {
|
|
16889
16889
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
16890
16890
|
if (propName.length <= prefix.length) continue;
|
|
@@ -17090,7 +17090,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
17090
17090
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
17091
17091
|
context.report({
|
|
17092
17092
|
node,
|
|
17093
|
-
message: MESSAGE$
|
|
17093
|
+
message: MESSAGE$44
|
|
17094
17094
|
});
|
|
17095
17095
|
}
|
|
17096
17096
|
};
|
|
@@ -17310,7 +17310,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
17310
17310
|
];
|
|
17311
17311
|
//#endregion
|
|
17312
17312
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
17313
|
-
const MESSAGE$
|
|
17313
|
+
const MESSAGE$43 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
17314
17314
|
const isConfigObjectPropName = (propName) => {
|
|
17315
17315
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
17316
17316
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -17399,7 +17399,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
17399
17399
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
17400
17400
|
context.report({
|
|
17401
17401
|
node,
|
|
17402
|
-
message: MESSAGE$
|
|
17402
|
+
message: MESSAGE$43
|
|
17403
17403
|
});
|
|
17404
17404
|
}
|
|
17405
17405
|
};
|
|
@@ -17407,7 +17407,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
17407
17407
|
});
|
|
17408
17408
|
//#endregion
|
|
17409
17409
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
17410
|
-
const MESSAGE$
|
|
17410
|
+
const MESSAGE$42 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
17411
17411
|
const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*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;
|
|
17412
17412
|
const resolveSettings$29 = (settings) => {
|
|
17413
17413
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -17448,7 +17448,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
17448
17448
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
17449
17449
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
17450
17450
|
node: attribute,
|
|
17451
|
-
message: MESSAGE$
|
|
17451
|
+
message: MESSAGE$42
|
|
17452
17452
|
});
|
|
17453
17453
|
}
|
|
17454
17454
|
} };
|
|
@@ -18072,7 +18072,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
18072
18072
|
});
|
|
18073
18073
|
//#endregion
|
|
18074
18074
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
18075
|
-
const MESSAGE$
|
|
18075
|
+
const MESSAGE$41 = "You can't tell what props reach this element when you spread them.";
|
|
18076
18076
|
const resolveSettings$25 = (settings) => {
|
|
18077
18077
|
const reactDoctor = settings?.["react-doctor"];
|
|
18078
18078
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -18115,7 +18115,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
18115
18115
|
}
|
|
18116
18116
|
context.report({
|
|
18117
18117
|
node: attribute,
|
|
18118
|
-
message: MESSAGE$
|
|
18118
|
+
message: MESSAGE$41
|
|
18119
18119
|
});
|
|
18120
18120
|
didReportInFile = true;
|
|
18121
18121
|
return;
|
|
@@ -18390,7 +18390,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
18390
18390
|
});
|
|
18391
18391
|
//#endregion
|
|
18392
18392
|
//#region src/plugin/rules/a11y/lang.ts
|
|
18393
|
-
const MESSAGE$
|
|
18393
|
+
const MESSAGE$40 = "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`.";
|
|
18394
18394
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
18395
18395
|
"aa",
|
|
18396
18396
|
"ab",
|
|
@@ -18870,7 +18870,7 @@ const lang = defineRule({
|
|
|
18870
18870
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
18871
18871
|
context.report({
|
|
18872
18872
|
node: langAttr,
|
|
18873
|
-
message: MESSAGE$
|
|
18873
|
+
message: MESSAGE$40
|
|
18874
18874
|
});
|
|
18875
18875
|
return;
|
|
18876
18876
|
}
|
|
@@ -18879,7 +18879,7 @@ const lang = defineRule({
|
|
|
18879
18879
|
if (value === null) return;
|
|
18880
18880
|
if (!isValidLangTag(value)) context.report({
|
|
18881
18881
|
node: langAttr,
|
|
18882
|
-
message: MESSAGE$
|
|
18882
|
+
message: MESSAGE$40
|
|
18883
18883
|
});
|
|
18884
18884
|
} })
|
|
18885
18885
|
});
|
|
@@ -18924,7 +18924,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
18924
18924
|
});
|
|
18925
18925
|
//#endregion
|
|
18926
18926
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
18927
|
-
const MESSAGE$
|
|
18927
|
+
const MESSAGE$39 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
18928
18928
|
const DEFAULT_AUDIO = ["audio"];
|
|
18929
18929
|
const DEFAULT_VIDEO = ["video"];
|
|
18930
18930
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -19013,7 +19013,7 @@ const mediaHasCaption = defineRule({
|
|
|
19013
19013
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
19014
19014
|
context.report({
|
|
19015
19015
|
node: node.name,
|
|
19016
|
-
message: MESSAGE$
|
|
19016
|
+
message: MESSAGE$39
|
|
19017
19017
|
});
|
|
19018
19018
|
return;
|
|
19019
19019
|
}
|
|
@@ -19032,7 +19032,7 @@ const mediaHasCaption = defineRule({
|
|
|
19032
19032
|
return kindValue.value.toLowerCase() === "captions";
|
|
19033
19033
|
})) context.report({
|
|
19034
19034
|
node: node.name,
|
|
19035
|
-
message: MESSAGE$
|
|
19035
|
+
message: MESSAGE$39
|
|
19036
19036
|
});
|
|
19037
19037
|
} };
|
|
19038
19038
|
}
|
|
@@ -21302,7 +21302,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
21302
21302
|
});
|
|
21303
21303
|
//#endregion
|
|
21304
21304
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
21305
|
-
const MESSAGE$
|
|
21305
|
+
const MESSAGE$38 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
21306
21306
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
21307
21307
|
const noAccessKey = defineRule({
|
|
21308
21308
|
id: "no-access-key",
|
|
@@ -21321,7 +21321,7 @@ const noAccessKey = defineRule({
|
|
|
21321
21321
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
21322
21322
|
context.report({
|
|
21323
21323
|
node: accessKey,
|
|
21324
|
-
message: MESSAGE$
|
|
21324
|
+
message: MESSAGE$38
|
|
21325
21325
|
});
|
|
21326
21326
|
return;
|
|
21327
21327
|
}
|
|
@@ -21331,7 +21331,7 @@ const noAccessKey = defineRule({
|
|
|
21331
21331
|
if (isUndefinedIdentifier(expression)) return;
|
|
21332
21332
|
context.report({
|
|
21333
21333
|
node: accessKey,
|
|
21334
|
-
message: MESSAGE$
|
|
21334
|
+
message: MESSAGE$38
|
|
21335
21335
|
});
|
|
21336
21336
|
}
|
|
21337
21337
|
} };
|
|
@@ -22199,7 +22199,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
22199
22199
|
});
|
|
22200
22200
|
//#endregion
|
|
22201
22201
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
22202
|
-
const MESSAGE$
|
|
22202
|
+
const MESSAGE$37 = "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.";
|
|
22203
22203
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
22204
22204
|
"button",
|
|
22205
22205
|
"embed",
|
|
@@ -22311,7 +22311,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
22311
22311
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
22312
22312
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
22313
22313
|
node: ariaHidden,
|
|
22314
|
-
message: MESSAGE$
|
|
22314
|
+
message: MESSAGE$37
|
|
22315
22315
|
});
|
|
22316
22316
|
} })
|
|
22317
22317
|
});
|
|
@@ -23192,7 +23192,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
23192
23192
|
};
|
|
23193
23193
|
//#endregion
|
|
23194
23194
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
23195
|
-
const MESSAGE$
|
|
23195
|
+
const MESSAGE$36 = "Your users can see & submit the wrong data when this list reorders.";
|
|
23196
23196
|
const SECOND_INDEX_METHODS = new Set([
|
|
23197
23197
|
"every",
|
|
23198
23198
|
"filter",
|
|
@@ -23311,14 +23311,14 @@ const noArrayIndexKey = defineRule({
|
|
|
23311
23311
|
if (propName !== "key") continue;
|
|
23312
23312
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
23313
23313
|
node: property,
|
|
23314
|
-
message: MESSAGE$
|
|
23314
|
+
message: MESSAGE$36
|
|
23315
23315
|
});
|
|
23316
23316
|
}
|
|
23317
23317
|
} })
|
|
23318
23318
|
});
|
|
23319
23319
|
//#endregion
|
|
23320
23320
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
23321
|
-
const MESSAGE$
|
|
23321
|
+
const MESSAGE$35 = "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.";
|
|
23322
23322
|
const noAsyncEffectCallback = defineRule({
|
|
23323
23323
|
id: "no-async-effect-callback",
|
|
23324
23324
|
title: "Async effect callback",
|
|
@@ -23332,13 +23332,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
23332
23332
|
if (!callback.async) return;
|
|
23333
23333
|
context.report({
|
|
23334
23334
|
node: callback,
|
|
23335
|
-
message: MESSAGE$
|
|
23335
|
+
message: MESSAGE$35
|
|
23336
23336
|
});
|
|
23337
23337
|
} })
|
|
23338
23338
|
});
|
|
23339
23339
|
//#endregion
|
|
23340
23340
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
23341
|
-
const MESSAGE$
|
|
23341
|
+
const MESSAGE$34 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
23342
23342
|
const resolveSettings$21 = (settings) => {
|
|
23343
23343
|
const reactDoctor = settings?.["react-doctor"];
|
|
23344
23344
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -23435,7 +23435,7 @@ const noAutofocus = defineRule({
|
|
|
23435
23435
|
if (isConditionallyRendered(node)) return;
|
|
23436
23436
|
context.report({
|
|
23437
23437
|
node: autoFocusAttribute,
|
|
23438
|
-
message: MESSAGE$
|
|
23438
|
+
message: MESSAGE$34
|
|
23439
23439
|
});
|
|
23440
23440
|
} };
|
|
23441
23441
|
}
|
|
@@ -24472,7 +24472,7 @@ const noChainStateUpdates = defineRule({
|
|
|
24472
24472
|
});
|
|
24473
24473
|
//#endregion
|
|
24474
24474
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
24475
|
-
const MESSAGE$
|
|
24475
|
+
const MESSAGE$33 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
24476
24476
|
const noChildrenProp = defineRule({
|
|
24477
24477
|
id: "no-children-prop",
|
|
24478
24478
|
title: "Children passed as a prop",
|
|
@@ -24484,7 +24484,7 @@ const noChildrenProp = defineRule({
|
|
|
24484
24484
|
if (node.name.name !== "children") return;
|
|
24485
24485
|
context.report({
|
|
24486
24486
|
node: node.name,
|
|
24487
|
-
message: MESSAGE$
|
|
24487
|
+
message: MESSAGE$33
|
|
24488
24488
|
});
|
|
24489
24489
|
},
|
|
24490
24490
|
CallExpression(node) {
|
|
@@ -24497,7 +24497,7 @@ const noChildrenProp = defineRule({
|
|
|
24497
24497
|
const propertyKey = property.key;
|
|
24498
24498
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
24499
24499
|
node: propertyKey,
|
|
24500
|
-
message: MESSAGE$
|
|
24500
|
+
message: MESSAGE$33
|
|
24501
24501
|
});
|
|
24502
24502
|
}
|
|
24503
24503
|
}
|
|
@@ -24505,7 +24505,7 @@ const noChildrenProp = defineRule({
|
|
|
24505
24505
|
});
|
|
24506
24506
|
//#endregion
|
|
24507
24507
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
24508
|
-
const MESSAGE$
|
|
24508
|
+
const MESSAGE$32 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
24509
24509
|
const noCloneElement = defineRule({
|
|
24510
24510
|
id: "no-clone-element",
|
|
24511
24511
|
title: "cloneElement makes child props fragile",
|
|
@@ -24518,7 +24518,7 @@ const noCloneElement = defineRule({
|
|
|
24518
24518
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
24519
24519
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
24520
24520
|
node: callee,
|
|
24521
|
-
message: MESSAGE$
|
|
24521
|
+
message: MESSAGE$32
|
|
24522
24522
|
});
|
|
24523
24523
|
return;
|
|
24524
24524
|
}
|
|
@@ -24531,14 +24531,14 @@ const noCloneElement = defineRule({
|
|
|
24531
24531
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
24532
24532
|
context.report({
|
|
24533
24533
|
node: callee,
|
|
24534
|
-
message: MESSAGE$
|
|
24534
|
+
message: MESSAGE$32
|
|
24535
24535
|
});
|
|
24536
24536
|
}
|
|
24537
24537
|
} })
|
|
24538
24538
|
});
|
|
24539
24539
|
//#endregion
|
|
24540
24540
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
24541
|
-
const MESSAGE$
|
|
24541
|
+
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
24542
24542
|
const CONTEXT_MODULES = [
|
|
24543
24543
|
"react",
|
|
24544
24544
|
"use-context-selector",
|
|
@@ -24574,13 +24574,13 @@ const noCreateContextInRender = defineRule({
|
|
|
24574
24574
|
if (!componentOrHookName) return;
|
|
24575
24575
|
context.report({
|
|
24576
24576
|
node,
|
|
24577
|
-
message: `${MESSAGE$
|
|
24577
|
+
message: `${MESSAGE$31} (called inside "${componentOrHookName}")`
|
|
24578
24578
|
});
|
|
24579
24579
|
} })
|
|
24580
24580
|
});
|
|
24581
24581
|
//#endregion
|
|
24582
24582
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
24583
|
-
const MESSAGE$
|
|
24583
|
+
const MESSAGE$30 = "`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.";
|
|
24584
24584
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
24585
24585
|
const parent = functionNode.parent;
|
|
24586
24586
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -24610,7 +24610,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
24610
24610
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
|
|
24611
24611
|
context.report({
|
|
24612
24612
|
node,
|
|
24613
|
-
message: MESSAGE$
|
|
24613
|
+
message: MESSAGE$30
|
|
24614
24614
|
});
|
|
24615
24615
|
} })
|
|
24616
24616
|
});
|
|
@@ -24750,7 +24750,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
24750
24750
|
});
|
|
24751
24751
|
//#endregion
|
|
24752
24752
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
24753
|
-
const MESSAGE$
|
|
24753
|
+
const MESSAGE$29 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
24754
24754
|
const noDanger = defineRule({
|
|
24755
24755
|
id: "no-danger",
|
|
24756
24756
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -24764,7 +24764,7 @@ const noDanger = defineRule({
|
|
|
24764
24764
|
if (!propAttribute) return;
|
|
24765
24765
|
context.report({
|
|
24766
24766
|
node: propAttribute.name,
|
|
24767
|
-
message: MESSAGE$
|
|
24767
|
+
message: MESSAGE$29
|
|
24768
24768
|
});
|
|
24769
24769
|
},
|
|
24770
24770
|
CallExpression(node) {
|
|
@@ -24776,7 +24776,7 @@ const noDanger = defineRule({
|
|
|
24776
24776
|
const propertyKey = property.key;
|
|
24777
24777
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
24778
24778
|
node: propertyKey,
|
|
24779
|
-
message: MESSAGE$
|
|
24779
|
+
message: MESSAGE$29
|
|
24780
24780
|
});
|
|
24781
24781
|
}
|
|
24782
24782
|
}
|
|
@@ -24799,7 +24799,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
24799
24799
|
};
|
|
24800
24800
|
//#endregion
|
|
24801
24801
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
24802
|
-
const MESSAGE$
|
|
24802
|
+
const MESSAGE$28 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
24803
24803
|
const mergePropsShape = (target, source) => {
|
|
24804
24804
|
target.hasDangerously ||= source.hasDangerously;
|
|
24805
24805
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -24874,7 +24874,7 @@ const noDangerWithChildren = defineRule({
|
|
|
24874
24874
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
24875
24875
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
24876
24876
|
node: opening,
|
|
24877
|
-
message: MESSAGE$
|
|
24877
|
+
message: MESSAGE$28
|
|
24878
24878
|
});
|
|
24879
24879
|
},
|
|
24880
24880
|
CallExpression(node) {
|
|
@@ -24887,7 +24887,7 @@ const noDangerWithChildren = defineRule({
|
|
|
24887
24887
|
const positionalChildren = node.arguments.slice(2);
|
|
24888
24888
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
24889
24889
|
node,
|
|
24890
|
-
message: MESSAGE$
|
|
24890
|
+
message: MESSAGE$28
|
|
24891
24891
|
});
|
|
24892
24892
|
}
|
|
24893
24893
|
})
|
|
@@ -26114,7 +26114,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
26114
26114
|
//#endregion
|
|
26115
26115
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
26116
26116
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
26117
|
-
const MESSAGE$
|
|
26117
|
+
const MESSAGE$27 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
26118
26118
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
26119
26119
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
26120
26120
|
let ancestor = setStateCall.parent;
|
|
@@ -26242,7 +26242,7 @@ const noDidMountSetState = defineRule({
|
|
|
26242
26242
|
}
|
|
26243
26243
|
context.report({
|
|
26244
26244
|
node: node.callee,
|
|
26245
|
-
message: MESSAGE$
|
|
26245
|
+
message: MESSAGE$27
|
|
26246
26246
|
});
|
|
26247
26247
|
} };
|
|
26248
26248
|
}
|
|
@@ -26250,7 +26250,7 @@ const noDidMountSetState = defineRule({
|
|
|
26250
26250
|
//#endregion
|
|
26251
26251
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
26252
26252
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
26253
|
-
const MESSAGE$
|
|
26253
|
+
const MESSAGE$26 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
26254
26254
|
const EQUALITY_OPERATORS = new Set([
|
|
26255
26255
|
"==",
|
|
26256
26256
|
"===",
|
|
@@ -26362,7 +26362,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
26362
26362
|
if (isInsideDiffGuard(node)) return;
|
|
26363
26363
|
context.report({
|
|
26364
26364
|
node: node.callee,
|
|
26365
|
-
message: MESSAGE$
|
|
26365
|
+
message: MESSAGE$26
|
|
26366
26366
|
});
|
|
26367
26367
|
} };
|
|
26368
26368
|
}
|
|
@@ -26385,7 +26385,7 @@ const isStateMemberExpression = (node) => {
|
|
|
26385
26385
|
};
|
|
26386
26386
|
//#endregion
|
|
26387
26387
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
26388
|
-
const MESSAGE$
|
|
26388
|
+
const MESSAGE$25 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
|
|
26389
26389
|
const shouldIgnoreMutation = (node) => {
|
|
26390
26390
|
let isConstructor = false;
|
|
26391
26391
|
let isInsideCallExpression = false;
|
|
@@ -26407,7 +26407,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
26407
26407
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
26408
26408
|
context.report({
|
|
26409
26409
|
node: reportNode,
|
|
26410
|
-
message: MESSAGE$
|
|
26410
|
+
message: MESSAGE$25
|
|
26411
26411
|
});
|
|
26412
26412
|
};
|
|
26413
26413
|
const noDirectMutationState = defineRule({
|
|
@@ -26753,7 +26753,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
26753
26753
|
});
|
|
26754
26754
|
//#endregion
|
|
26755
26755
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
26756
|
-
const MESSAGE$
|
|
26756
|
+
const MESSAGE$24 = "`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.";
|
|
26757
26757
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
26758
26758
|
const noDocumentWrite = defineRule({
|
|
26759
26759
|
id: "no-document-write",
|
|
@@ -26768,7 +26768,7 @@ const noDocumentWrite = defineRule({
|
|
|
26768
26768
|
if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
|
|
26769
26769
|
context.report({
|
|
26770
26770
|
node,
|
|
26771
|
-
message: MESSAGE$
|
|
26771
|
+
message: MESSAGE$24
|
|
26772
26772
|
});
|
|
26773
26773
|
} })
|
|
26774
26774
|
});
|
|
@@ -28903,7 +28903,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
28903
28903
|
"ReactDOM",
|
|
28904
28904
|
"ReactDom"
|
|
28905
28905
|
]);
|
|
28906
|
-
const MESSAGE$
|
|
28906
|
+
const MESSAGE$23 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
28907
28907
|
const noFindDomNode = defineRule({
|
|
28908
28908
|
id: "no-find-dom-node",
|
|
28909
28909
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -28914,7 +28914,7 @@ const noFindDomNode = defineRule({
|
|
|
28914
28914
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
28915
28915
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
28916
28916
|
node: callee,
|
|
28917
|
-
message: MESSAGE$
|
|
28917
|
+
message: MESSAGE$23
|
|
28918
28918
|
});
|
|
28919
28919
|
return;
|
|
28920
28920
|
}
|
|
@@ -28925,7 +28925,7 @@ const noFindDomNode = defineRule({
|
|
|
28925
28925
|
if (callee.property.name !== "findDOMNode") return;
|
|
28926
28926
|
context.report({
|
|
28927
28927
|
node: callee.property,
|
|
28928
|
-
message: MESSAGE$
|
|
28928
|
+
message: MESSAGE$23
|
|
28929
28929
|
});
|
|
28930
28930
|
}
|
|
28931
28931
|
} })
|
|
@@ -29470,7 +29470,7 @@ const noGrayOnColoredBackground = defineRule({
|
|
|
29470
29470
|
});
|
|
29471
29471
|
//#endregion
|
|
29472
29472
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
29473
|
-
const MESSAGE$
|
|
29473
|
+
const MESSAGE$22 = "`<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.";
|
|
29474
29474
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
29475
29475
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
29476
29476
|
title: "Lazy image with high fetchPriority",
|
|
@@ -29484,11 +29484,158 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
29484
29484
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
29485
29485
|
context.report({
|
|
29486
29486
|
node: node.name,
|
|
29487
|
-
message: MESSAGE$
|
|
29487
|
+
message: MESSAGE$22
|
|
29488
29488
|
});
|
|
29489
29489
|
} })
|
|
29490
29490
|
});
|
|
29491
29491
|
//#endregion
|
|
29492
|
+
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
29493
|
+
const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
29494
|
+
const REACT_USE_REF_OPTIONS = {
|
|
29495
|
+
allowGlobalReactNamespace: false,
|
|
29496
|
+
allowUnboundBareCalls: false
|
|
29497
|
+
};
|
|
29498
|
+
const hasVisibleBindingNamed = (node, bindingName, scopes) => {
|
|
29499
|
+
let scope = scopes.scopeFor(node);
|
|
29500
|
+
while (true) {
|
|
29501
|
+
if (scope.symbolsByName.has(bindingName)) return true;
|
|
29502
|
+
if (!scope.parent) return false;
|
|
29503
|
+
scope = scope.parent;
|
|
29504
|
+
}
|
|
29505
|
+
};
|
|
29506
|
+
const hasEnclosingTypeParameterNamed = (node, typeParameterName) => {
|
|
29507
|
+
let ancestor = node.parent;
|
|
29508
|
+
while (ancestor) {
|
|
29509
|
+
if ("typeParameters" in ancestor) {
|
|
29510
|
+
const typeParameters = ancestor.typeParameters;
|
|
29511
|
+
if (typeParameters && isNodeOfType(typeParameters, "TSTypeParameterDeclaration") && typeParameters.params.some((typeParameter) => isNodeOfType(typeParameter, "TSTypeParameter") && isNodeOfType(typeParameter.name, "Identifier") && typeParameter.name.name === typeParameterName)) return true;
|
|
29512
|
+
}
|
|
29513
|
+
ancestor = ancestor.parent;
|
|
29514
|
+
}
|
|
29515
|
+
return false;
|
|
29516
|
+
};
|
|
29517
|
+
const isHtmlInputElementType = (typeNode, scopes) => {
|
|
29518
|
+
if (isNodeOfType(typeNode, "TSTypeReference")) return isNodeOfType(typeNode.typeName, "Identifier") && typeNode.typeName.name === "HTMLInputElement" && !hasVisibleBindingNamed(typeNode, "HTMLInputElement", scopes) && !hasEnclosingTypeParameterNamed(typeNode, "HTMLInputElement");
|
|
29519
|
+
if (!isNodeOfType(typeNode, "TSUnionType")) return false;
|
|
29520
|
+
let hasHtmlInputElementMember = false;
|
|
29521
|
+
for (const unionMember of typeNode.types) {
|
|
29522
|
+
if (isNodeOfType(unionMember, "TSNullKeyword") || isNodeOfType(unionMember, "TSUndefinedKeyword")) continue;
|
|
29523
|
+
if (!isHtmlInputElementType(unionMember, scopes)) return false;
|
|
29524
|
+
hasHtmlInputElementMember = true;
|
|
29525
|
+
}
|
|
29526
|
+
return hasHtmlInputElementMember;
|
|
29527
|
+
};
|
|
29528
|
+
const getBindingTypeAnnotation = (symbol) => {
|
|
29529
|
+
if (!isNodeOfType(symbol.bindingIdentifier, "Identifier")) return null;
|
|
29530
|
+
const annotation = symbol.bindingIdentifier.typeAnnotation;
|
|
29531
|
+
if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
|
|
29532
|
+
return annotation.typeAnnotation;
|
|
29533
|
+
};
|
|
29534
|
+
const getDirectConstInitializer = (symbol) => {
|
|
29535
|
+
if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
29536
|
+
return symbol.initializer;
|
|
29537
|
+
};
|
|
29538
|
+
const hasTypedHtmlInputRefOrigin = (rawExpression, scopes) => {
|
|
29539
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
29540
|
+
let expression = stripParenExpression(rawExpression);
|
|
29541
|
+
while (isNodeOfType(expression, "Identifier")) {
|
|
29542
|
+
const symbol = scopes.symbolFor(expression);
|
|
29543
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
29544
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
29545
|
+
if (!initializer) return false;
|
|
29546
|
+
visitedSymbolIds.add(symbol.id);
|
|
29547
|
+
expression = stripParenExpression(initializer);
|
|
29548
|
+
}
|
|
29549
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
29550
|
+
if (!isReactApiCall(expression, "useRef", scopes, REACT_USE_REF_OPTIONS)) return false;
|
|
29551
|
+
if (!isNodeOfType(expression.typeArguments, "TSTypeParameterInstantiation")) return false;
|
|
29552
|
+
const typeArgument = expression.typeArguments.params[0];
|
|
29553
|
+
return Boolean(typeArgument && isHtmlInputElementType(typeArgument, scopes));
|
|
29554
|
+
};
|
|
29555
|
+
const isProvenHtmlInputElement = (rawExpression, scopes) => {
|
|
29556
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
29557
|
+
let expression = stripParenExpression(rawExpression);
|
|
29558
|
+
while (true) {
|
|
29559
|
+
if (isNodeOfType(expression, "MemberExpression") && !expression.computed && isNodeOfType(expression.property, "Identifier") && expression.property.name === "current") return hasTypedHtmlInputRefOrigin(expression.object, scopes);
|
|
29560
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
29561
|
+
const symbol = scopes.symbolFor(expression);
|
|
29562
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
29563
|
+
const typeAnnotation = getBindingTypeAnnotation(symbol);
|
|
29564
|
+
if (typeAnnotation && isHtmlInputElementType(typeAnnotation, scopes)) return true;
|
|
29565
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
29566
|
+
if (!initializer) return false;
|
|
29567
|
+
visitedSymbolIds.add(symbol.id);
|
|
29568
|
+
expression = stripParenExpression(initializer);
|
|
29569
|
+
}
|
|
29570
|
+
};
|
|
29571
|
+
const getIndeterminateAttributeReceiver = (node) => {
|
|
29572
|
+
const callee = stripParenExpression(node.callee);
|
|
29573
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setAttribute" && callee.property.name !== "toggleAttribute") return null;
|
|
29574
|
+
const attributeName = node.arguments[0];
|
|
29575
|
+
if (!isNodeOfType(attributeName, "Literal") || attributeName.value !== "indeterminate") return null;
|
|
29576
|
+
if (callee.property.name === "setAttribute") {
|
|
29577
|
+
const attributeValue = node.arguments[1];
|
|
29578
|
+
return attributeValue && !isNodeOfType(attributeValue, "SpreadElement") ? callee.object : null;
|
|
29579
|
+
}
|
|
29580
|
+
if (node.arguments.length === 1) return callee.object;
|
|
29581
|
+
const forceArgument = node.arguments[1];
|
|
29582
|
+
if (!forceArgument || isNodeOfType(forceArgument, "SpreadElement")) return null;
|
|
29583
|
+
const forceExpression = stripParenExpression(forceArgument);
|
|
29584
|
+
if (!isNodeOfType(forceExpression, "Literal") || forceExpression.value !== true) return null;
|
|
29585
|
+
return callee.object;
|
|
29586
|
+
};
|
|
29587
|
+
const noIndeterminateAttribute = defineRule({
|
|
29588
|
+
id: "no-indeterminate-attribute",
|
|
29589
|
+
title: "Indeterminate checkbox state set as an attribute",
|
|
29590
|
+
severity: "warn",
|
|
29591
|
+
recommendation: "Assign the checkbox element's `indeterminate` DOM property, usually through a ref, because the HTML attribute does not control its visual state.",
|
|
29592
|
+
create: (context) => {
|
|
29593
|
+
if (classifyReactNativeFileTarget(context) === "react-native") return {};
|
|
29594
|
+
return {
|
|
29595
|
+
JSXOpeningElement(node) {
|
|
29596
|
+
if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
|
|
29597
|
+
let typeAttribute = null;
|
|
29598
|
+
let typeAttributeIndex = null;
|
|
29599
|
+
let indeterminateAttribute = null;
|
|
29600
|
+
let indeterminateAttributeIndex = null;
|
|
29601
|
+
let lastSpreadIndex = null;
|
|
29602
|
+
for (let attributeIndex = 0; attributeIndex < node.attributes.length; attributeIndex++) {
|
|
29603
|
+
const attribute = node.attributes[attributeIndex];
|
|
29604
|
+
if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
|
|
29605
|
+
lastSpreadIndex = attributeIndex;
|
|
29606
|
+
continue;
|
|
29607
|
+
}
|
|
29608
|
+
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) continue;
|
|
29609
|
+
if (attribute.name.name === "type") {
|
|
29610
|
+
typeAttribute = attribute;
|
|
29611
|
+
typeAttributeIndex = attributeIndex;
|
|
29612
|
+
} else if (attribute.name.name === "indeterminate") {
|
|
29613
|
+
indeterminateAttribute = attribute;
|
|
29614
|
+
indeterminateAttributeIndex = attributeIndex;
|
|
29615
|
+
}
|
|
29616
|
+
}
|
|
29617
|
+
if (!typeAttribute || typeAttributeIndex === null || !indeterminateAttribute || indeterminateAttributeIndex === null) return;
|
|
29618
|
+
if (lastSpreadIndex !== null && (lastSpreadIndex > typeAttributeIndex || lastSpreadIndex > indeterminateAttributeIndex)) return;
|
|
29619
|
+
const inputTypeValues = getJsxPropStaticStringValues(typeAttribute, context.scopes);
|
|
29620
|
+
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
29621
|
+
context.report({
|
|
29622
|
+
node: indeterminateAttribute,
|
|
29623
|
+
message: MESSAGE$21
|
|
29624
|
+
});
|
|
29625
|
+
},
|
|
29626
|
+
CallExpression(node) {
|
|
29627
|
+
const receiver = getIndeterminateAttributeReceiver(node);
|
|
29628
|
+
if (!receiver) return;
|
|
29629
|
+
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
29630
|
+
context.report({
|
|
29631
|
+
node,
|
|
29632
|
+
message: MESSAGE$21
|
|
29633
|
+
});
|
|
29634
|
+
}
|
|
29635
|
+
};
|
|
29636
|
+
}
|
|
29637
|
+
});
|
|
29638
|
+
//#endregion
|
|
29492
29639
|
//#region src/plugin/utils/contains-non-deterministic-source.ts
|
|
29493
29640
|
const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
|
|
29494
29641
|
"Math.random",
|
|
@@ -31013,6 +31160,64 @@ const noManyBooleanProps = defineRule({
|
|
|
31013
31160
|
}
|
|
31014
31161
|
});
|
|
31015
31162
|
//#endregion
|
|
31163
|
+
//#region src/plugin/utils/is-global-match-media-call.ts
|
|
31164
|
+
const isGlobalMatchMediaCall = (node, scopes) => {
|
|
31165
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
31166
|
+
const callee = stripParenExpression(node.callee);
|
|
31167
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
|
|
31168
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
|
|
31169
|
+
const receiver = stripParenExpression(callee.object);
|
|
31170
|
+
return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
|
|
31171
|
+
};
|
|
31172
|
+
//#endregion
|
|
31173
|
+
//#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
|
|
31174
|
+
const REACT_USE_STATE_OPTIONS = {
|
|
31175
|
+
allowGlobalReactNamespace: true,
|
|
31176
|
+
allowUnboundBareCalls: false
|
|
31177
|
+
};
|
|
31178
|
+
const isReactUseStateCall = (node, context) => isReactApiCall(node, "useState", context.scopes, REACT_USE_STATE_OPTIONS);
|
|
31179
|
+
const findDirectMatchMediaCall = (root, context) => {
|
|
31180
|
+
let matchMediaCall = null;
|
|
31181
|
+
walkAst(root, (visitedNode) => {
|
|
31182
|
+
if (matchMediaCall) return false;
|
|
31183
|
+
if (isFunctionLike$1(visitedNode)) return false;
|
|
31184
|
+
if (isGlobalMatchMediaCall(visitedNode, context.scopes)) {
|
|
31185
|
+
matchMediaCall = visitedNode;
|
|
31186
|
+
return false;
|
|
31187
|
+
}
|
|
31188
|
+
});
|
|
31189
|
+
return matchMediaCall;
|
|
31190
|
+
};
|
|
31191
|
+
const findMatchMediaCallDuringInitialization = (initializer, context) => {
|
|
31192
|
+
const rootInitializer = stripParenExpression(initializer);
|
|
31193
|
+
if (!isFunctionLike$1(rootInitializer)) return findDirectMatchMediaCall(rootInitializer, context);
|
|
31194
|
+
if (rootInitializer.async || rootInitializer.generator) return null;
|
|
31195
|
+
return findDirectMatchMediaCall(rootInitializer.body, context);
|
|
31196
|
+
};
|
|
31197
|
+
const noMatchMediaInStateInitializer = defineRule({
|
|
31198
|
+
id: "no-match-media-in-state-initializer",
|
|
31199
|
+
title: "matchMedia in state initializer",
|
|
31200
|
+
severity: "warn",
|
|
31201
|
+
category: "Correctness",
|
|
31202
|
+
disabledWhen: ["vite", "cra"],
|
|
31203
|
+
recommendation: "Prefer CSS media queries for layout, or subscribe with `useSyncExternalStore` and provide a stable server snapshot.",
|
|
31204
|
+
create: (context) => {
|
|
31205
|
+
if (isTestlikeFilename(context.filename)) return {};
|
|
31206
|
+
if (classifyReactNativeFileTarget(context) === "react-native") return {};
|
|
31207
|
+
return { CallExpression(node) {
|
|
31208
|
+
if (!isReactUseStateCall(node, context)) return;
|
|
31209
|
+
const initializer = node.arguments?.[0];
|
|
31210
|
+
if (!initializer || initializer.type === "SpreadElement") return;
|
|
31211
|
+
const matchMediaCall = findMatchMediaCallDuringInitialization(initializer, context);
|
|
31212
|
+
if (!matchMediaCall) return;
|
|
31213
|
+
context.report({
|
|
31214
|
+
node: matchMediaCall,
|
|
31215
|
+
message: "`matchMedia()` in a useState initializer can cause an SSR crash or seed different server and hydration state. Prefer CSS media queries for layout, or use `useSyncExternalStore` with a stable server snapshot."
|
|
31216
|
+
});
|
|
31217
|
+
} };
|
|
31218
|
+
}
|
|
31219
|
+
});
|
|
31220
|
+
//#endregion
|
|
31016
31221
|
//#region src/plugin/rules/state-and-effects/no-mirror-prop-effect.ts
|
|
31017
31222
|
const getPropRootName = (expression, propNames) => {
|
|
31018
31223
|
const rootName = getRootIdentifierName(expression, { followCallChains: true });
|
|
@@ -54903,6 +55108,17 @@ const reactDoctorRules = [
|
|
|
54903
55108
|
requires: [...new Set(["react", ...noImgLazyWithHighFetchpriority.requires ?? []])]
|
|
54904
55109
|
}
|
|
54905
55110
|
},
|
|
55111
|
+
{
|
|
55112
|
+
key: "react-doctor/no-indeterminate-attribute",
|
|
55113
|
+
id: "no-indeterminate-attribute",
|
|
55114
|
+
source: "react-doctor",
|
|
55115
|
+
originallyExternal: false,
|
|
55116
|
+
rule: {
|
|
55117
|
+
...noIndeterminateAttribute,
|
|
55118
|
+
framework: "global",
|
|
55119
|
+
category: "Bugs"
|
|
55120
|
+
}
|
|
55121
|
+
},
|
|
54906
55122
|
{
|
|
54907
55123
|
key: "react-doctor/no-initialize-state",
|
|
54908
55124
|
id: "no-initialize-state",
|
|
@@ -55097,6 +55313,18 @@ const reactDoctorRules = [
|
|
|
55097
55313
|
category: "Maintainability"
|
|
55098
55314
|
}
|
|
55099
55315
|
},
|
|
55316
|
+
{
|
|
55317
|
+
key: "react-doctor/no-match-media-in-state-initializer",
|
|
55318
|
+
id: "no-match-media-in-state-initializer",
|
|
55319
|
+
source: "react-doctor",
|
|
55320
|
+
originallyExternal: false,
|
|
55321
|
+
rule: {
|
|
55322
|
+
...noMatchMediaInStateInitializer,
|
|
55323
|
+
framework: "global",
|
|
55324
|
+
category: "Bugs",
|
|
55325
|
+
requires: [...new Set(["react", ...noMatchMediaInStateInitializer.requires ?? []])]
|
|
55326
|
+
}
|
|
55327
|
+
},
|
|
55100
55328
|
{
|
|
55101
55329
|
key: "react-doctor/no-mirror-prop-effect",
|
|
55102
55330
|
id: "no-mirror-prop-effect",
|
|
@@ -57963,7 +58191,9 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
57963
58191
|
"nextjs-no-use-search-params-without-suspense",
|
|
57964
58192
|
"no-dynamic-import-path",
|
|
57965
58193
|
"no-full-lodash-import",
|
|
58194
|
+
"no-indeterminate-attribute",
|
|
57966
58195
|
"no-locale-format-in-render",
|
|
58196
|
+
"no-match-media-in-state-initializer",
|
|
57967
58197
|
"no-mutating-reducer-state",
|
|
57968
58198
|
"prefer-dynamic-import",
|
|
57969
58199
|
"rendering-hydration-mismatch-time",
|
|
@@ -58098,7 +58328,9 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
|
|
|
58098
58328
|
["nextjs-no-use-search-params-without-suspense", collectNextjsSearchParamsDependencies],
|
|
58099
58329
|
["no-dynamic-import-path", collectNearestManifestDependencies],
|
|
58100
58330
|
["no-full-lodash-import", collectNearestManifestDependencies],
|
|
58331
|
+
["no-indeterminate-attribute", collectNearestManifestDependencies],
|
|
58101
58332
|
["no-locale-format-in-render", collectNearestManifestDependencies],
|
|
58333
|
+
["no-match-media-in-state-initializer", collectNearestManifestDependencies],
|
|
58102
58334
|
["no-mutating-reducer-state", collectMutatingReducerDependencies],
|
|
58103
58335
|
["prefer-dynamic-import", collectNearestManifestDependencies],
|
|
58104
58336
|
["rendering-hydration-mismatch-time", collectNearestManifestDependencies],
|