oxlint-plugin-react-doctor 0.7.7-dev.ec144d9 → 0.7.7-dev.fbc804e
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 +46 -0
- package/dist/index.js +578 -331
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,13 @@ import { analyze } from "eslint-scope";
|
|
|
7
7
|
//#region src/plugin/utils/is-node-of-type.ts
|
|
8
8
|
const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
|
|
9
9
|
//#endregion
|
|
10
|
+
//#region src/plugin/utils/is-type-only-import.ts
|
|
11
|
+
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
12
|
+
if (!specifiers || specifiers.length === 0) return false;
|
|
13
|
+
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
14
|
+
};
|
|
15
|
+
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
16
|
+
//#endregion
|
|
10
17
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
11
18
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
12
19
|
"solid-js",
|
|
@@ -21,17 +28,33 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
|
21
28
|
"vidode"
|
|
22
29
|
]);
|
|
23
30
|
const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
|
|
31
|
+
const REACT_JSX_DIALECT_PACKAGE_PREFIXES = [
|
|
32
|
+
"react",
|
|
33
|
+
"react-dom",
|
|
34
|
+
"preact"
|
|
35
|
+
];
|
|
24
36
|
const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
|
|
25
|
-
const
|
|
37
|
+
const collectJsxRuntimeImports = (program) => {
|
|
38
|
+
let hasNonReactRuntime = false;
|
|
39
|
+
let hasReactRuntime = false;
|
|
26
40
|
for (const statement of program.body) {
|
|
27
41
|
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
28
|
-
const
|
|
42
|
+
const importDeclaration = statement;
|
|
43
|
+
if (isTypeOnlyImport(importDeclaration)) continue;
|
|
44
|
+
const source = importDeclaration.source;
|
|
29
45
|
const value = source && typeof source.value === "string" ? source.value : null;
|
|
30
46
|
if (!value) continue;
|
|
31
|
-
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value))
|
|
32
|
-
if (startsWithAny(value,
|
|
47
|
+
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value) || startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasNonReactRuntime = true;
|
|
48
|
+
if (startsWithAny(value, REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasReactRuntime = true;
|
|
33
49
|
}
|
|
34
|
-
return
|
|
50
|
+
return {
|
|
51
|
+
hasNonReactRuntime,
|
|
52
|
+
hasReactRuntime
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const fileImportsNonReactJsxDialect = (program) => {
|
|
56
|
+
const runtimeImports = collectJsxRuntimeImports(program);
|
|
57
|
+
return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
35
58
|
};
|
|
36
59
|
const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
|
|
37
60
|
for (const attribute of openingNode.attributes) {
|
|
@@ -244,6 +267,7 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
|
|
|
244
267
|
const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
245
268
|
const innerVisitors = create(context);
|
|
246
269
|
let fileIsNonReactJsx = false;
|
|
270
|
+
let fileImportsReactRuntime = false;
|
|
247
271
|
const wrappedVisitors = {};
|
|
248
272
|
for (const [key, visitor] of Object.entries(innerVisitors)) {
|
|
249
273
|
if (typeof visitor !== "function") {
|
|
@@ -256,14 +280,16 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
256
280
|
}
|
|
257
281
|
if (key === "Program") {
|
|
258
282
|
wrappedVisitors.Program = (node) => {
|
|
259
|
-
|
|
283
|
+
const runtimeImports = collectJsxRuntimeImports(node);
|
|
284
|
+
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
285
|
+
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
260
286
|
visitor(node);
|
|
261
287
|
};
|
|
262
288
|
continue;
|
|
263
289
|
}
|
|
264
290
|
if (key === "JSXOpeningElement") {
|
|
265
291
|
wrappedVisitors.JSXOpeningElement = (node) => {
|
|
266
|
-
if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
292
|
+
if (!fileImportsReactRuntime && !fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
267
293
|
if (fileIsNonReactJsx) return;
|
|
268
294
|
visitor(node);
|
|
269
295
|
};
|
|
@@ -275,7 +301,9 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
275
301
|
};
|
|
276
302
|
}
|
|
277
303
|
if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
|
|
278
|
-
|
|
304
|
+
const runtimeImports = collectJsxRuntimeImports(node);
|
|
305
|
+
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
306
|
+
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
279
307
|
};
|
|
280
308
|
return wrappedVisitors;
|
|
281
309
|
});
|
|
@@ -2312,7 +2340,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2312
2340
|
});
|
|
2313
2341
|
//#endregion
|
|
2314
2342
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
2315
|
-
const MESSAGE$
|
|
2343
|
+
const MESSAGE$62 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
2316
2344
|
const isTransComponentsTemplate = (node) => {
|
|
2317
2345
|
let current = node.parent;
|
|
2318
2346
|
while (current) {
|
|
@@ -2348,7 +2376,7 @@ const anchorHasContent = defineRule({
|
|
|
2348
2376
|
if (isTransComponentsTemplate(node)) return;
|
|
2349
2377
|
context.report({
|
|
2350
2378
|
node: opening.name,
|
|
2351
|
-
message: MESSAGE$
|
|
2379
|
+
message: MESSAGE$62
|
|
2352
2380
|
});
|
|
2353
2381
|
} };
|
|
2354
2382
|
}
|
|
@@ -2780,7 +2808,7 @@ const parseJsxValue = (value) => {
|
|
|
2780
2808
|
};
|
|
2781
2809
|
//#endregion
|
|
2782
2810
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2783
|
-
const MESSAGE$
|
|
2811
|
+
const MESSAGE$61 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2784
2812
|
const mayBeContentEditable = (node) => {
|
|
2785
2813
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
|
|
2786
2814
|
if (!attribute) return false;
|
|
@@ -2811,7 +2839,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2811
2839
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2812
2840
|
context.report({
|
|
2813
2841
|
node: node.name,
|
|
2814
|
-
message: MESSAGE$
|
|
2842
|
+
message: MESSAGE$61
|
|
2815
2843
|
});
|
|
2816
2844
|
return;
|
|
2817
2845
|
}
|
|
@@ -2819,7 +2847,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2819
2847
|
if (mayBeContentEditable(node)) return;
|
|
2820
2848
|
context.report({
|
|
2821
2849
|
node: node.name,
|
|
2822
|
-
message: MESSAGE$
|
|
2850
|
+
message: MESSAGE$61
|
|
2823
2851
|
});
|
|
2824
2852
|
} })
|
|
2825
2853
|
});
|
|
@@ -5224,7 +5252,7 @@ const asyncParallel = defineRule({
|
|
|
5224
5252
|
});
|
|
5225
5253
|
//#endregion
|
|
5226
5254
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
5227
|
-
const MESSAGE$
|
|
5255
|
+
const MESSAGE$60 = "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.";
|
|
5228
5256
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
5229
5257
|
const STORAGE_GLOBALS = new Set([
|
|
5230
5258
|
"window",
|
|
@@ -5289,7 +5317,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5289
5317
|
if (keyString === null || !isAuthCredentialKey(keyString)) return;
|
|
5290
5318
|
context.report({
|
|
5291
5319
|
node,
|
|
5292
|
-
message: MESSAGE$
|
|
5320
|
+
message: MESSAGE$60
|
|
5293
5321
|
});
|
|
5294
5322
|
},
|
|
5295
5323
|
AssignmentExpression(node) {
|
|
@@ -5300,7 +5328,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5300
5328
|
if (!propertyName || !isAuthCredentialKey(propertyName)) return;
|
|
5301
5329
|
context.report({
|
|
5302
5330
|
node: target,
|
|
5303
|
-
message: MESSAGE$
|
|
5331
|
+
message: MESSAGE$60
|
|
5304
5332
|
});
|
|
5305
5333
|
}
|
|
5306
5334
|
}))
|
|
@@ -5439,7 +5467,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
|
|
|
5439
5467
|
const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
|
|
5440
5468
|
const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
|
|
5441
5469
|
const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
|
|
5442
|
-
const MESSAGE$
|
|
5470
|
+
const MESSAGE$59 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
|
|
5443
5471
|
const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
|
|
5444
5472
|
const scanWorkflowContent = (content) => {
|
|
5445
5473
|
const lines = content.split("\n");
|
|
@@ -5484,7 +5512,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5484
5512
|
const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
|
|
5485
5513
|
const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
|
|
5486
5514
|
return [{
|
|
5487
|
-
message: MESSAGE$
|
|
5515
|
+
message: MESSAGE$59,
|
|
5488
5516
|
line: step.startLineIndex + installLineOffset + 1,
|
|
5489
5517
|
column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
|
|
5490
5518
|
}];
|
|
@@ -5494,7 +5522,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5494
5522
|
const scanNonWorkflowConfig = scanByPattern({
|
|
5495
5523
|
shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
|
|
5496
5524
|
pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
|
|
5497
|
-
message: MESSAGE$
|
|
5525
|
+
message: MESSAGE$59
|
|
5498
5526
|
});
|
|
5499
5527
|
const scan = (file) => {
|
|
5500
5528
|
if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
|
|
@@ -6263,7 +6291,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
6263
6291
|
};
|
|
6264
6292
|
//#endregion
|
|
6265
6293
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
6266
|
-
const MESSAGE$
|
|
6294
|
+
const MESSAGE$58 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
6267
6295
|
const KEY_HANDLERS = [
|
|
6268
6296
|
"onKeyUp",
|
|
6269
6297
|
"onKeyDown",
|
|
@@ -6474,7 +6502,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6474
6502
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
|
|
6475
6503
|
context.report({
|
|
6476
6504
|
node: node.name,
|
|
6477
|
-
message: MESSAGE$
|
|
6505
|
+
message: MESSAGE$58
|
|
6478
6506
|
});
|
|
6479
6507
|
} };
|
|
6480
6508
|
}
|
|
@@ -8727,7 +8755,7 @@ const getClassNameLiteral = (classAttribute) => {
|
|
|
8727
8755
|
};
|
|
8728
8756
|
//#endregion
|
|
8729
8757
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
8730
|
-
const MESSAGE$
|
|
8758
|
+
const MESSAGE$57 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
8731
8759
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
8732
8760
|
"td",
|
|
8733
8761
|
"th",
|
|
@@ -9165,7 +9193,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9165
9193
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
9166
9194
|
context.report({
|
|
9167
9195
|
node: candidate.opening,
|
|
9168
|
-
message: MESSAGE$
|
|
9196
|
+
message: MESSAGE$57
|
|
9169
9197
|
});
|
|
9170
9198
|
}
|
|
9171
9199
|
}
|
|
@@ -9926,7 +9954,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
9926
9954
|
});
|
|
9927
9955
|
//#endregion
|
|
9928
9956
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
9929
|
-
const MESSAGE$
|
|
9957
|
+
const MESSAGE$56 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
9930
9958
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
9931
9959
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
9932
9960
|
"aria-label",
|
|
@@ -9951,7 +9979,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
9951
9979
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
9952
9980
|
context.report({
|
|
9953
9981
|
node: node.name,
|
|
9954
|
-
message: MESSAGE$
|
|
9982
|
+
message: MESSAGE$56
|
|
9955
9983
|
});
|
|
9956
9984
|
} };
|
|
9957
9985
|
}
|
|
@@ -9991,7 +10019,7 @@ const isEs6Component = (node) => {
|
|
|
9991
10019
|
};
|
|
9992
10020
|
//#endregion
|
|
9993
10021
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
9994
|
-
const MESSAGE$
|
|
10022
|
+
const MESSAGE$55 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
9995
10023
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
9996
10024
|
"observer",
|
|
9997
10025
|
"lazy",
|
|
@@ -10184,7 +10212,7 @@ const displayName = defineRule({
|
|
|
10184
10212
|
const reportAt = (node) => {
|
|
10185
10213
|
context.report({
|
|
10186
10214
|
node,
|
|
10187
|
-
message: MESSAGE$
|
|
10215
|
+
message: MESSAGE$55
|
|
10188
10216
|
});
|
|
10189
10217
|
};
|
|
10190
10218
|
return {
|
|
@@ -14747,7 +14775,7 @@ const forbidElements = defineRule({
|
|
|
14747
14775
|
});
|
|
14748
14776
|
//#endregion
|
|
14749
14777
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
14750
|
-
const MESSAGE$
|
|
14778
|
+
const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
14751
14779
|
const forwardRefUsesRef = defineRule({
|
|
14752
14780
|
id: "forward-ref-uses-ref",
|
|
14753
14781
|
title: "forwardRef without ref parameter",
|
|
@@ -14770,7 +14798,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
14770
14798
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
14771
14799
|
context.report({
|
|
14772
14800
|
node: inner,
|
|
14773
|
-
message: MESSAGE$
|
|
14801
|
+
message: MESSAGE$54
|
|
14774
14802
|
});
|
|
14775
14803
|
} })
|
|
14776
14804
|
});
|
|
@@ -14811,7 +14839,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
14811
14839
|
});
|
|
14812
14840
|
//#endregion
|
|
14813
14841
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
14814
|
-
const MESSAGE$
|
|
14842
|
+
const MESSAGE$53 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
14815
14843
|
const DEFAULT_HEADING_TAGS = [
|
|
14816
14844
|
"h1",
|
|
14817
14845
|
"h2",
|
|
@@ -14845,7 +14873,7 @@ const headingHasContent = defineRule({
|
|
|
14845
14873
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
14846
14874
|
context.report({
|
|
14847
14875
|
node,
|
|
14848
|
-
message: MESSAGE$
|
|
14876
|
+
message: MESSAGE$53
|
|
14849
14877
|
});
|
|
14850
14878
|
} };
|
|
14851
14879
|
}
|
|
@@ -15009,7 +15037,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
15009
15037
|
});
|
|
15010
15038
|
//#endregion
|
|
15011
15039
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
15012
|
-
const MESSAGE$
|
|
15040
|
+
const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
15013
15041
|
const resolveSettings$39 = (settings) => {
|
|
15014
15042
|
const reactDoctor = settings?.["react-doctor"];
|
|
15015
15043
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -15056,13 +15084,13 @@ const htmlHasLang = defineRule({
|
|
|
15056
15084
|
if (!lang) {
|
|
15057
15085
|
context.report({
|
|
15058
15086
|
node: node.name,
|
|
15059
|
-
message: MESSAGE$
|
|
15087
|
+
message: MESSAGE$52
|
|
15060
15088
|
});
|
|
15061
15089
|
return;
|
|
15062
15090
|
}
|
|
15063
15091
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
15064
15092
|
node: lang,
|
|
15065
|
-
message: MESSAGE$
|
|
15093
|
+
message: MESSAGE$52
|
|
15066
15094
|
});
|
|
15067
15095
|
} };
|
|
15068
15096
|
}
|
|
@@ -15302,7 +15330,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
15302
15330
|
});
|
|
15303
15331
|
//#endregion
|
|
15304
15332
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
15305
|
-
const MESSAGE$
|
|
15333
|
+
const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
15306
15334
|
const evaluateTitleValue = (value) => {
|
|
15307
15335
|
if (!value) return "missing";
|
|
15308
15336
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -15342,14 +15370,14 @@ const iframeHasTitle = defineRule({
|
|
|
15342
15370
|
if (!titleAttr) {
|
|
15343
15371
|
if (hasSpread || tag === "iframe") context.report({
|
|
15344
15372
|
node: node.name,
|
|
15345
|
-
message: MESSAGE$
|
|
15373
|
+
message: MESSAGE$51
|
|
15346
15374
|
});
|
|
15347
15375
|
return;
|
|
15348
15376
|
}
|
|
15349
15377
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
15350
15378
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
15351
15379
|
node: titleAttr,
|
|
15352
|
-
message: MESSAGE$
|
|
15380
|
+
message: MESSAGE$51
|
|
15353
15381
|
});
|
|
15354
15382
|
} })
|
|
15355
15383
|
});
|
|
@@ -15474,7 +15502,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
15474
15502
|
});
|
|
15475
15503
|
//#endregion
|
|
15476
15504
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
15477
|
-
const MESSAGE$
|
|
15505
|
+
const MESSAGE$50 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
15478
15506
|
const DEFAULT_COMPONENTS = ["img"];
|
|
15479
15507
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
15480
15508
|
"image",
|
|
@@ -15539,7 +15567,7 @@ const imgRedundantAlt = defineRule({
|
|
|
15539
15567
|
if (!altAttribute) return;
|
|
15540
15568
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
15541
15569
|
node: altAttribute,
|
|
15542
|
-
message: MESSAGE$
|
|
15570
|
+
message: MESSAGE$50
|
|
15543
15571
|
});
|
|
15544
15572
|
} };
|
|
15545
15573
|
}
|
|
@@ -17509,7 +17537,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
17509
17537
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
17510
17538
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
17511
17539
|
};
|
|
17512
|
-
const MESSAGE$
|
|
17540
|
+
const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
17513
17541
|
const jsHoistRegexp = defineRule({
|
|
17514
17542
|
id: "js-hoist-regexp",
|
|
17515
17543
|
title: "RegExp built inside a loop",
|
|
@@ -17526,7 +17554,7 @@ const jsHoistRegexp = defineRule({
|
|
|
17526
17554
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
17527
17555
|
context.report({
|
|
17528
17556
|
node,
|
|
17529
|
-
message: MESSAGE$
|
|
17557
|
+
message: MESSAGE$49
|
|
17530
17558
|
});
|
|
17531
17559
|
};
|
|
17532
17560
|
return createLoopAwareVisitors({
|
|
@@ -20146,7 +20174,7 @@ const jsxMaxDepth = defineRule({
|
|
|
20146
20174
|
});
|
|
20147
20175
|
//#endregion
|
|
20148
20176
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
20149
|
-
const MESSAGE$
|
|
20177
|
+
const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
20150
20178
|
const LITERAL_TEXT_TAGS = new Set([
|
|
20151
20179
|
"code",
|
|
20152
20180
|
"pre",
|
|
@@ -20216,7 +20244,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
20216
20244
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
20217
20245
|
context.report({
|
|
20218
20246
|
node,
|
|
20219
|
-
message: MESSAGE$
|
|
20247
|
+
message: MESSAGE$48
|
|
20220
20248
|
});
|
|
20221
20249
|
} })
|
|
20222
20250
|
});
|
|
@@ -20247,7 +20275,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
20247
20275
|
};
|
|
20248
20276
|
//#endregion
|
|
20249
20277
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
20250
|
-
const MESSAGE$
|
|
20278
|
+
const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
20251
20279
|
const CONTEXT_MODULES$1 = [
|
|
20252
20280
|
"react",
|
|
20253
20281
|
"use-context-selector",
|
|
@@ -20345,7 +20373,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
20345
20373
|
if (!isConstructedValue(innerExpression)) continue;
|
|
20346
20374
|
context.report({
|
|
20347
20375
|
node: attribute,
|
|
20348
|
-
message: MESSAGE$
|
|
20376
|
+
message: MESSAGE$47
|
|
20349
20377
|
});
|
|
20350
20378
|
}
|
|
20351
20379
|
}
|
|
@@ -21200,7 +21228,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
21200
21228
|
];
|
|
21201
21229
|
//#endregion
|
|
21202
21230
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
21203
|
-
const MESSAGE$
|
|
21231
|
+
const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
21204
21232
|
const isDataArrayPropName = (propName) => {
|
|
21205
21233
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
21206
21234
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -21287,7 +21315,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
21287
21315
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
21288
21316
|
context.report({
|
|
21289
21317
|
node,
|
|
21290
|
-
message: MESSAGE$
|
|
21318
|
+
message: MESSAGE$46
|
|
21291
21319
|
});
|
|
21292
21320
|
}
|
|
21293
21321
|
};
|
|
@@ -21545,7 +21573,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
21545
21573
|
]);
|
|
21546
21574
|
//#endregion
|
|
21547
21575
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
21548
|
-
const MESSAGE$
|
|
21576
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
21549
21577
|
const isAccessorPredicateName = (propName) => {
|
|
21550
21578
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
21551
21579
|
if (propName.length <= prefix.length) continue;
|
|
@@ -21751,7 +21779,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
21751
21779
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
21752
21780
|
context.report({
|
|
21753
21781
|
node,
|
|
21754
|
-
message: MESSAGE$
|
|
21782
|
+
message: MESSAGE$45
|
|
21755
21783
|
});
|
|
21756
21784
|
}
|
|
21757
21785
|
};
|
|
@@ -21971,7 +21999,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
21971
21999
|
];
|
|
21972
22000
|
//#endregion
|
|
21973
22001
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
21974
|
-
const MESSAGE$
|
|
22002
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
21975
22003
|
const isConfigObjectPropName = (propName) => {
|
|
21976
22004
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
21977
22005
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -22060,7 +22088,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22060
22088
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
22061
22089
|
context.report({
|
|
22062
22090
|
node,
|
|
22063
|
-
message: MESSAGE$
|
|
22091
|
+
message: MESSAGE$44
|
|
22064
22092
|
});
|
|
22065
22093
|
}
|
|
22066
22094
|
};
|
|
@@ -22068,7 +22096,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22068
22096
|
});
|
|
22069
22097
|
//#endregion
|
|
22070
22098
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
22071
|
-
const MESSAGE$
|
|
22099
|
+
const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
22072
22100
|
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;
|
|
22073
22101
|
const resolveSettings$29 = (settings) => {
|
|
22074
22102
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -22106,7 +22134,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
22106
22134
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
22107
22135
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
22108
22136
|
node: attribute,
|
|
22109
|
-
message: MESSAGE$
|
|
22137
|
+
message: MESSAGE$43
|
|
22110
22138
|
});
|
|
22111
22139
|
}
|
|
22112
22140
|
} };
|
|
@@ -22726,7 +22754,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
22726
22754
|
});
|
|
22727
22755
|
//#endregion
|
|
22728
22756
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
22729
|
-
const MESSAGE$
|
|
22757
|
+
const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
|
|
22730
22758
|
const resolveSettings$25 = (settings) => {
|
|
22731
22759
|
const reactDoctor = settings?.["react-doctor"];
|
|
22732
22760
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -22769,7 +22797,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
22769
22797
|
}
|
|
22770
22798
|
context.report({
|
|
22771
22799
|
node: attribute,
|
|
22772
|
-
message: MESSAGE$
|
|
22800
|
+
message: MESSAGE$42
|
|
22773
22801
|
});
|
|
22774
22802
|
didReportInFile = true;
|
|
22775
22803
|
return;
|
|
@@ -23045,7 +23073,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
23045
23073
|
});
|
|
23046
23074
|
//#endregion
|
|
23047
23075
|
//#region src/plugin/rules/a11y/lang.ts
|
|
23048
|
-
const MESSAGE$
|
|
23076
|
+
const MESSAGE$41 = "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`.";
|
|
23049
23077
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
23050
23078
|
"aa",
|
|
23051
23079
|
"ab",
|
|
@@ -23525,7 +23553,7 @@ const lang = defineRule({
|
|
|
23525
23553
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
23526
23554
|
context.report({
|
|
23527
23555
|
node: langAttr,
|
|
23528
|
-
message: MESSAGE$
|
|
23556
|
+
message: MESSAGE$41
|
|
23529
23557
|
});
|
|
23530
23558
|
return;
|
|
23531
23559
|
}
|
|
@@ -23534,7 +23562,7 @@ const lang = defineRule({
|
|
|
23534
23562
|
if (value === null) return;
|
|
23535
23563
|
if (!isValidLangTag(value)) context.report({
|
|
23536
23564
|
node: langAttr,
|
|
23537
|
-
message: MESSAGE$
|
|
23565
|
+
message: MESSAGE$41
|
|
23538
23566
|
});
|
|
23539
23567
|
} })
|
|
23540
23568
|
});
|
|
@@ -23585,7 +23613,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
23585
23613
|
});
|
|
23586
23614
|
//#endregion
|
|
23587
23615
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
23588
|
-
const MESSAGE$
|
|
23616
|
+
const MESSAGE$40 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
23589
23617
|
const DEFAULT_AUDIO = ["audio"];
|
|
23590
23618
|
const DEFAULT_VIDEO = ["video"];
|
|
23591
23619
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -23674,7 +23702,7 @@ const mediaHasCaption = defineRule({
|
|
|
23674
23702
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
23675
23703
|
context.report({
|
|
23676
23704
|
node: node.name,
|
|
23677
|
-
message: MESSAGE$
|
|
23705
|
+
message: MESSAGE$40
|
|
23678
23706
|
});
|
|
23679
23707
|
return;
|
|
23680
23708
|
}
|
|
@@ -23693,7 +23721,7 @@ const mediaHasCaption = defineRule({
|
|
|
23693
23721
|
return kindValue.value.toLowerCase() === "captions";
|
|
23694
23722
|
})) context.report({
|
|
23695
23723
|
node: node.name,
|
|
23696
|
-
message: MESSAGE$
|
|
23724
|
+
message: MESSAGE$40
|
|
23697
23725
|
});
|
|
23698
23726
|
} };
|
|
23699
23727
|
}
|
|
@@ -25236,7 +25264,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
25236
25264
|
});
|
|
25237
25265
|
//#endregion
|
|
25238
25266
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
25239
|
-
const MESSAGE$
|
|
25267
|
+
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
25240
25268
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
25241
25269
|
const noAccessKey = defineRule({
|
|
25242
25270
|
id: "no-access-key",
|
|
@@ -25255,7 +25283,7 @@ const noAccessKey = defineRule({
|
|
|
25255
25283
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
25256
25284
|
context.report({
|
|
25257
25285
|
node: accessKey,
|
|
25258
|
-
message: MESSAGE$
|
|
25286
|
+
message: MESSAGE$39
|
|
25259
25287
|
});
|
|
25260
25288
|
return;
|
|
25261
25289
|
}
|
|
@@ -25265,7 +25293,7 @@ const noAccessKey = defineRule({
|
|
|
25265
25293
|
if (isUndefinedIdentifier(expression)) return;
|
|
25266
25294
|
context.report({
|
|
25267
25295
|
node: accessKey,
|
|
25268
|
-
message: MESSAGE$
|
|
25296
|
+
message: MESSAGE$39
|
|
25269
25297
|
});
|
|
25270
25298
|
}
|
|
25271
25299
|
} };
|
|
@@ -27120,7 +27148,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
27120
27148
|
});
|
|
27121
27149
|
//#endregion
|
|
27122
27150
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
27123
|
-
const MESSAGE$
|
|
27151
|
+
const MESSAGE$38 = "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.";
|
|
27124
27152
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
27125
27153
|
"button",
|
|
27126
27154
|
"embed",
|
|
@@ -27232,7 +27260,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
27232
27260
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
27233
27261
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
27234
27262
|
node: ariaHidden,
|
|
27235
|
-
message: MESSAGE$
|
|
27263
|
+
message: MESSAGE$38
|
|
27236
27264
|
});
|
|
27237
27265
|
} })
|
|
27238
27266
|
});
|
|
@@ -28154,7 +28182,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
28154
28182
|
};
|
|
28155
28183
|
//#endregion
|
|
28156
28184
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
28157
|
-
const MESSAGE$
|
|
28185
|
+
const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
|
|
28158
28186
|
const SECOND_INDEX_METHODS = new Set([
|
|
28159
28187
|
"every",
|
|
28160
28188
|
"filter",
|
|
@@ -28273,14 +28301,14 @@ const noArrayIndexKey = defineRule({
|
|
|
28273
28301
|
if (propName !== "key") continue;
|
|
28274
28302
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
28275
28303
|
node: property,
|
|
28276
|
-
message: MESSAGE$
|
|
28304
|
+
message: MESSAGE$37
|
|
28277
28305
|
});
|
|
28278
28306
|
}
|
|
28279
28307
|
} })
|
|
28280
28308
|
});
|
|
28281
28309
|
//#endregion
|
|
28282
28310
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
28283
|
-
const MESSAGE$
|
|
28311
|
+
const MESSAGE$36 = "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.";
|
|
28284
28312
|
const noAsyncEffectCallback = defineRule({
|
|
28285
28313
|
id: "no-async-effect-callback",
|
|
28286
28314
|
title: "Async effect callback",
|
|
@@ -28298,13 +28326,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
28298
28326
|
if (!callback.async) return;
|
|
28299
28327
|
context.report({
|
|
28300
28328
|
node: callback,
|
|
28301
|
-
message: MESSAGE$
|
|
28329
|
+
message: MESSAGE$36
|
|
28302
28330
|
});
|
|
28303
28331
|
} })
|
|
28304
28332
|
});
|
|
28305
28333
|
//#endregion
|
|
28306
28334
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
28307
|
-
const MESSAGE$
|
|
28335
|
+
const MESSAGE$35 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
28308
28336
|
const resolveSettings$21 = (settings) => {
|
|
28309
28337
|
const reactDoctor = settings?.["react-doctor"];
|
|
28310
28338
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -28401,7 +28429,7 @@ const noAutofocus = defineRule({
|
|
|
28401
28429
|
if (isConditionallyRendered(node)) return;
|
|
28402
28430
|
context.report({
|
|
28403
28431
|
node: autoFocusAttribute,
|
|
28404
|
-
message: MESSAGE$
|
|
28432
|
+
message: MESSAGE$35
|
|
28405
28433
|
});
|
|
28406
28434
|
} };
|
|
28407
28435
|
}
|
|
@@ -29374,7 +29402,7 @@ const noChainStateUpdates = defineRule({
|
|
|
29374
29402
|
});
|
|
29375
29403
|
//#endregion
|
|
29376
29404
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
29377
|
-
const MESSAGE$
|
|
29405
|
+
const MESSAGE$34 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
29378
29406
|
const noChildrenProp = defineRule({
|
|
29379
29407
|
id: "no-children-prop",
|
|
29380
29408
|
title: "Children passed as a prop",
|
|
@@ -29386,7 +29414,7 @@ const noChildrenProp = defineRule({
|
|
|
29386
29414
|
if (node.name.name !== "children") return;
|
|
29387
29415
|
context.report({
|
|
29388
29416
|
node: node.name,
|
|
29389
|
-
message: MESSAGE$
|
|
29417
|
+
message: MESSAGE$34
|
|
29390
29418
|
});
|
|
29391
29419
|
},
|
|
29392
29420
|
CallExpression(node) {
|
|
@@ -29399,7 +29427,7 @@ const noChildrenProp = defineRule({
|
|
|
29399
29427
|
const propertyKey = property.key;
|
|
29400
29428
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
29401
29429
|
node: propertyKey,
|
|
29402
|
-
message: MESSAGE$
|
|
29430
|
+
message: MESSAGE$34
|
|
29403
29431
|
});
|
|
29404
29432
|
}
|
|
29405
29433
|
}
|
|
@@ -29407,7 +29435,7 @@ const noChildrenProp = defineRule({
|
|
|
29407
29435
|
});
|
|
29408
29436
|
//#endregion
|
|
29409
29437
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
29410
|
-
const MESSAGE$
|
|
29438
|
+
const MESSAGE$33 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
29411
29439
|
const noCloneElement = defineRule({
|
|
29412
29440
|
id: "no-clone-element",
|
|
29413
29441
|
title: "cloneElement makes child props fragile",
|
|
@@ -29420,7 +29448,7 @@ const noCloneElement = defineRule({
|
|
|
29420
29448
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
29421
29449
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
29422
29450
|
node: callee,
|
|
29423
|
-
message: MESSAGE$
|
|
29451
|
+
message: MESSAGE$33
|
|
29424
29452
|
});
|
|
29425
29453
|
return;
|
|
29426
29454
|
}
|
|
@@ -29433,7 +29461,7 @@ const noCloneElement = defineRule({
|
|
|
29433
29461
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
29434
29462
|
context.report({
|
|
29435
29463
|
node: callee,
|
|
29436
|
-
message: MESSAGE$
|
|
29464
|
+
message: MESSAGE$33
|
|
29437
29465
|
});
|
|
29438
29466
|
}
|
|
29439
29467
|
} })
|
|
@@ -29453,7 +29481,7 @@ const getCallMethodName = (callee) => {
|
|
|
29453
29481
|
};
|
|
29454
29482
|
//#endregion
|
|
29455
29483
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
29456
|
-
const MESSAGE$
|
|
29484
|
+
const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
29457
29485
|
const CONTEXT_MODULES = [
|
|
29458
29486
|
"react",
|
|
29459
29487
|
"use-context-selector",
|
|
@@ -29501,13 +29529,13 @@ const noCreateContextInRender = defineRule({
|
|
|
29501
29529
|
if (!componentOrHookName) return;
|
|
29502
29530
|
context.report({
|
|
29503
29531
|
node,
|
|
29504
|
-
message: `${MESSAGE$
|
|
29532
|
+
message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
|
|
29505
29533
|
});
|
|
29506
29534
|
} })
|
|
29507
29535
|
});
|
|
29508
29536
|
//#endregion
|
|
29509
29537
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
29510
|
-
const MESSAGE$
|
|
29538
|
+
const MESSAGE$31 = "`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.";
|
|
29511
29539
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
29512
29540
|
const parent = functionNode.parent;
|
|
29513
29541
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -29537,7 +29565,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
29537
29565
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
|
|
29538
29566
|
context.report({
|
|
29539
29567
|
node,
|
|
29540
|
-
message: MESSAGE$
|
|
29568
|
+
message: MESSAGE$31
|
|
29541
29569
|
});
|
|
29542
29570
|
} })
|
|
29543
29571
|
});
|
|
@@ -29677,7 +29705,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
29677
29705
|
});
|
|
29678
29706
|
//#endregion
|
|
29679
29707
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
29680
|
-
const MESSAGE$
|
|
29708
|
+
const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
29681
29709
|
const noDanger = defineRule({
|
|
29682
29710
|
id: "no-danger",
|
|
29683
29711
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -29691,7 +29719,7 @@ const noDanger = defineRule({
|
|
|
29691
29719
|
if (!propAttribute) return;
|
|
29692
29720
|
context.report({
|
|
29693
29721
|
node: propAttribute.name,
|
|
29694
|
-
message: MESSAGE$
|
|
29722
|
+
message: MESSAGE$30
|
|
29695
29723
|
});
|
|
29696
29724
|
},
|
|
29697
29725
|
CallExpression(node) {
|
|
@@ -29703,7 +29731,7 @@ const noDanger = defineRule({
|
|
|
29703
29731
|
const propertyKey = property.key;
|
|
29704
29732
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
29705
29733
|
node: propertyKey,
|
|
29706
|
-
message: MESSAGE$
|
|
29734
|
+
message: MESSAGE$30
|
|
29707
29735
|
});
|
|
29708
29736
|
}
|
|
29709
29737
|
}
|
|
@@ -29726,7 +29754,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
29726
29754
|
};
|
|
29727
29755
|
//#endregion
|
|
29728
29756
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
29729
|
-
const MESSAGE$
|
|
29757
|
+
const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
29730
29758
|
const mergePropsShape = (target, source) => {
|
|
29731
29759
|
target.hasDangerously ||= source.hasDangerously;
|
|
29732
29760
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -29801,7 +29829,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29801
29829
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
29802
29830
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
29803
29831
|
node: opening,
|
|
29804
|
-
message: MESSAGE$
|
|
29832
|
+
message: MESSAGE$29
|
|
29805
29833
|
});
|
|
29806
29834
|
},
|
|
29807
29835
|
CallExpression(node) {
|
|
@@ -29814,7 +29842,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29814
29842
|
const positionalChildren = node.arguments.slice(2);
|
|
29815
29843
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
29816
29844
|
node,
|
|
29817
|
-
message: MESSAGE$
|
|
29845
|
+
message: MESSAGE$29
|
|
29818
29846
|
});
|
|
29819
29847
|
}
|
|
29820
29848
|
})
|
|
@@ -30779,7 +30807,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
30779
30807
|
//#endregion
|
|
30780
30808
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
30781
30809
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
30782
|
-
const MESSAGE$
|
|
30810
|
+
const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
30783
30811
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
30784
30812
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
30785
30813
|
let ancestor = setStateCall.parent;
|
|
@@ -30907,7 +30935,7 @@ const noDidMountSetState = defineRule({
|
|
|
30907
30935
|
}
|
|
30908
30936
|
context.report({
|
|
30909
30937
|
node: node.callee,
|
|
30910
|
-
message: MESSAGE$
|
|
30938
|
+
message: MESSAGE$28
|
|
30911
30939
|
});
|
|
30912
30940
|
} };
|
|
30913
30941
|
}
|
|
@@ -30915,7 +30943,7 @@ const noDidMountSetState = defineRule({
|
|
|
30915
30943
|
//#endregion
|
|
30916
30944
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
30917
30945
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
30918
|
-
const MESSAGE$
|
|
30946
|
+
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
30919
30947
|
const EQUALITY_OPERATORS = new Set([
|
|
30920
30948
|
"==",
|
|
30921
30949
|
"===",
|
|
@@ -31089,7 +31117,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
31089
31117
|
if (isInsideDiffGuard(node)) return;
|
|
31090
31118
|
context.report({
|
|
31091
31119
|
node: node.callee,
|
|
31092
|
-
message: MESSAGE$
|
|
31120
|
+
message: MESSAGE$27
|
|
31093
31121
|
});
|
|
31094
31122
|
} };
|
|
31095
31123
|
}
|
|
@@ -31112,7 +31140,7 @@ const isStateMemberExpression = (node) => {
|
|
|
31112
31140
|
};
|
|
31113
31141
|
//#endregion
|
|
31114
31142
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
31115
|
-
const MESSAGE$
|
|
31143
|
+
const MESSAGE$26 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
|
|
31116
31144
|
const shouldIgnoreMutation = (node) => {
|
|
31117
31145
|
let isConstructor = false;
|
|
31118
31146
|
let isInsideCallExpression = false;
|
|
@@ -31134,7 +31162,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
31134
31162
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
31135
31163
|
context.report({
|
|
31136
31164
|
node: reportNode,
|
|
31137
|
-
message: MESSAGE$
|
|
31165
|
+
message: MESSAGE$26
|
|
31138
31166
|
});
|
|
31139
31167
|
};
|
|
31140
31168
|
const noDirectMutationState = defineRule({
|
|
@@ -31485,7 +31513,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
31485
31513
|
});
|
|
31486
31514
|
//#endregion
|
|
31487
31515
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
31488
|
-
const MESSAGE$
|
|
31516
|
+
const MESSAGE$25 = "`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.";
|
|
31489
31517
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
31490
31518
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
31491
31519
|
const noDocumentWrite = defineRule({
|
|
@@ -31502,7 +31530,7 @@ const noDocumentWrite = defineRule({
|
|
|
31502
31530
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
31503
31531
|
context.report({
|
|
31504
31532
|
node,
|
|
31505
|
-
message: MESSAGE$
|
|
31533
|
+
message: MESSAGE$25
|
|
31506
31534
|
});
|
|
31507
31535
|
} })
|
|
31508
31536
|
});
|
|
@@ -33720,7 +33748,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
33720
33748
|
"ReactDOM",
|
|
33721
33749
|
"ReactDom"
|
|
33722
33750
|
]);
|
|
33723
|
-
const MESSAGE$
|
|
33751
|
+
const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
33724
33752
|
const noFindDomNode = defineRule({
|
|
33725
33753
|
id: "no-find-dom-node",
|
|
33726
33754
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -33731,7 +33759,7 @@ const noFindDomNode = defineRule({
|
|
|
33731
33759
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
33732
33760
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
33733
33761
|
node: callee,
|
|
33734
|
-
message: MESSAGE$
|
|
33762
|
+
message: MESSAGE$24
|
|
33735
33763
|
});
|
|
33736
33764
|
return;
|
|
33737
33765
|
}
|
|
@@ -33742,7 +33770,7 @@ const noFindDomNode = defineRule({
|
|
|
33742
33770
|
if (callee.property.name !== "findDOMNode") return;
|
|
33743
33771
|
context.report({
|
|
33744
33772
|
node: callee.property,
|
|
33745
|
-
message: MESSAGE$
|
|
33773
|
+
message: MESSAGE$24
|
|
33746
33774
|
});
|
|
33747
33775
|
}
|
|
33748
33776
|
} })
|
|
@@ -33951,13 +33979,6 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
33951
33979
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
33952
33980
|
};
|
|
33953
33981
|
//#endregion
|
|
33954
|
-
//#region src/plugin/utils/is-type-only-import.ts
|
|
33955
|
-
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
33956
|
-
if (!specifiers || specifiers.length === 0) return false;
|
|
33957
|
-
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
33958
|
-
};
|
|
33959
|
-
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
33960
|
-
//#endregion
|
|
33961
33982
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
33962
33983
|
const isLibraryDevPage = (filename) => {
|
|
33963
33984
|
if (!filename) return false;
|
|
@@ -34656,7 +34677,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34656
34677
|
});
|
|
34657
34678
|
//#endregion
|
|
34658
34679
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
34659
|
-
const MESSAGE$
|
|
34680
|
+
const MESSAGE$23 = "`<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.";
|
|
34660
34681
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
34661
34682
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
34662
34683
|
title: "Lazy image with high fetchPriority",
|
|
@@ -34670,7 +34691,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
34670
34691
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
34671
34692
|
context.report({
|
|
34672
34693
|
node: node.name,
|
|
34673
|
-
message: MESSAGE$
|
|
34694
|
+
message: MESSAGE$23
|
|
34674
34695
|
});
|
|
34675
34696
|
} })
|
|
34676
34697
|
});
|
|
@@ -34859,7 +34880,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
34859
34880
|
});
|
|
34860
34881
|
//#endregion
|
|
34861
34882
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
34862
|
-
const MESSAGE$
|
|
34883
|
+
const MESSAGE$22 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
34863
34884
|
const REACT_USE_REF_OPTIONS = {
|
|
34864
34885
|
allowGlobalReactNamespace: false,
|
|
34865
34886
|
allowUnboundBareCalls: false
|
|
@@ -34960,7 +34981,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34960
34981
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
34961
34982
|
context.report({
|
|
34962
34983
|
node: indeterminateAttribute,
|
|
34963
|
-
message: MESSAGE$
|
|
34984
|
+
message: MESSAGE$22
|
|
34964
34985
|
});
|
|
34965
34986
|
},
|
|
34966
34987
|
CallExpression(node) {
|
|
@@ -34969,7 +34990,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34969
34990
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
34970
34991
|
context.report({
|
|
34971
34992
|
node,
|
|
34972
|
-
message: MESSAGE$
|
|
34993
|
+
message: MESSAGE$22
|
|
34973
34994
|
});
|
|
34974
34995
|
}
|
|
34975
34996
|
};
|
|
@@ -35257,7 +35278,7 @@ const noIsMounted = defineRule({
|
|
|
35257
35278
|
});
|
|
35258
35279
|
//#endregion
|
|
35259
35280
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
35260
|
-
const MESSAGE$
|
|
35281
|
+
const MESSAGE$21 = "`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)`.";
|
|
35261
35282
|
const isJsonMethodCall = (node, method) => {
|
|
35262
35283
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
35263
35284
|
const callee = node.callee;
|
|
@@ -35321,13 +35342,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
35321
35342
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
35322
35343
|
context.report({
|
|
35323
35344
|
node,
|
|
35324
|
-
message: MESSAGE$
|
|
35345
|
+
message: MESSAGE$21
|
|
35325
35346
|
});
|
|
35326
35347
|
} })
|
|
35327
35348
|
});
|
|
35328
35349
|
//#endregion
|
|
35329
35350
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
35330
|
-
const MESSAGE$
|
|
35351
|
+
const MESSAGE$20 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
35331
35352
|
const isJsxElementTypeReference = (node) => {
|
|
35332
35353
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
35333
35354
|
const typeName = node.typeName;
|
|
@@ -35381,7 +35402,7 @@ const noJsxElementType = defineRule({
|
|
|
35381
35402
|
if (isJsxImported) return;
|
|
35382
35403
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
35383
35404
|
node: typeAnnotation,
|
|
35384
|
-
message: MESSAGE$
|
|
35405
|
+
message: MESSAGE$20
|
|
35385
35406
|
});
|
|
35386
35407
|
}
|
|
35387
35408
|
};
|
|
@@ -35613,6 +35634,221 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
35613
35634
|
}
|
|
35614
35635
|
});
|
|
35615
35636
|
//#endregion
|
|
35637
|
+
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
35638
|
+
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
35639
|
+
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
35640
|
+
const expression = stripParenExpression(node);
|
|
35641
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
35642
|
+
const propertyName = getStaticPropertyName(expression);
|
|
35643
|
+
const receiver = stripParenExpression(expression.object);
|
|
35644
|
+
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
35645
|
+
}
|
|
35646
|
+
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
35647
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
35648
|
+
const symbol = scopes.symbolFor(expression);
|
|
35649
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
35650
|
+
visitedSymbolIds.add(symbol.id);
|
|
35651
|
+
if (isImportedFromReact(symbol)) {
|
|
35652
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
35653
|
+
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
35654
|
+
}
|
|
35655
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
35656
|
+
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
35657
|
+
};
|
|
35658
|
+
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
35659
|
+
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
35660
|
+
visitedClassNodes.add(classNode);
|
|
35661
|
+
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
35662
|
+
};
|
|
35663
|
+
//#endregion
|
|
35664
|
+
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
35665
|
+
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
35666
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
35667
|
+
let containsReactHookCall = false;
|
|
35668
|
+
walkAst(functionNode.body, (node) => {
|
|
35669
|
+
if (containsReactHookCall) return false;
|
|
35670
|
+
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
35671
|
+
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
35672
|
+
containsReactHookCall = true;
|
|
35673
|
+
return false;
|
|
35674
|
+
}
|
|
35675
|
+
});
|
|
35676
|
+
return containsReactHookCall;
|
|
35677
|
+
};
|
|
35678
|
+
//#endregion
|
|
35679
|
+
//#region src/plugin/utils/function-returns-props-children.ts
|
|
35680
|
+
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
35681
|
+
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
35682
|
+
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
35683
|
+
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
35684
|
+
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
35685
|
+
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
35686
|
+
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
35687
|
+
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
35688
|
+
const propertyValue = stripParenExpression(property.value);
|
|
35689
|
+
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
35690
|
+
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
35691
|
+
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
35692
|
+
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
35693
|
+
}
|
|
35694
|
+
}
|
|
35695
|
+
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
35696
|
+
const candidate = stripParenExpression(expression);
|
|
35697
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
35698
|
+
const symbol = scopes.symbolFor(candidate);
|
|
35699
|
+
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
35700
|
+
}
|
|
35701
|
+
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
35702
|
+
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
35703
|
+
const receiver = stripParenExpression(candidate.object);
|
|
35704
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
35705
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
35706
|
+
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
35707
|
+
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
35708
|
+
}, controlFlow);
|
|
35709
|
+
};
|
|
35710
|
+
//#endregion
|
|
35711
|
+
//#region src/plugin/utils/function-returns-only-null.ts
|
|
35712
|
+
const isNullExpression = (expression) => {
|
|
35713
|
+
const candidate = stripParenExpression(expression);
|
|
35714
|
+
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
35715
|
+
};
|
|
35716
|
+
const functionReturnsOnlyNull = (functionNode) => {
|
|
35717
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
35718
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
35719
|
+
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
35720
|
+
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
35721
|
+
};
|
|
35722
|
+
//#endregion
|
|
35723
|
+
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
35724
|
+
const findFactoryRoot = (node) => {
|
|
35725
|
+
const candidate = stripParenExpression(node);
|
|
35726
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
35727
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
35728
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
35729
|
+
return null;
|
|
35730
|
+
};
|
|
35731
|
+
const findFactoryPropertyName = (node) => {
|
|
35732
|
+
const candidate = stripParenExpression(node);
|
|
35733
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
35734
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
35735
|
+
return null;
|
|
35736
|
+
};
|
|
35737
|
+
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
35738
|
+
const candidate = stripParenExpression(expression);
|
|
35739
|
+
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
35740
|
+
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
35741
|
+
if (!factoryRoot) return false;
|
|
35742
|
+
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
35743
|
+
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
35744
|
+
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
35745
|
+
if (!symbol || symbol.kind !== "import") return false;
|
|
35746
|
+
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
35747
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
35748
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
35749
|
+
};
|
|
35750
|
+
//#endregion
|
|
35751
|
+
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
35752
|
+
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
35753
|
+
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
35754
|
+
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
35755
|
+
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
35756
|
+
const candidate = stripParenExpression(expression);
|
|
35757
|
+
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
35758
|
+
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
35759
|
+
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
35760
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
35761
|
+
const symbol = scopes.symbolFor(candidate);
|
|
35762
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
35763
|
+
visitedSymbolIds.add(symbol.id);
|
|
35764
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
35765
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
35766
|
+
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
35767
|
+
}
|
|
35768
|
+
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
35769
|
+
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
35770
|
+
const factoryCallee = stripParenExpression(candidate.callee);
|
|
35771
|
+
if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
|
|
35772
|
+
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
35773
|
+
const wrappedComponent = candidate.arguments[0];
|
|
35774
|
+
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
35775
|
+
}
|
|
35776
|
+
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
35777
|
+
const factory = candidate.arguments[0];
|
|
35778
|
+
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
35779
|
+
const unwrappedFactory = stripParenExpression(factory);
|
|
35780
|
+
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
35781
|
+
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
35782
|
+
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
35783
|
+
const returnedExpression = returnStatements[0]?.argument;
|
|
35784
|
+
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
35785
|
+
};
|
|
35786
|
+
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
35787
|
+
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
35788
|
+
for (const candidateSymbol of candidateSymbols) {
|
|
35789
|
+
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
35790
|
+
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
35791
|
+
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
35792
|
+
continue;
|
|
35793
|
+
}
|
|
35794
|
+
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
35795
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
35796
|
+
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
35797
|
+
continue;
|
|
35798
|
+
}
|
|
35799
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
35800
|
+
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
35801
|
+
continue;
|
|
35802
|
+
}
|
|
35803
|
+
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
35804
|
+
}
|
|
35805
|
+
return false;
|
|
35806
|
+
};
|
|
35807
|
+
//#endregion
|
|
35808
|
+
//#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
|
|
35809
|
+
const REACT_COMPONENT_TYPE_NAMES = new Set([
|
|
35810
|
+
"ComponentClass",
|
|
35811
|
+
"ComponentType",
|
|
35812
|
+
"FC",
|
|
35813
|
+
"FunctionComponent"
|
|
35814
|
+
]);
|
|
35815
|
+
const findVisibleSymbol = (identifier, scopes) => {
|
|
35816
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
35817
|
+
let scope = scopes.scopeFor(identifier);
|
|
35818
|
+
while (true) {
|
|
35819
|
+
const symbol = scope.symbolsByName.get(identifier.name);
|
|
35820
|
+
if (symbol) return symbol;
|
|
35821
|
+
if (!scope.parent) return null;
|
|
35822
|
+
scope = scope.parent;
|
|
35823
|
+
}
|
|
35824
|
+
};
|
|
35825
|
+
const isReactNamespaceType = (identifier, scopes) => {
|
|
35826
|
+
const symbol = findVisibleSymbol(identifier, scopes);
|
|
35827
|
+
return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
35828
|
+
};
|
|
35829
|
+
const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
|
|
35830
|
+
if (!typeNode) return false;
|
|
35831
|
+
if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
35832
|
+
if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
|
|
35833
|
+
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
35834
|
+
const typeName = typeNode.typeName;
|
|
35835
|
+
if (isNodeOfType(typeName, "TSQualifiedName")) return isNodeOfType(typeName.left, "Identifier") && isNodeOfType(typeName.right, "Identifier") && REACT_COMPONENT_TYPE_NAMES.has(typeName.right.name) && isReactNamespaceType(typeName.left, scopes);
|
|
35836
|
+
if (!isNodeOfType(typeName, "Identifier")) return false;
|
|
35837
|
+
const typeSymbol = findVisibleSymbol(typeName, scopes);
|
|
35838
|
+
if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
|
|
35839
|
+
if (isImportedFromReact(typeSymbol)) {
|
|
35840
|
+
const importedName = getImportedName(typeSymbol.declarationNode);
|
|
35841
|
+
return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
|
|
35842
|
+
}
|
|
35843
|
+
if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
|
|
35844
|
+
visitedSymbolIds.add(typeSymbol.id);
|
|
35845
|
+
return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
35846
|
+
};
|
|
35847
|
+
const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
|
|
35848
|
+
const bindingIdentifier = symbol.bindingIdentifier;
|
|
35849
|
+
return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
|
|
35850
|
+
};
|
|
35851
|
+
//#endregion
|
|
35616
35852
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
35617
35853
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
35618
35854
|
"childContextTypes",
|
|
@@ -35623,15 +35859,6 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
35623
35859
|
if (memberName === "childContextTypes" || memberName === "getChildContext") return `${memberName} uses the old context API that React 19 removes, so your provider stops passing data. Switch to \`createContext\` with \`<MyContext.Provider value={...}>\` & read it with \`useContext()\`, moving every consumer together.`;
|
|
35624
35860
|
return "contextTypes uses the old context API that React 19 removes, so your component stops receiving context. Use `static contextType = MyContext` or `useContext()` in a function component, & update the provider too.";
|
|
35625
35861
|
};
|
|
35626
|
-
const isInsideClassBody = (node) => {
|
|
35627
|
-
let current = node.parent;
|
|
35628
|
-
while (current) {
|
|
35629
|
-
if (isNodeOfType(current, "ClassBody")) return true;
|
|
35630
|
-
if (isFunctionLike$2(current)) return false;
|
|
35631
|
-
current = current.parent;
|
|
35632
|
-
}
|
|
35633
|
-
return false;
|
|
35634
|
-
};
|
|
35635
35862
|
const noLegacyContextApi = defineRule({
|
|
35636
35863
|
id: "no-legacy-context-api",
|
|
35637
35864
|
title: "Legacy context API",
|
|
@@ -35646,6 +35873,7 @@ const noLegacyContextApi = defineRule({
|
|
|
35646
35873
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
35647
35874
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
35648
35875
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
35876
|
+
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
35649
35877
|
context.report({
|
|
35650
35878
|
node: memberNode.key,
|
|
35651
35879
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -35653,6 +35881,8 @@ const noLegacyContextApi = defineRule({
|
|
|
35653
35881
|
};
|
|
35654
35882
|
return {
|
|
35655
35883
|
ClassBody(node) {
|
|
35884
|
+
const classNode = node.parent;
|
|
35885
|
+
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
35656
35886
|
for (const member of node.body ?? []) checkMember(member);
|
|
35657
35887
|
},
|
|
35658
35888
|
AssignmentExpression(node) {
|
|
@@ -35662,9 +35892,11 @@ const noLegacyContextApi = defineRule({
|
|
|
35662
35892
|
if (left.computed) return;
|
|
35663
35893
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
35664
35894
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
35665
|
-
if (
|
|
35666
|
-
|
|
35667
|
-
if (
|
|
35895
|
+
if (left.property.name === "getChildContext") return;
|
|
35896
|
+
const component = stripParenExpression(left.object);
|
|
35897
|
+
if (!isNodeOfType(component, "Identifier")) return;
|
|
35898
|
+
const symbol = context.scopes.symbolFor(component);
|
|
35899
|
+
if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
|
|
35668
35900
|
context.report({
|
|
35669
35901
|
node: left,
|
|
35670
35902
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -36486,7 +36718,7 @@ const noMoment = defineRule({
|
|
|
36486
36718
|
});
|
|
36487
36719
|
//#endregion
|
|
36488
36720
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
36489
|
-
const MESSAGE$
|
|
36721
|
+
const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
36490
36722
|
const resolveSettings$16 = (settings) => {
|
|
36491
36723
|
const reactDoctor = settings?.["react-doctor"];
|
|
36492
36724
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -36845,7 +37077,7 @@ const noMultiComp = defineRule({
|
|
|
36845
37077
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
36846
37078
|
for (const component of flagged.slice(1)) context.report({
|
|
36847
37079
|
node: component.reportNode,
|
|
36848
|
-
message: MESSAGE$
|
|
37080
|
+
message: MESSAGE$19
|
|
36849
37081
|
});
|
|
36850
37082
|
} };
|
|
36851
37083
|
}
|
|
@@ -37058,7 +37290,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
37058
37290
|
};
|
|
37059
37291
|
//#endregion
|
|
37060
37292
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
37061
|
-
const MESSAGE$
|
|
37293
|
+
const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
|
|
37062
37294
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
37063
37295
|
"copyWithin",
|
|
37064
37296
|
"fill",
|
|
@@ -37267,7 +37499,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37267
37499
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
37268
37500
|
context.report({
|
|
37269
37501
|
node: options.crossFileConsumerCallSite,
|
|
37270
|
-
message: `${MESSAGE$
|
|
37502
|
+
message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
37271
37503
|
});
|
|
37272
37504
|
return;
|
|
37273
37505
|
}
|
|
@@ -37276,7 +37508,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37276
37508
|
reportedNodes.add(mutation.node);
|
|
37277
37509
|
context.report({
|
|
37278
37510
|
node: mutation.node,
|
|
37279
|
-
message: MESSAGE$
|
|
37511
|
+
message: MESSAGE$18
|
|
37280
37512
|
});
|
|
37281
37513
|
}
|
|
37282
37514
|
};
|
|
@@ -37683,7 +37915,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
37683
37915
|
});
|
|
37684
37916
|
//#endregion
|
|
37685
37917
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
37686
|
-
const MESSAGE$
|
|
37918
|
+
const MESSAGE$17 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
37687
37919
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
37688
37920
|
"onKeyDown",
|
|
37689
37921
|
"onKeyUp",
|
|
@@ -37802,7 +38034,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37802
38034
|
if (numeric === null) {
|
|
37803
38035
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
37804
38036
|
node: tabIndex,
|
|
37805
|
-
message: MESSAGE$
|
|
38037
|
+
message: MESSAGE$17
|
|
37806
38038
|
});
|
|
37807
38039
|
return;
|
|
37808
38040
|
}
|
|
@@ -37824,7 +38056,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37824
38056
|
if (!roleAttribute) {
|
|
37825
38057
|
context.report({
|
|
37826
38058
|
node: tabIndex,
|
|
37827
|
-
message: MESSAGE$
|
|
38059
|
+
message: MESSAGE$17
|
|
37828
38060
|
});
|
|
37829
38061
|
return;
|
|
37830
38062
|
}
|
|
@@ -37838,7 +38070,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37838
38070
|
}
|
|
37839
38071
|
context.report({
|
|
37840
38072
|
node: tabIndex,
|
|
37841
|
-
message: MESSAGE$
|
|
38073
|
+
message: MESSAGE$17
|
|
37842
38074
|
});
|
|
37843
38075
|
} };
|
|
37844
38076
|
}
|
|
@@ -39257,174 +39489,6 @@ const noPropCallbackInRender = defineRule({
|
|
|
39257
39489
|
} })
|
|
39258
39490
|
});
|
|
39259
39491
|
//#endregion
|
|
39260
|
-
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
39261
|
-
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
39262
|
-
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
39263
|
-
const expression = stripParenExpression(node);
|
|
39264
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39265
|
-
const propertyName = getStaticPropertyName(expression);
|
|
39266
|
-
const receiver = stripParenExpression(expression.object);
|
|
39267
|
-
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
39268
|
-
}
|
|
39269
|
-
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39270
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39271
|
-
const symbol = scopes.symbolFor(expression);
|
|
39272
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
39273
|
-
visitedSymbolIds.add(symbol.id);
|
|
39274
|
-
if (isImportedFromReact(symbol)) {
|
|
39275
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
39276
|
-
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
39277
|
-
}
|
|
39278
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39279
|
-
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
39280
|
-
};
|
|
39281
|
-
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39282
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
39283
|
-
visitedClassNodes.add(classNode);
|
|
39284
|
-
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39285
|
-
};
|
|
39286
|
-
//#endregion
|
|
39287
|
-
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
39288
|
-
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
39289
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
39290
|
-
let containsReactHookCall = false;
|
|
39291
|
-
walkAst(functionNode.body, (node) => {
|
|
39292
|
-
if (containsReactHookCall) return false;
|
|
39293
|
-
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
39294
|
-
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39295
|
-
containsReactHookCall = true;
|
|
39296
|
-
return false;
|
|
39297
|
-
}
|
|
39298
|
-
});
|
|
39299
|
-
return containsReactHookCall;
|
|
39300
|
-
};
|
|
39301
|
-
//#endregion
|
|
39302
|
-
//#region src/plugin/utils/function-returns-props-children.ts
|
|
39303
|
-
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
39304
|
-
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
39305
|
-
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
39306
|
-
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
39307
|
-
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
39308
|
-
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
39309
|
-
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
39310
|
-
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
39311
|
-
const propertyValue = stripParenExpression(property.value);
|
|
39312
|
-
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
39313
|
-
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
39314
|
-
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
39315
|
-
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
39316
|
-
}
|
|
39317
|
-
}
|
|
39318
|
-
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
39319
|
-
const candidate = stripParenExpression(expression);
|
|
39320
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
39321
|
-
const symbol = scopes.symbolFor(candidate);
|
|
39322
|
-
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
39323
|
-
}
|
|
39324
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
39325
|
-
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
39326
|
-
const receiver = stripParenExpression(candidate.object);
|
|
39327
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
39328
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
39329
|
-
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
39330
|
-
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
39331
|
-
}, controlFlow);
|
|
39332
|
-
};
|
|
39333
|
-
//#endregion
|
|
39334
|
-
//#region src/plugin/utils/function-returns-only-null.ts
|
|
39335
|
-
const isNullExpression = (expression) => {
|
|
39336
|
-
const candidate = stripParenExpression(expression);
|
|
39337
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39338
|
-
};
|
|
39339
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
39340
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
39341
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39342
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39343
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39344
|
-
};
|
|
39345
|
-
//#endregion
|
|
39346
|
-
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
39347
|
-
const findFactoryRoot = (node) => {
|
|
39348
|
-
const candidate = stripParenExpression(node);
|
|
39349
|
-
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
39350
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
39351
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
39352
|
-
return null;
|
|
39353
|
-
};
|
|
39354
|
-
const findFactoryPropertyName = (node) => {
|
|
39355
|
-
const candidate = stripParenExpression(node);
|
|
39356
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
39357
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
39358
|
-
return null;
|
|
39359
|
-
};
|
|
39360
|
-
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
39361
|
-
const candidate = stripParenExpression(expression);
|
|
39362
|
-
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
39363
|
-
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
39364
|
-
if (!factoryRoot) return false;
|
|
39365
|
-
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
39366
|
-
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
39367
|
-
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
39368
|
-
if (!symbol || symbol.kind !== "import") return false;
|
|
39369
|
-
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
39370
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
39371
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
39372
|
-
};
|
|
39373
|
-
//#endregion
|
|
39374
|
-
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
39375
|
-
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
39376
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
39377
|
-
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39378
|
-
const candidate = stripParenExpression(expression);
|
|
39379
|
-
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
39380
|
-
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
39381
|
-
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
39382
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
39383
|
-
const symbol = scopes.symbolFor(candidate);
|
|
39384
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
39385
|
-
visitedSymbolIds.add(symbol.id);
|
|
39386
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
39387
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
39388
|
-
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
39389
|
-
}
|
|
39390
|
-
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
39391
|
-
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
39392
|
-
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39393
|
-
const wrappedComponent = candidate.arguments[0];
|
|
39394
|
-
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
39395
|
-
}
|
|
39396
|
-
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
39397
|
-
const factory = candidate.arguments[0];
|
|
39398
|
-
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
39399
|
-
const unwrappedFactory = stripParenExpression(factory);
|
|
39400
|
-
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
39401
|
-
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
39402
|
-
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
39403
|
-
const returnedExpression = returnStatements[0]?.argument;
|
|
39404
|
-
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
39405
|
-
};
|
|
39406
|
-
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
39407
|
-
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
39408
|
-
for (const candidateSymbol of candidateSymbols) {
|
|
39409
|
-
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
39410
|
-
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
39411
|
-
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
39412
|
-
continue;
|
|
39413
|
-
}
|
|
39414
|
-
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
39415
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
39416
|
-
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
39417
|
-
continue;
|
|
39418
|
-
}
|
|
39419
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
39420
|
-
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
39421
|
-
continue;
|
|
39422
|
-
}
|
|
39423
|
-
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
39424
|
-
}
|
|
39425
|
-
return false;
|
|
39426
|
-
};
|
|
39427
|
-
//#endregion
|
|
39428
39492
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
39429
39493
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
39430
39494
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -39632,7 +39696,7 @@ const noRandomKey = defineRule({
|
|
|
39632
39696
|
});
|
|
39633
39697
|
//#endregion
|
|
39634
39698
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
39635
|
-
const MESSAGE$
|
|
39699
|
+
const MESSAGE$16 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
39636
39700
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
39637
39701
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
39638
39702
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -39658,13 +39722,13 @@ const noReactChildren = defineRule({
|
|
|
39658
39722
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
39659
39723
|
context.report({
|
|
39660
39724
|
node: calleeOuter,
|
|
39661
|
-
message: MESSAGE$
|
|
39725
|
+
message: MESSAGE$16
|
|
39662
39726
|
});
|
|
39663
39727
|
return;
|
|
39664
39728
|
}
|
|
39665
39729
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
39666
39730
|
node: calleeOuter,
|
|
39667
|
-
message: MESSAGE$
|
|
39731
|
+
message: MESSAGE$16
|
|
39668
39732
|
});
|
|
39669
39733
|
} })
|
|
39670
39734
|
});
|
|
@@ -40345,7 +40409,7 @@ const noRenderPropChildren = defineRule({
|
|
|
40345
40409
|
});
|
|
40346
40410
|
//#endregion
|
|
40347
40411
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
40348
|
-
const MESSAGE$
|
|
40412
|
+
const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
40349
40413
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
40350
40414
|
const isUsedAsReturnValue = (parent) => {
|
|
40351
40415
|
if (!parent) return false;
|
|
@@ -40363,7 +40427,7 @@ const noRenderReturnValue = defineRule({
|
|
|
40363
40427
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
40364
40428
|
context.report({
|
|
40365
40429
|
node: node.callee,
|
|
40366
|
-
message: MESSAGE$
|
|
40430
|
+
message: MESSAGE$15
|
|
40367
40431
|
});
|
|
40368
40432
|
} })
|
|
40369
40433
|
});
|
|
@@ -41172,7 +41236,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
41172
41236
|
});
|
|
41173
41237
|
//#endregion
|
|
41174
41238
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
41175
|
-
const MESSAGE$
|
|
41239
|
+
const MESSAGE$14 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
41176
41240
|
const noSetState = defineRule({
|
|
41177
41241
|
id: "no-set-state",
|
|
41178
41242
|
title: "Local class state forbidden",
|
|
@@ -41187,7 +41251,7 @@ const noSetState = defineRule({
|
|
|
41187
41251
|
if (!getParentComponent(node)) return;
|
|
41188
41252
|
context.report({
|
|
41189
41253
|
node: node.callee,
|
|
41190
|
-
message: MESSAGE$
|
|
41254
|
+
message: MESSAGE$14
|
|
41191
41255
|
});
|
|
41192
41256
|
} })
|
|
41193
41257
|
});
|
|
@@ -41557,7 +41621,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
41557
41621
|
};
|
|
41558
41622
|
//#endregion
|
|
41559
41623
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
41560
|
-
const MESSAGE$
|
|
41624
|
+
const MESSAGE$13 = "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.";
|
|
41561
41625
|
const DEFAULT_HANDLERS = [
|
|
41562
41626
|
"onClick",
|
|
41563
41627
|
"onMouseDown",
|
|
@@ -41672,7 +41736,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41672
41736
|
if (!roleAttribute || !roleAttribute.value) {
|
|
41673
41737
|
context.report({
|
|
41674
41738
|
node: node.name,
|
|
41675
|
-
message: MESSAGE$
|
|
41739
|
+
message: MESSAGE$13
|
|
41676
41740
|
});
|
|
41677
41741
|
return;
|
|
41678
41742
|
}
|
|
@@ -41682,7 +41746,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41682
41746
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
41683
41747
|
context.report({
|
|
41684
41748
|
node: node.name,
|
|
41685
|
-
message: MESSAGE$
|
|
41749
|
+
message: MESSAGE$13
|
|
41686
41750
|
});
|
|
41687
41751
|
return;
|
|
41688
41752
|
}
|
|
@@ -41690,7 +41754,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41690
41754
|
if (!settings.allowExpressionValues) {
|
|
41691
41755
|
context.report({
|
|
41692
41756
|
node: node.name,
|
|
41693
|
-
message: MESSAGE$
|
|
41757
|
+
message: MESSAGE$13
|
|
41694
41758
|
});
|
|
41695
41759
|
return;
|
|
41696
41760
|
}
|
|
@@ -41698,7 +41762,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41698
41762
|
if (isStaticNullishExpression(expression)) {
|
|
41699
41763
|
context.report({
|
|
41700
41764
|
node: node.name,
|
|
41701
|
-
message: MESSAGE$
|
|
41765
|
+
message: MESSAGE$13
|
|
41702
41766
|
});
|
|
41703
41767
|
return;
|
|
41704
41768
|
}
|
|
@@ -41708,7 +41772,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41708
41772
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
41709
41773
|
context.report({
|
|
41710
41774
|
node: node.name,
|
|
41711
|
-
message: MESSAGE$
|
|
41775
|
+
message: MESSAGE$13
|
|
41712
41776
|
});
|
|
41713
41777
|
return;
|
|
41714
41778
|
}
|
|
@@ -41716,7 +41780,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41716
41780
|
}
|
|
41717
41781
|
context.report({
|
|
41718
41782
|
node: node.name,
|
|
41719
|
-
message: MESSAGE$
|
|
41783
|
+
message: MESSAGE$13
|
|
41720
41784
|
});
|
|
41721
41785
|
} };
|
|
41722
41786
|
}
|
|
@@ -41822,7 +41886,7 @@ const noStringRefs = defineRule({
|
|
|
41822
41886
|
});
|
|
41823
41887
|
//#endregion
|
|
41824
41888
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
41825
|
-
const MESSAGE$
|
|
41889
|
+
const MESSAGE$12 = "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)`).";
|
|
41826
41890
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
41827
41891
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
41828
41892
|
const noSyncXhr = defineRule({
|
|
@@ -41843,7 +41907,7 @@ const noSyncXhr = defineRule({
|
|
|
41843
41907
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
41844
41908
|
context.report({
|
|
41845
41909
|
node,
|
|
41846
|
-
message: MESSAGE$
|
|
41910
|
+
message: MESSAGE$12
|
|
41847
41911
|
});
|
|
41848
41912
|
};
|
|
41849
41913
|
return {
|
|
@@ -41868,7 +41932,7 @@ const noSyncXhr = defineRule({
|
|
|
41868
41932
|
});
|
|
41869
41933
|
//#endregion
|
|
41870
41934
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
41871
|
-
const MESSAGE$
|
|
41935
|
+
const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
|
|
41872
41936
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
41873
41937
|
let ancestor = node.parent;
|
|
41874
41938
|
while (ancestor) {
|
|
@@ -41956,7 +42020,7 @@ const noThisInSfc = defineRule({
|
|
|
41956
42020
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
41957
42021
|
context.report({
|
|
41958
42022
|
node,
|
|
41959
|
-
message: MESSAGE$
|
|
42023
|
+
message: MESSAGE$11
|
|
41960
42024
|
});
|
|
41961
42025
|
} };
|
|
41962
42026
|
}
|
|
@@ -43837,6 +43901,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43837
43901
|
severity: "warn",
|
|
43838
43902
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
43839
43903
|
category: "Performance",
|
|
43904
|
+
tags: ["react-jsx-only"],
|
|
43840
43905
|
create: (context) => {
|
|
43841
43906
|
const settings = resolveSettings$8(context.settings);
|
|
43842
43907
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -44103,7 +44168,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
44103
44168
|
//#endregion
|
|
44104
44169
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
44105
44170
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
44106
|
-
const MESSAGE$
|
|
44171
|
+
const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
44107
44172
|
const resolveSettings$7 = (settings) => {
|
|
44108
44173
|
const reactDoctor = settings?.["react-doctor"];
|
|
44109
44174
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -44137,7 +44202,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
44137
44202
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
44138
44203
|
context.report({
|
|
44139
44204
|
node: node.callee,
|
|
44140
|
-
message: MESSAGE$
|
|
44205
|
+
message: MESSAGE$10
|
|
44141
44206
|
});
|
|
44142
44207
|
} };
|
|
44143
44208
|
}
|
|
@@ -45133,7 +45198,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
45133
45198
|
});
|
|
45134
45199
|
//#endregion
|
|
45135
45200
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
45136
|
-
const MESSAGE$
|
|
45201
|
+
const MESSAGE$9 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
|
|
45137
45202
|
const preactPreferOndblclick = defineRule({
|
|
45138
45203
|
id: "preact-prefer-ondblclick",
|
|
45139
45204
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -45148,7 +45213,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
45148
45213
|
if (!onDoubleClickAttribute) return;
|
|
45149
45214
|
context.report({
|
|
45150
45215
|
node: onDoubleClickAttribute,
|
|
45151
|
-
message: MESSAGE$
|
|
45216
|
+
message: MESSAGE$9
|
|
45152
45217
|
});
|
|
45153
45218
|
} })
|
|
45154
45219
|
});
|
|
@@ -45399,7 +45464,7 @@ const preferExplicitVariants = defineRule({
|
|
|
45399
45464
|
});
|
|
45400
45465
|
//#endregion
|
|
45401
45466
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
45402
|
-
const MESSAGE$
|
|
45467
|
+
const MESSAGE$8 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
|
|
45403
45468
|
const resolveSettings$4 = (settings) => {
|
|
45404
45469
|
const reactDoctor = settings?.["react-doctor"];
|
|
45405
45470
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -45438,7 +45503,7 @@ const preferFunctionComponent = defineRule({
|
|
|
45438
45503
|
const reportNode = node.id ?? node;
|
|
45439
45504
|
context.report({
|
|
45440
45505
|
node: reportNode,
|
|
45441
|
-
message: MESSAGE$
|
|
45506
|
+
message: MESSAGE$8
|
|
45442
45507
|
});
|
|
45443
45508
|
};
|
|
45444
45509
|
return {
|
|
@@ -47547,7 +47612,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
47547
47612
|
});
|
|
47548
47613
|
//#endregion
|
|
47549
47614
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
47550
|
-
const MESSAGE$
|
|
47615
|
+
const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
|
|
47551
47616
|
const reactInJsxScope = defineRule({
|
|
47552
47617
|
id: "react-in-jsx-scope",
|
|
47553
47618
|
title: "React not in scope for JSX",
|
|
@@ -47559,19 +47624,190 @@ const reactInJsxScope = defineRule({
|
|
|
47559
47624
|
if (findVariableInitializer(node, "React")) return;
|
|
47560
47625
|
context.report({
|
|
47561
47626
|
node: node.name,
|
|
47562
|
-
message: MESSAGE$
|
|
47627
|
+
message: MESSAGE$7
|
|
47563
47628
|
});
|
|
47564
47629
|
},
|
|
47565
47630
|
JSXFragment(node) {
|
|
47566
47631
|
if (findVariableInitializer(node, "React")) return;
|
|
47567
47632
|
context.report({
|
|
47568
47633
|
node: node.openingFragment,
|
|
47569
|
-
message: MESSAGE$
|
|
47634
|
+
message: MESSAGE$7
|
|
47570
47635
|
});
|
|
47571
47636
|
}
|
|
47572
47637
|
})
|
|
47573
47638
|
});
|
|
47574
47639
|
//#endregion
|
|
47640
|
+
//#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
|
|
47641
|
+
const MESSAGE$6 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
|
|
47642
|
+
const REACT_MARKDOWN_MODULE = "react-markdown";
|
|
47643
|
+
const REHYPE_RAW_MODULE = "rehype-raw";
|
|
47644
|
+
const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
|
|
47645
|
+
const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
|
|
47646
|
+
const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
|
|
47647
|
+
const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
|
|
47648
|
+
const DEFAULT_EXPORT_NAMES = new Set(["default"]);
|
|
47649
|
+
const getImportDeclaration = (symbol) => {
|
|
47650
|
+
if (symbol.kind !== "import") return null;
|
|
47651
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
47652
|
+
return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
|
|
47653
|
+
};
|
|
47654
|
+
const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
|
|
47655
|
+
const isDefaultImportSymbol = (symbol, moduleName) => {
|
|
47656
|
+
if (!isImportFromModule(symbol, moduleName)) return false;
|
|
47657
|
+
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
47658
|
+
};
|
|
47659
|
+
const resolveImportedIdentifier = (node, scopes) => {
|
|
47660
|
+
if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
|
|
47661
|
+
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
47662
|
+
return symbol?.kind === "import" ? symbol : null;
|
|
47663
|
+
};
|
|
47664
|
+
const getStaticMemberParts = (node) => {
|
|
47665
|
+
if (isNodeOfType(node, "MemberExpression")) {
|
|
47666
|
+
if (node.computed || !isNodeOfType(node.property, "Identifier")) return null;
|
|
47667
|
+
return {
|
|
47668
|
+
object: node.object,
|
|
47669
|
+
propertyName: node.property.name
|
|
47670
|
+
};
|
|
47671
|
+
}
|
|
47672
|
+
if (isNodeOfType(node, "JSXMemberExpression")) {
|
|
47673
|
+
if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
|
|
47674
|
+
return {
|
|
47675
|
+
object: node.object,
|
|
47676
|
+
propertyName: node.property.name
|
|
47677
|
+
};
|
|
47678
|
+
}
|
|
47679
|
+
return null;
|
|
47680
|
+
};
|
|
47681
|
+
const isNamespaceMemberFromModule = (node, moduleName, memberNames, scopes) => {
|
|
47682
|
+
const memberParts = getStaticMemberParts(node);
|
|
47683
|
+
if (!memberParts || !memberNames.has(memberParts.propertyName)) return false;
|
|
47684
|
+
const namespaceSymbol = resolveImportedIdentifier(memberParts.object, scopes);
|
|
47685
|
+
return Boolean(namespaceSymbol && isImportFromModule(namespaceSymbol, moduleName) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
|
|
47686
|
+
};
|
|
47687
|
+
const isReactMarkdownComponent = (node, scopes) => {
|
|
47688
|
+
if (isNodeOfType(node, "JSXIdentifier")) {
|
|
47689
|
+
const symbol = resolveImportedIdentifier(node, scopes);
|
|
47690
|
+
if (!symbol || !isImportFromModule(symbol, REACT_MARKDOWN_MODULE)) return false;
|
|
47691
|
+
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return true;
|
|
47692
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
47693
|
+
return importedName === "default" || Boolean(importedName && REACT_MARKDOWN_NAMED_EXPORTS.has(importedName));
|
|
47694
|
+
}
|
|
47695
|
+
return isNamespaceMemberFromModule(node, REACT_MARKDOWN_MODULE, REACT_MARKDOWN_NAMESPACE_EXPORTS, scopes);
|
|
47696
|
+
};
|
|
47697
|
+
const isPluginFromModule = (rawNode, moduleName, scopes, visitedSymbolIds) => {
|
|
47698
|
+
const node = stripParenExpression(rawNode);
|
|
47699
|
+
if (isNodeOfType(node, "Identifier")) {
|
|
47700
|
+
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
47701
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
47702
|
+
if (isDefaultImportSymbol(symbol, moduleName)) return true;
|
|
47703
|
+
if (symbol.kind !== "const" || !symbol.initializer) return false;
|
|
47704
|
+
visitedSymbolIds.add(symbol.id);
|
|
47705
|
+
return isPluginFromModule(symbol.initializer, moduleName, scopes, visitedSymbolIds);
|
|
47706
|
+
}
|
|
47707
|
+
if (isNamespaceMemberFromModule(node, moduleName, DEFAULT_EXPORT_NAMES, scopes)) return true;
|
|
47708
|
+
if (!isNodeOfType(node, "ArrayExpression")) return false;
|
|
47709
|
+
for (const element of node.elements) {
|
|
47710
|
+
if (!element || isNodeOfType(element, "SpreadElement")) continue;
|
|
47711
|
+
return isPluginFromModule(element, moduleName, scopes, visitedSymbolIds);
|
|
47712
|
+
}
|
|
47713
|
+
return false;
|
|
47714
|
+
};
|
|
47715
|
+
const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
|
|
47716
|
+
const node = stripParenExpression(rawNode);
|
|
47717
|
+
if (isNodeOfType(node, "Identifier")) {
|
|
47718
|
+
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
47719
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
47720
|
+
visitedSymbolIds.add(symbol.id);
|
|
47721
|
+
return collectPluginEntries(symbol.initializer, scopes, visitedSymbolIds);
|
|
47722
|
+
}
|
|
47723
|
+
if (!isNodeOfType(node, "ArrayExpression")) return null;
|
|
47724
|
+
const entries = [];
|
|
47725
|
+
for (const element of node.elements) {
|
|
47726
|
+
if (!element) continue;
|
|
47727
|
+
if (isNodeOfType(element, "SpreadElement")) {
|
|
47728
|
+
const spreadEntries = collectPluginEntries(element.argument, scopes, new Set(visitedSymbolIds));
|
|
47729
|
+
if (spreadEntries === null) return null;
|
|
47730
|
+
entries.push(...spreadEntries);
|
|
47731
|
+
continue;
|
|
47732
|
+
}
|
|
47733
|
+
entries.push(element);
|
|
47734
|
+
}
|
|
47735
|
+
return entries;
|
|
47736
|
+
};
|
|
47737
|
+
const findEffectiveExplicitAttribute = (attributes, attributeName) => {
|
|
47738
|
+
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
47739
|
+
const attribute = attributes[attributeIndex];
|
|
47740
|
+
if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
|
|
47741
|
+
if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
|
|
47742
|
+
}
|
|
47743
|
+
return null;
|
|
47744
|
+
};
|
|
47745
|
+
const getAttributeExpression = (attribute) => {
|
|
47746
|
+
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
47747
|
+
return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
|
|
47748
|
+
};
|
|
47749
|
+
const isDomPurifyNamespace = (node, scopes) => {
|
|
47750
|
+
const symbol = resolveImportedIdentifier(node, scopes);
|
|
47751
|
+
if (!symbol) return false;
|
|
47752
|
+
const importDeclaration = getImportDeclaration(symbol);
|
|
47753
|
+
if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
|
|
47754
|
+
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
47755
|
+
};
|
|
47756
|
+
const isDomPurifySanitizeCall = (node, scopes) => {
|
|
47757
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
47758
|
+
const memberParts = getStaticMemberParts(stripParenExpression(node.callee));
|
|
47759
|
+
return Boolean(memberParts && memberParts.propertyName === "sanitize" && isDomPurifyNamespace(memberParts.object, scopes));
|
|
47760
|
+
};
|
|
47761
|
+
const isStaticOrSanitizedMarkdownExpression = (rawNode, scopes, visitedSymbolIds) => {
|
|
47762
|
+
const node = stripParenExpression(rawNode);
|
|
47763
|
+
if (isNodeOfType(node, "Literal")) return true;
|
|
47764
|
+
if (isNodeOfType(node, "TemplateLiteral")) return node.expressions.every((expression) => isStaticOrSanitizedMarkdownExpression(expression, scopes, new Set(visitedSymbolIds)));
|
|
47765
|
+
if (isDomPurifySanitizeCall(node, scopes)) return true;
|
|
47766
|
+
if (isNodeOfType(node, "ConditionalExpression")) return isStaticOrSanitizedMarkdownExpression(node.consequent, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.alternate, scopes, new Set(visitedSymbolIds));
|
|
47767
|
+
if (isNodeOfType(node, "BinaryExpression") && node.operator === "+") return isStaticOrSanitizedMarkdownExpression(node.left, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.right, scopes, new Set(visitedSymbolIds));
|
|
47768
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
47769
|
+
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
47770
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
47771
|
+
visitedSymbolIds.add(symbol.id);
|
|
47772
|
+
return isStaticOrSanitizedMarkdownExpression(symbol.initializer, scopes, visitedSymbolIds);
|
|
47773
|
+
};
|
|
47774
|
+
const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
|
|
47775
|
+
const jsxElement = openingElement.parent;
|
|
47776
|
+
if (!isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
47777
|
+
const meaningfulChildren = jsxElement.children.filter((child) => !isNodeOfType(child, "JSXText") || child.value.trim().length > 0);
|
|
47778
|
+
if (meaningfulChildren.length > 0) return meaningfulChildren.some((child) => {
|
|
47779
|
+
if (isNodeOfType(child, "JSXText")) return false;
|
|
47780
|
+
if (!isNodeOfType(child, "JSXExpressionContainer")) return true;
|
|
47781
|
+
if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
|
|
47782
|
+
return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
|
|
47783
|
+
});
|
|
47784
|
+
const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
|
|
47785
|
+
if (!childrenAttribute) return false;
|
|
47786
|
+
const childrenExpression = getAttributeExpression(childrenAttribute);
|
|
47787
|
+
return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
|
|
47788
|
+
};
|
|
47789
|
+
const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
47790
|
+
id: "react-markdown-unsanitized-raw-html",
|
|
47791
|
+
title: "Unsanitized raw HTML in React Markdown",
|
|
47792
|
+
severity: "warn",
|
|
47793
|
+
recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
|
|
47794
|
+
create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
|
|
47795
|
+
if (!isReactMarkdownComponent(node.name, context.scopes)) return;
|
|
47796
|
+
const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
|
|
47797
|
+
if (!pluginsAttribute) return;
|
|
47798
|
+
const pluginsExpression = getAttributeExpression(pluginsAttribute);
|
|
47799
|
+
if (!pluginsExpression) return;
|
|
47800
|
+
const pluginEntries = collectPluginEntries(pluginsExpression, context.scopes, /* @__PURE__ */ new Set());
|
|
47801
|
+
if (pluginEntries === null) return;
|
|
47802
|
+
if (!pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_RAW_MODULE, context.scopes, /* @__PURE__ */ new Set()))) return;
|
|
47803
|
+
if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
|
|
47804
|
+
context.report({
|
|
47805
|
+
node,
|
|
47806
|
+
message: MESSAGE$6
|
|
47807
|
+
});
|
|
47808
|
+
} }))
|
|
47809
|
+
});
|
|
47810
|
+
//#endregion
|
|
47575
47811
|
//#region src/plugin/utils/collect-react-redux-selector-aliases.ts
|
|
47576
47812
|
const REACT_REDUX_MODULE = "react-redux";
|
|
47577
47813
|
const collectReactReduxSelectorAliases = (programRoot) => {
|
|
@@ -62805,6 +63041,17 @@ const reactDoctorRules = [
|
|
|
62805
63041
|
requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
|
|
62806
63042
|
}
|
|
62807
63043
|
},
|
|
63044
|
+
{
|
|
63045
|
+
key: "react-doctor/react-markdown-unsanitized-raw-html",
|
|
63046
|
+
id: "react-markdown-unsanitized-raw-html",
|
|
63047
|
+
source: "react-doctor",
|
|
63048
|
+
originallyExternal: false,
|
|
63049
|
+
rule: {
|
|
63050
|
+
...reactMarkdownUnsanitizedRawHtml,
|
|
63051
|
+
framework: "global",
|
|
63052
|
+
category: "Security"
|
|
63053
|
+
}
|
|
63054
|
+
},
|
|
62808
63055
|
{
|
|
62809
63056
|
key: "react-doctor/redux-useselector-inline-derivation",
|
|
62810
63057
|
id: "redux-useselector-inline-derivation",
|