oxlint-plugin-react-doctor 0.7.7-dev.fbc804e → 0.7.7
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 +0 -46
- package/dist/index.js +413 -1320
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,13 +7,6 @@ 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
|
|
17
10
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
18
11
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
19
12
|
"solid-js",
|
|
@@ -28,33 +21,17 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
|
28
21
|
"vidode"
|
|
29
22
|
]);
|
|
30
23
|
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
|
-
];
|
|
36
24
|
const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
|
|
37
|
-
const
|
|
38
|
-
let hasNonReactRuntime = false;
|
|
39
|
-
let hasReactRuntime = false;
|
|
25
|
+
const fileImportsNonReactJsxDialect = (program) => {
|
|
40
26
|
for (const statement of program.body) {
|
|
41
27
|
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
42
|
-
const
|
|
43
|
-
if (isTypeOnlyImport(importDeclaration)) continue;
|
|
44
|
-
const source = importDeclaration.source;
|
|
28
|
+
const source = statement.source;
|
|
45
29
|
const value = source && typeof source.value === "string" ? source.value : null;
|
|
46
30
|
if (!value) continue;
|
|
47
|
-
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)
|
|
48
|
-
if (startsWithAny(value,
|
|
31
|
+
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)) return true;
|
|
32
|
+
if (startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) return true;
|
|
49
33
|
}
|
|
50
|
-
return
|
|
51
|
-
hasNonReactRuntime,
|
|
52
|
-
hasReactRuntime
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
const fileImportsNonReactJsxDialect = (program) => {
|
|
56
|
-
const runtimeImports = collectJsxRuntimeImports(program);
|
|
57
|
-
return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
34
|
+
return false;
|
|
58
35
|
};
|
|
59
36
|
const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
|
|
60
37
|
for (const attribute of openingNode.attributes) {
|
|
@@ -267,7 +244,6 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
|
|
|
267
244
|
const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
268
245
|
const innerVisitors = create(context);
|
|
269
246
|
let fileIsNonReactJsx = false;
|
|
270
|
-
let fileImportsReactRuntime = false;
|
|
271
247
|
const wrappedVisitors = {};
|
|
272
248
|
for (const [key, visitor] of Object.entries(innerVisitors)) {
|
|
273
249
|
if (typeof visitor !== "function") {
|
|
@@ -280,16 +256,14 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
280
256
|
}
|
|
281
257
|
if (key === "Program") {
|
|
282
258
|
wrappedVisitors.Program = (node) => {
|
|
283
|
-
|
|
284
|
-
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
285
|
-
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
259
|
+
fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
|
|
286
260
|
visitor(node);
|
|
287
261
|
};
|
|
288
262
|
continue;
|
|
289
263
|
}
|
|
290
264
|
if (key === "JSXOpeningElement") {
|
|
291
265
|
wrappedVisitors.JSXOpeningElement = (node) => {
|
|
292
|
-
if (!
|
|
266
|
+
if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
293
267
|
if (fileIsNonReactJsx) return;
|
|
294
268
|
visitor(node);
|
|
295
269
|
};
|
|
@@ -301,9 +275,7 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
301
275
|
};
|
|
302
276
|
}
|
|
303
277
|
if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
|
|
304
|
-
|
|
305
|
-
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
306
|
-
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
278
|
+
fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
|
|
307
279
|
};
|
|
308
280
|
return wrappedVisitors;
|
|
309
281
|
});
|
|
@@ -2340,7 +2312,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2340
2312
|
});
|
|
2341
2313
|
//#endregion
|
|
2342
2314
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
2343
|
-
const MESSAGE$
|
|
2315
|
+
const MESSAGE$61 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
2344
2316
|
const isTransComponentsTemplate = (node) => {
|
|
2345
2317
|
let current = node.parent;
|
|
2346
2318
|
while (current) {
|
|
@@ -2376,7 +2348,7 @@ const anchorHasContent = defineRule({
|
|
|
2376
2348
|
if (isTransComponentsTemplate(node)) return;
|
|
2377
2349
|
context.report({
|
|
2378
2350
|
node: opening.name,
|
|
2379
|
-
message: MESSAGE$
|
|
2351
|
+
message: MESSAGE$61
|
|
2380
2352
|
});
|
|
2381
2353
|
} };
|
|
2382
2354
|
}
|
|
@@ -2808,7 +2780,7 @@ const parseJsxValue = (value) => {
|
|
|
2808
2780
|
};
|
|
2809
2781
|
//#endregion
|
|
2810
2782
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2811
|
-
const MESSAGE$
|
|
2783
|
+
const MESSAGE$60 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2812
2784
|
const mayBeContentEditable = (node) => {
|
|
2813
2785
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
|
|
2814
2786
|
if (!attribute) return false;
|
|
@@ -2839,7 +2811,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2839
2811
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2840
2812
|
context.report({
|
|
2841
2813
|
node: node.name,
|
|
2842
|
-
message: MESSAGE$
|
|
2814
|
+
message: MESSAGE$60
|
|
2843
2815
|
});
|
|
2844
2816
|
return;
|
|
2845
2817
|
}
|
|
@@ -2847,7 +2819,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2847
2819
|
if (mayBeContentEditable(node)) return;
|
|
2848
2820
|
context.report({
|
|
2849
2821
|
node: node.name,
|
|
2850
|
-
message: MESSAGE$
|
|
2822
|
+
message: MESSAGE$60
|
|
2851
2823
|
});
|
|
2852
2824
|
} })
|
|
2853
2825
|
});
|
|
@@ -5252,7 +5224,7 @@ const asyncParallel = defineRule({
|
|
|
5252
5224
|
});
|
|
5253
5225
|
//#endregion
|
|
5254
5226
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
5255
|
-
const MESSAGE$
|
|
5227
|
+
const MESSAGE$59 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
|
|
5256
5228
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
5257
5229
|
const STORAGE_GLOBALS = new Set([
|
|
5258
5230
|
"window",
|
|
@@ -5317,7 +5289,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5317
5289
|
if (keyString === null || !isAuthCredentialKey(keyString)) return;
|
|
5318
5290
|
context.report({
|
|
5319
5291
|
node,
|
|
5320
|
-
message: MESSAGE$
|
|
5292
|
+
message: MESSAGE$59
|
|
5321
5293
|
});
|
|
5322
5294
|
},
|
|
5323
5295
|
AssignmentExpression(node) {
|
|
@@ -5328,7 +5300,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5328
5300
|
if (!propertyName || !isAuthCredentialKey(propertyName)) return;
|
|
5329
5301
|
context.report({
|
|
5330
5302
|
node: target,
|
|
5331
|
-
message: MESSAGE$
|
|
5303
|
+
message: MESSAGE$59
|
|
5332
5304
|
});
|
|
5333
5305
|
}
|
|
5334
5306
|
}))
|
|
@@ -5467,7 +5439,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
|
|
|
5467
5439
|
const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
|
|
5468
5440
|
const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
|
|
5469
5441
|
const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
|
|
5470
|
-
const MESSAGE$
|
|
5442
|
+
const MESSAGE$58 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
|
|
5471
5443
|
const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
|
|
5472
5444
|
const scanWorkflowContent = (content) => {
|
|
5473
5445
|
const lines = content.split("\n");
|
|
@@ -5512,7 +5484,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5512
5484
|
const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
|
|
5513
5485
|
const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
|
|
5514
5486
|
return [{
|
|
5515
|
-
message: MESSAGE$
|
|
5487
|
+
message: MESSAGE$58,
|
|
5516
5488
|
line: step.startLineIndex + installLineOffset + 1,
|
|
5517
5489
|
column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
|
|
5518
5490
|
}];
|
|
@@ -5522,7 +5494,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5522
5494
|
const scanNonWorkflowConfig = scanByPattern({
|
|
5523
5495
|
shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
|
|
5524
5496
|
pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
|
|
5525
|
-
message: MESSAGE$
|
|
5497
|
+
message: MESSAGE$58
|
|
5526
5498
|
});
|
|
5527
5499
|
const scan = (file) => {
|
|
5528
5500
|
if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
|
|
@@ -6019,17 +5991,15 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
6019
5991
|
//#endregion
|
|
6020
5992
|
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
6021
5993
|
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
6022
|
-
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition")
|
|
6023
|
-
const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
|
|
5994
|
+
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition")) return null;
|
|
6024
5995
|
if (node.computed) {
|
|
6025
|
-
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
6026
|
-
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
5996
|
+
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
6027
5997
|
return null;
|
|
6028
5998
|
}
|
|
6029
|
-
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
6030
|
-
if (isNodeOfType(key, "Literal")) {
|
|
6031
|
-
if (typeof key.value === "string") return key.value;
|
|
6032
|
-
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
5999
|
+
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
6000
|
+
if (isNodeOfType(node.key, "Literal")) {
|
|
6001
|
+
if (typeof node.key.value === "string") return node.key.value;
|
|
6002
|
+
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
6033
6003
|
}
|
|
6034
6004
|
return null;
|
|
6035
6005
|
};
|
|
@@ -6291,7 +6261,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
6291
6261
|
};
|
|
6292
6262
|
//#endregion
|
|
6293
6263
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
6294
|
-
const MESSAGE$
|
|
6264
|
+
const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
6295
6265
|
const KEY_HANDLERS = [
|
|
6296
6266
|
"onKeyUp",
|
|
6297
6267
|
"onKeyDown",
|
|
@@ -6502,7 +6472,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6502
6472
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
|
|
6503
6473
|
context.report({
|
|
6504
6474
|
node: node.name,
|
|
6505
|
-
message: MESSAGE$
|
|
6475
|
+
message: MESSAGE$57
|
|
6506
6476
|
});
|
|
6507
6477
|
} };
|
|
6508
6478
|
}
|
|
@@ -8755,7 +8725,7 @@ const getClassNameLiteral = (classAttribute) => {
|
|
|
8755
8725
|
};
|
|
8756
8726
|
//#endregion
|
|
8757
8727
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
8758
|
-
const MESSAGE$
|
|
8728
|
+
const MESSAGE$56 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
8759
8729
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
8760
8730
|
"td",
|
|
8761
8731
|
"th",
|
|
@@ -9193,7 +9163,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9193
9163
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
9194
9164
|
context.report({
|
|
9195
9165
|
node: candidate.opening,
|
|
9196
|
-
message: MESSAGE$
|
|
9166
|
+
message: MESSAGE$56
|
|
9197
9167
|
});
|
|
9198
9168
|
}
|
|
9199
9169
|
}
|
|
@@ -9954,7 +9924,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
9954
9924
|
});
|
|
9955
9925
|
//#endregion
|
|
9956
9926
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
9957
|
-
const MESSAGE$
|
|
9927
|
+
const MESSAGE$55 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
9958
9928
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
9959
9929
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
9960
9930
|
"aria-label",
|
|
@@ -9979,7 +9949,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
9979
9949
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
9980
9950
|
context.report({
|
|
9981
9951
|
node: node.name,
|
|
9982
|
-
message: MESSAGE$
|
|
9952
|
+
message: MESSAGE$55
|
|
9983
9953
|
});
|
|
9984
9954
|
} };
|
|
9985
9955
|
}
|
|
@@ -10019,7 +9989,7 @@ const isEs6Component = (node) => {
|
|
|
10019
9989
|
};
|
|
10020
9990
|
//#endregion
|
|
10021
9991
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
10022
|
-
const MESSAGE$
|
|
9992
|
+
const MESSAGE$54 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
10023
9993
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
10024
9994
|
"observer",
|
|
10025
9995
|
"lazy",
|
|
@@ -10212,7 +10182,7 @@ const displayName = defineRule({
|
|
|
10212
10182
|
const reportAt = (node) => {
|
|
10213
10183
|
context.report({
|
|
10214
10184
|
node,
|
|
10215
|
-
message: MESSAGE$
|
|
10185
|
+
message: MESSAGE$54
|
|
10216
10186
|
});
|
|
10217
10187
|
};
|
|
10218
10188
|
return {
|
|
@@ -11033,14 +11003,6 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
|
|
|
11033
11003
|
"catch",
|
|
11034
11004
|
"finally"
|
|
11035
11005
|
]);
|
|
11036
|
-
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11037
|
-
const getPromiseChainCallForCallback = (candidate) => {
|
|
11038
|
-
let callbackContainer = candidate.parent;
|
|
11039
|
-
while (callbackContainer && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callbackContainer.type)) callbackContainer = callbackContainer.parent;
|
|
11040
|
-
if (!isNodeOfType(callbackContainer, "CallExpression")) return null;
|
|
11041
|
-
if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
|
|
11042
|
-
return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
|
|
11043
|
-
};
|
|
11044
11006
|
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
11045
11007
|
const invokedFunctions = new Set([effectCallback]);
|
|
11046
11008
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
@@ -11052,6 +11014,7 @@ const collectEffectInvokedFunctions = (effectCallback) => {
|
|
|
11052
11014
|
invokedFunctions.add(strippedCandidate);
|
|
11053
11015
|
pendingFunctions.push(strippedCandidate);
|
|
11054
11016
|
};
|
|
11017
|
+
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11055
11018
|
while (pendingFunctions.length > 0) {
|
|
11056
11019
|
const currentFunction = pendingFunctions.pop();
|
|
11057
11020
|
if (!currentFunction) break;
|
|
@@ -11259,50 +11222,6 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
|
|
|
11259
11222
|
return true;
|
|
11260
11223
|
};
|
|
11261
11224
|
//#endregion
|
|
11262
|
-
//#region src/plugin/utils/is-node-reachable-within-function.ts
|
|
11263
|
-
const isInsideStaticallyUnreachableBranch = (node) => {
|
|
11264
|
-
let child = node;
|
|
11265
|
-
let parent = node.parent;
|
|
11266
|
-
while (parent) {
|
|
11267
|
-
if (isNodeOfType(parent, "IfStatement") && isNodeOfType(parent.test, "Literal")) {
|
|
11268
|
-
if (parent.test.value === false && parent.consequent === child) return true;
|
|
11269
|
-
if (parent.test.value === true && parent.alternate === child) return true;
|
|
11270
|
-
}
|
|
11271
|
-
if (isNodeOfType(parent, "ConditionalExpression") && isNodeOfType(parent.test, "Literal")) {
|
|
11272
|
-
if (parent.test.value === false && parent.consequent === child) return true;
|
|
11273
|
-
if (parent.test.value === true && parent.alternate === child) return true;
|
|
11274
|
-
}
|
|
11275
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) {
|
|
11276
|
-
if (isNodeOfType(parent.left, "Literal") && (parent.operator === "&&" && !parent.left.value || parent.operator === "||" && Boolean(parent.left.value))) return true;
|
|
11277
|
-
}
|
|
11278
|
-
child = parent;
|
|
11279
|
-
parent = parent.parent;
|
|
11280
|
-
}
|
|
11281
|
-
return false;
|
|
11282
|
-
};
|
|
11283
|
-
const isNodeReachableWithinFunction = (node, context) => {
|
|
11284
|
-
if (isInsideStaticallyUnreachableBranch(node)) return false;
|
|
11285
|
-
const owner = context.cfg.enclosingFunction(node);
|
|
11286
|
-
if (!owner) return true;
|
|
11287
|
-
const functionCfg = context.cfg.cfgFor(owner);
|
|
11288
|
-
if (!functionCfg) return true;
|
|
11289
|
-
const targetBlock = functionCfg.blockOf(node);
|
|
11290
|
-
if (!targetBlock) return true;
|
|
11291
|
-
const visitedBlocks = new Set([functionCfg.entry]);
|
|
11292
|
-
const pendingBlocks = [functionCfg.entry];
|
|
11293
|
-
while (pendingBlocks.length > 0) {
|
|
11294
|
-
const currentBlock = pendingBlocks.pop();
|
|
11295
|
-
if (!currentBlock) break;
|
|
11296
|
-
if (currentBlock === targetBlock) return true;
|
|
11297
|
-
for (const edge of currentBlock.successors) {
|
|
11298
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
11299
|
-
visitedBlocks.add(edge.to);
|
|
11300
|
-
pendingBlocks.push(edge.to);
|
|
11301
|
-
}
|
|
11302
|
-
}
|
|
11303
|
-
return false;
|
|
11304
|
-
};
|
|
11305
|
-
//#endregion
|
|
11306
11225
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
11307
11226
|
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
11308
11227
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
@@ -11431,6 +11350,27 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
11431
11350
|
});
|
|
11432
11351
|
return usages.filter((usage) => isNodeReachableWithinFunction(usage.node, context));
|
|
11433
11352
|
};
|
|
11353
|
+
const isNodeReachableWithinFunction = (node, context) => {
|
|
11354
|
+
const owner = context.cfg.enclosingFunction(node);
|
|
11355
|
+
if (!owner) return true;
|
|
11356
|
+
const functionCfg = context.cfg.cfgFor(owner);
|
|
11357
|
+
if (!functionCfg) return true;
|
|
11358
|
+
const targetBlock = functionCfg.blockOf(node);
|
|
11359
|
+
if (!targetBlock) return true;
|
|
11360
|
+
const visitedBlocks = new Set([functionCfg.entry]);
|
|
11361
|
+
const pendingBlocks = [functionCfg.entry];
|
|
11362
|
+
while (pendingBlocks.length > 0) {
|
|
11363
|
+
const currentBlock = pendingBlocks.pop();
|
|
11364
|
+
if (!currentBlock) break;
|
|
11365
|
+
if (currentBlock === targetBlock) return true;
|
|
11366
|
+
for (const edge of currentBlock.successors) {
|
|
11367
|
+
if (visitedBlocks.has(edge.to)) continue;
|
|
11368
|
+
visitedBlocks.add(edge.to);
|
|
11369
|
+
pendingBlocks.push(edge.to);
|
|
11370
|
+
}
|
|
11371
|
+
}
|
|
11372
|
+
return false;
|
|
11373
|
+
};
|
|
11434
11374
|
const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
|
|
11435
11375
|
let pathAnchor = usageNode;
|
|
11436
11376
|
let pathOwner = findEnclosingFunction(pathAnchor);
|
|
@@ -11904,94 +11844,6 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
11904
11844
|
return didFindUnmountCleanup;
|
|
11905
11845
|
};
|
|
11906
11846
|
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
11907
|
-
const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
|
|
11908
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
11909
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
|
|
11910
|
-
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
11911
|
-
if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
|
|
11912
|
-
return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
|
|
11913
|
-
}
|
|
11914
|
-
if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
|
|
11915
|
-
"===",
|
|
11916
|
-
"==",
|
|
11917
|
-
"!==",
|
|
11918
|
-
"!="
|
|
11919
|
-
].includes(unwrappedExpression.operator)) {
|
|
11920
|
-
const leftValue = readStaticBoolean(unwrappedExpression.left);
|
|
11921
|
-
const rightValue = readStaticBoolean(unwrappedExpression.right);
|
|
11922
|
-
const booleanValue = leftValue ?? rightValue;
|
|
11923
|
-
const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
|
|
11924
|
-
if (booleanValue === null || comparedKey === null) return [];
|
|
11925
|
-
return [{
|
|
11926
|
-
key: comparedKey,
|
|
11927
|
-
value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
|
|
11928
|
-
}];
|
|
11929
|
-
}
|
|
11930
|
-
const expressionKey = resolveExpressionKey(unwrappedExpression, context);
|
|
11931
|
-
return expressionKey === null ? [] : [{
|
|
11932
|
-
key: expressionKey,
|
|
11933
|
-
value: blockedExpressionValue
|
|
11934
|
-
}];
|
|
11935
|
-
};
|
|
11936
|
-
const isDirectEarlyReturnConsequent = (ifStatement) => {
|
|
11937
|
-
if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
|
|
11938
|
-
if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
|
|
11939
|
-
return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
|
|
11940
|
-
};
|
|
11941
|
-
const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
|
|
11942
|
-
if (!isFunctionLike$2(callback) || callback.async) return [];
|
|
11943
|
-
const guardStates = [];
|
|
11944
|
-
walkAst(callback.body, (child) => {
|
|
11945
|
-
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
11946
|
-
if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
|
|
11947
|
-
});
|
|
11948
|
-
let descendant = usageNode;
|
|
11949
|
-
let ancestor = descendant.parent;
|
|
11950
|
-
while (ancestor && ancestor !== callback) {
|
|
11951
|
-
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
|
|
11952
|
-
descendant = ancestor;
|
|
11953
|
-
ancestor = ancestor.parent;
|
|
11954
|
-
}
|
|
11955
|
-
return guardStates;
|
|
11956
|
-
};
|
|
11957
|
-
const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
|
|
11958
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
11959
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
11960
|
-
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction) || cleanupFunction.async) return false;
|
|
11961
|
-
let didInvalidateGuard = false;
|
|
11962
|
-
walkAst(cleanupFunction.body, (child) => {
|
|
11963
|
-
if (didInvalidateGuard) return false;
|
|
11964
|
-
if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
|
|
11965
|
-
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveExpressionKey(child.left, context) === guardState.key && readStaticBoolean(child.right) === guardState.value && context.cfg.isUnconditionalFromEntry(child)) {
|
|
11966
|
-
didInvalidateGuard = true;
|
|
11967
|
-
return false;
|
|
11968
|
-
}
|
|
11969
|
-
});
|
|
11970
|
-
return didInvalidateGuard;
|
|
11971
|
-
};
|
|
11972
|
-
const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, context) => {
|
|
11973
|
-
const usageStart = getRangeStart(usageNode);
|
|
11974
|
-
if (!isFunctionLike$2(callback) || usageStart === null) return true;
|
|
11975
|
-
let didWriteGuard = false;
|
|
11976
|
-
walkAst(callback.body, (child) => {
|
|
11977
|
-
if (didWriteGuard) return false;
|
|
11978
|
-
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
11979
|
-
const childStart = getRangeStart(child);
|
|
11980
|
-
if (childStart === null || childStart >= usageStart) return;
|
|
11981
|
-
const writtenExpression = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : null;
|
|
11982
|
-
if (writtenExpression && resolveExpressionKey(writtenExpression, context) === guardState.key) {
|
|
11983
|
-
didWriteGuard = true;
|
|
11984
|
-
return false;
|
|
11985
|
-
}
|
|
11986
|
-
});
|
|
11987
|
-
return didWriteGuard;
|
|
11988
|
-
};
|
|
11989
|
-
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
11990
|
-
const usageFunction = findEnclosingFunction(usage.node);
|
|
11991
|
-
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
11992
|
-
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
11993
|
-
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
11994
|
-
};
|
|
11995
11847
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
11996
11848
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
11997
11849
|
if (callback.async) return false;
|
|
@@ -12031,7 +11883,6 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
12031
11883
|
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
|
|
12032
11884
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
12033
11885
|
});
|
|
12034
|
-
if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
|
|
12035
11886
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
12036
11887
|
};
|
|
12037
11888
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
@@ -14775,7 +14626,7 @@ const forbidElements = defineRule({
|
|
|
14775
14626
|
});
|
|
14776
14627
|
//#endregion
|
|
14777
14628
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
14778
|
-
const MESSAGE$
|
|
14629
|
+
const MESSAGE$53 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
14779
14630
|
const forwardRefUsesRef = defineRule({
|
|
14780
14631
|
id: "forward-ref-uses-ref",
|
|
14781
14632
|
title: "forwardRef without ref parameter",
|
|
@@ -14798,7 +14649,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
14798
14649
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
14799
14650
|
context.report({
|
|
14800
14651
|
node: inner,
|
|
14801
|
-
message: MESSAGE$
|
|
14652
|
+
message: MESSAGE$53
|
|
14802
14653
|
});
|
|
14803
14654
|
} })
|
|
14804
14655
|
});
|
|
@@ -14839,7 +14690,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
14839
14690
|
});
|
|
14840
14691
|
//#endregion
|
|
14841
14692
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
14842
|
-
const MESSAGE$
|
|
14693
|
+
const MESSAGE$52 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
14843
14694
|
const DEFAULT_HEADING_TAGS = [
|
|
14844
14695
|
"h1",
|
|
14845
14696
|
"h2",
|
|
@@ -14873,7 +14724,7 @@ const headingHasContent = defineRule({
|
|
|
14873
14724
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
14874
14725
|
context.report({
|
|
14875
14726
|
node,
|
|
14876
|
-
message: MESSAGE$
|
|
14727
|
+
message: MESSAGE$52
|
|
14877
14728
|
});
|
|
14878
14729
|
} };
|
|
14879
14730
|
}
|
|
@@ -15037,7 +14888,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
15037
14888
|
});
|
|
15038
14889
|
//#endregion
|
|
15039
14890
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
15040
|
-
const MESSAGE$
|
|
14891
|
+
const MESSAGE$51 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
15041
14892
|
const resolveSettings$39 = (settings) => {
|
|
15042
14893
|
const reactDoctor = settings?.["react-doctor"];
|
|
15043
14894
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -15084,13 +14935,13 @@ const htmlHasLang = defineRule({
|
|
|
15084
14935
|
if (!lang) {
|
|
15085
14936
|
context.report({
|
|
15086
14937
|
node: node.name,
|
|
15087
|
-
message: MESSAGE$
|
|
14938
|
+
message: MESSAGE$51
|
|
15088
14939
|
});
|
|
15089
14940
|
return;
|
|
15090
14941
|
}
|
|
15091
14942
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
15092
14943
|
node: lang,
|
|
15093
|
-
message: MESSAGE$
|
|
14944
|
+
message: MESSAGE$51
|
|
15094
14945
|
});
|
|
15095
14946
|
} };
|
|
15096
14947
|
}
|
|
@@ -15330,7 +15181,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
15330
15181
|
});
|
|
15331
15182
|
//#endregion
|
|
15332
15183
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
15333
|
-
const MESSAGE$
|
|
15184
|
+
const MESSAGE$50 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
15334
15185
|
const evaluateTitleValue = (value) => {
|
|
15335
15186
|
if (!value) return "missing";
|
|
15336
15187
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -15370,14 +15221,14 @@ const iframeHasTitle = defineRule({
|
|
|
15370
15221
|
if (!titleAttr) {
|
|
15371
15222
|
if (hasSpread || tag === "iframe") context.report({
|
|
15372
15223
|
node: node.name,
|
|
15373
|
-
message: MESSAGE$
|
|
15224
|
+
message: MESSAGE$50
|
|
15374
15225
|
});
|
|
15375
15226
|
return;
|
|
15376
15227
|
}
|
|
15377
15228
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
15378
15229
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
15379
15230
|
node: titleAttr,
|
|
15380
|
-
message: MESSAGE$
|
|
15231
|
+
message: MESSAGE$50
|
|
15381
15232
|
});
|
|
15382
15233
|
} })
|
|
15383
15234
|
});
|
|
@@ -15502,7 +15353,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
15502
15353
|
});
|
|
15503
15354
|
//#endregion
|
|
15504
15355
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
15505
|
-
const MESSAGE$
|
|
15356
|
+
const MESSAGE$49 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
15506
15357
|
const DEFAULT_COMPONENTS = ["img"];
|
|
15507
15358
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
15508
15359
|
"image",
|
|
@@ -15567,7 +15418,7 @@ const imgRedundantAlt = defineRule({
|
|
|
15567
15418
|
if (!altAttribute) return;
|
|
15568
15419
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
15569
15420
|
node: altAttribute,
|
|
15570
|
-
message: MESSAGE$
|
|
15421
|
+
message: MESSAGE$49
|
|
15571
15422
|
});
|
|
15572
15423
|
} };
|
|
15573
15424
|
}
|
|
@@ -17350,7 +17201,7 @@ const GLOBAL_BUILTIN_NAMES = new Set([
|
|
|
17350
17201
|
]);
|
|
17351
17202
|
const STRING_PROTOTYPE_PATH = "String.prototype";
|
|
17352
17203
|
const REGEXP_PROTOTYPE_PATH = "RegExp.prototype";
|
|
17353
|
-
const getStaticStringValue
|
|
17204
|
+
const getStaticStringValue = (argument) => {
|
|
17354
17205
|
if (!argument) return null;
|
|
17355
17206
|
const unwrappedArgument = stripParenExpression(argument);
|
|
17356
17207
|
if (isNodeOfType(unwrappedArgument, "Literal") && typeof unwrappedArgument.value === "string") return unwrappedArgument.value;
|
|
@@ -17358,7 +17209,7 @@ const getStaticStringValue$1 = (argument) => {
|
|
|
17358
17209
|
return null;
|
|
17359
17210
|
};
|
|
17360
17211
|
const getEffectiveRegExpFlags = (patternArgument, flagsArgument) => {
|
|
17361
|
-
if (flagsArgument) return getStaticStringValue
|
|
17212
|
+
if (flagsArgument) return getStaticStringValue(flagsArgument);
|
|
17362
17213
|
if (!patternArgument) return "";
|
|
17363
17214
|
const unwrappedPattern = stripParenExpression(patternArgument);
|
|
17364
17215
|
if (isNodeOfType(unwrappedPattern, "Literal") && unwrappedPattern.value instanceof RegExp) return unwrappedPattern.value.flags;
|
|
@@ -17508,7 +17359,7 @@ const getCallRegExpHazard = (node, context, symbolCache) => {
|
|
|
17508
17359
|
const mutationHazard = targetPath === "global" ? "globalRegExpReplaced" : "replaceAllIntegrityLost";
|
|
17509
17360
|
const guardedPropertyName = targetPath === "global" ? "RegExp" : "replaceAll";
|
|
17510
17361
|
if (isSinglePropertyMutation) {
|
|
17511
|
-
const propertyName = getStaticStringValue
|
|
17362
|
+
const propertyName = getStaticStringValue(node.arguments?.[1]);
|
|
17512
17363
|
return propertyName === null || propertyName === guardedPropertyName ? mutationHazard : "none";
|
|
17513
17364
|
}
|
|
17514
17365
|
if (!isPropertyCollectionMutation) return "none";
|
|
@@ -17537,7 +17388,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
17537
17388
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
17538
17389
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
17539
17390
|
};
|
|
17540
|
-
const MESSAGE$
|
|
17391
|
+
const MESSAGE$48 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
17541
17392
|
const jsHoistRegexp = defineRule({
|
|
17542
17393
|
id: "js-hoist-regexp",
|
|
17543
17394
|
title: "RegExp built inside a loop",
|
|
@@ -17554,7 +17405,7 @@ const jsHoistRegexp = defineRule({
|
|
|
17554
17405
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
17555
17406
|
context.report({
|
|
17556
17407
|
node,
|
|
17557
|
-
message: MESSAGE$
|
|
17408
|
+
message: MESSAGE$48
|
|
17558
17409
|
});
|
|
17559
17410
|
};
|
|
17560
17411
|
return createLoopAwareVisitors({
|
|
@@ -18482,26 +18333,6 @@ const MEMBERSHIP_COMPARISON_OPERATORS = new Set([
|
|
|
18482
18333
|
]);
|
|
18483
18334
|
const isNegativeOneLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "UnaryExpression") && expression.operator === "-" && isNodeOfType(expression.argument, "Literal") && expression.argument.value === 1;
|
|
18484
18335
|
const isZeroLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "Literal") && expression.value === 0;
|
|
18485
|
-
const isToIntegerOrInfinityZero = (value) => Number.isNaN(value) || Math.trunc(value) === 0;
|
|
18486
|
-
const isZeroFromIndex = (expression) => {
|
|
18487
|
-
if (!expression) return false;
|
|
18488
|
-
const strippedExpression = stripParenExpression(expression);
|
|
18489
|
-
if (isNodeOfType(strippedExpression, "Literal")) {
|
|
18490
|
-
if (typeof strippedExpression.value === "number") return isToIntegerOrInfinityZero(strippedExpression.value);
|
|
18491
|
-
if (typeof strippedExpression.value === "string") return isToIntegerOrInfinityZero(Number(strippedExpression.value));
|
|
18492
|
-
return strippedExpression.value === null || strippedExpression.value === false;
|
|
18493
|
-
}
|
|
18494
|
-
if (isNodeOfType(strippedExpression, "UnaryExpression") && strippedExpression.operator === "void") return true;
|
|
18495
|
-
if (isNodeOfType(strippedExpression, "UnaryExpression") && (strippedExpression.operator === "-" || strippedExpression.operator === "+") && isNodeOfType(strippedExpression.argument, "Literal") && typeof strippedExpression.argument.value === "number") return isToIntegerOrInfinityZero(strippedExpression.operator === "-" ? -strippedExpression.argument.value : strippedExpression.argument.value);
|
|
18496
|
-
if (isNodeOfType(strippedExpression, "Identifier")) return (strippedExpression.name === "undefined" || strippedExpression.name === "NaN") && findVariableInitializer(strippedExpression, strippedExpression.name) === null;
|
|
18497
|
-
return isNodeOfType(strippedExpression, "MemberExpression") && !strippedExpression.computed && isNodeOfType(strippedExpression.object, "Identifier") && strippedExpression.object.name === "Number" && isNodeOfType(strippedExpression.property, "Identifier") && strippedExpression.property.name === "NaN" && findVariableInitializer(strippedExpression.object, strippedExpression.object.name) === null;
|
|
18498
|
-
};
|
|
18499
|
-
const hasSemanticsPreservingIncludesArguments = (node) => {
|
|
18500
|
-
if (node.arguments.length === 1) return !isNodeOfType(node.arguments[0], "SpreadElement");
|
|
18501
|
-
if (node.arguments.length !== 2) return false;
|
|
18502
|
-
if (isNodeOfType(node.arguments[0], "SpreadElement") || isNodeOfType(node.arguments[1], "SpreadElement")) return false;
|
|
18503
|
-
return isZeroFromIndex(node.arguments[1]);
|
|
18504
|
-
};
|
|
18505
18336
|
const PARENT_WRAPPER_TYPES = new Set([
|
|
18506
18337
|
"ParenthesizedExpression",
|
|
18507
18338
|
"ChainExpression",
|
|
@@ -18522,306 +18353,6 @@ const isIndexOfResultUsedAsMembershipTest = (node) => {
|
|
|
18522
18353
|
if (isNegativeOneLiteral(otherOperand)) return true;
|
|
18523
18354
|
return isZeroLiteral(otherOperand) && (parent.operator === ">=" || parent.operator === "<");
|
|
18524
18355
|
};
|
|
18525
|
-
const getTypeAnnotation = (node) => {
|
|
18526
|
-
if (!node || !("typeAnnotation" in node)) return null;
|
|
18527
|
-
const annotation = node.typeAnnotation;
|
|
18528
|
-
if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
|
|
18529
|
-
return annotation.typeAnnotation;
|
|
18530
|
-
};
|
|
18531
|
-
const getDeclaredPropertyType = (members, propertyName) => {
|
|
18532
|
-
for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation(member);
|
|
18533
|
-
return null;
|
|
18534
|
-
};
|
|
18535
|
-
const getArrayElementType = (typeNode) => {
|
|
18536
|
-
if (!typeNode) return null;
|
|
18537
|
-
if (isNodeOfType(typeNode, "TSArrayType")) return typeNode.elementType;
|
|
18538
|
-
if (isNodeOfType(typeNode, "TSTypeReference") && isNodeOfType(typeNode.typeName, "Identifier") && (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray")) return typeNode.typeArguments?.params?.[0] ?? null;
|
|
18539
|
-
if (isNodeOfType(typeNode, "TSUnionType")) {
|
|
18540
|
-
const arrayElementTypes = typeNode.types.map(getArrayElementType).filter(Boolean);
|
|
18541
|
-
return arrayElementTypes.length === 1 ? arrayElementTypes[0] : null;
|
|
18542
|
-
}
|
|
18543
|
-
return null;
|
|
18544
|
-
};
|
|
18545
|
-
const getDestructuredDeclaredType = (identifier) => {
|
|
18546
|
-
const binding = findVariableInitializer(identifier, identifier.name);
|
|
18547
|
-
if (!binding) return null;
|
|
18548
|
-
const property = binding.bindingIdentifier.parent;
|
|
18549
|
-
const objectPattern = property?.parent;
|
|
18550
|
-
if (!isNodeOfType(property, "Property") || !isNodeOfType(property.key, "Identifier") || !isNodeOfType(objectPattern, "ObjectPattern")) return null;
|
|
18551
|
-
const propsType = getTypeAnnotation(objectPattern);
|
|
18552
|
-
if (!propsType) return null;
|
|
18553
|
-
if (isNodeOfType(propsType, "TSTypeLiteral")) return getDeclaredPropertyType(propsType.members ?? [], property.key.name);
|
|
18554
|
-
if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return null;
|
|
18555
|
-
const program = findProgramRoot(identifier);
|
|
18556
|
-
if (!program) return null;
|
|
18557
|
-
for (const statement of program.body) {
|
|
18558
|
-
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
18559
|
-
if (isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name) return getDeclaredPropertyType(declaration.body.body, property.key.name);
|
|
18560
|
-
if (isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name && isNodeOfType(declaration.typeAnnotation, "TSTypeLiteral")) return getDeclaredPropertyType(declaration.typeAnnotation.members ?? [], property.key.name);
|
|
18561
|
-
}
|
|
18562
|
-
return null;
|
|
18563
|
-
};
|
|
18564
|
-
const getIdentifierDeclaredType = (identifier, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
18565
|
-
const binding = findVariableInitializer(identifier, identifier.name);
|
|
18566
|
-
if (!binding || visitedBindingIdentifiers.has(binding.bindingIdentifier)) return null;
|
|
18567
|
-
visitedBindingIdentifiers.add(binding.bindingIdentifier);
|
|
18568
|
-
const directType = getTypeAnnotation(binding.bindingIdentifier);
|
|
18569
|
-
if (directType) return directType;
|
|
18570
|
-
const initializer = binding.initializer;
|
|
18571
|
-
if (isNodeOfType(initializer, "TSAsExpression") || isNodeOfType(initializer, "TSTypeAssertion") || isNodeOfType(initializer, "TSSatisfiesExpression")) return initializer.typeAnnotation;
|
|
18572
|
-
const destructuredType = getDestructuredDeclaredType(identifier);
|
|
18573
|
-
if (destructuredType) return destructuredType;
|
|
18574
|
-
const declarator = binding.bindingIdentifier.parent;
|
|
18575
|
-
const declaration = declarator?.parent;
|
|
18576
|
-
const forOfStatement = declaration?.parent;
|
|
18577
|
-
if (isNodeOfType(declarator, "VariableDeclarator") && isNodeOfType(declaration, "VariableDeclaration") && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isNodeOfType(forOfStatement.right, "Identifier")) return getArrayElementType(getIdentifierDeclaredType(forOfStatement.right, visitedBindingIdentifiers));
|
|
18578
|
-
return null;
|
|
18579
|
-
};
|
|
18580
|
-
const isNativeIterationIndex = (identifier) => {
|
|
18581
|
-
const binding = findVariableInitializer(identifier, identifier.name);
|
|
18582
|
-
if (!binding) return false;
|
|
18583
|
-
const callback = binding.bindingIdentifier.parent;
|
|
18584
|
-
if (!isInlineFunctionExpression(callback)) return false;
|
|
18585
|
-
const callbackCall = callback.parent;
|
|
18586
|
-
if (!isNodeOfType(callbackCall, "CallExpression") || !isIterationCallbackCall(callbackCall) || !isNodeOfType(callbackCall.callee, "MemberExpression") || !isNodeOfType(callbackCall.callee.property, "Identifier")) return false;
|
|
18587
|
-
const indexParameterPosition = callbackCall.callee.property.name === "reduce" || callbackCall.callee.property.name === "reduceRight" ? 2 : 1;
|
|
18588
|
-
return callback.params?.[indexParameterPosition] === binding.bindingIdentifier;
|
|
18589
|
-
};
|
|
18590
|
-
const hasSameIdentifierBinding = (leftIdentifier, rightIdentifier) => {
|
|
18591
|
-
if (leftIdentifier.name !== rightIdentifier.name) return false;
|
|
18592
|
-
const leftBinding = findVariableInitializer(leftIdentifier, leftIdentifier.name);
|
|
18593
|
-
const rightBinding = findVariableInitializer(rightIdentifier, rightIdentifier.name);
|
|
18594
|
-
return Boolean(leftBinding && rightBinding && leftBinding.bindingIdentifier === rightBinding.bindingIdentifier);
|
|
18595
|
-
};
|
|
18596
|
-
const NON_NAN_RELATIONAL_OPERATORS = new Set([
|
|
18597
|
-
"<",
|
|
18598
|
-
"<=",
|
|
18599
|
-
">",
|
|
18600
|
-
">="
|
|
18601
|
-
]);
|
|
18602
|
-
const testProvesIdentifierIsNotNaN = (test, identifier) => {
|
|
18603
|
-
if (!test) return false;
|
|
18604
|
-
const strippedTest = stripParenExpression(test);
|
|
18605
|
-
if (isNodeOfType(strippedTest, "LogicalExpression") && strippedTest.operator === "&&") return testProvesIdentifierIsNotNaN(strippedTest.left, identifier) || testProvesIdentifierIsNotNaN(strippedTest.right, identifier);
|
|
18606
|
-
if (!isNodeOfType(strippedTest, "BinaryExpression") || !NON_NAN_RELATIONAL_OPERATORS.has(strippedTest.operator)) return false;
|
|
18607
|
-
return isNodeOfType(strippedTest.left, "Identifier") && hasSameIdentifierBinding(strippedTest.left, identifier) || isNodeOfType(strippedTest.right, "Identifier") && hasSameIdentifierBinding(strippedTest.right, identifier);
|
|
18608
|
-
};
|
|
18609
|
-
const writeTargetContainsIdentifierBinding = (writeTarget, identifier) => {
|
|
18610
|
-
let containsBinding = false;
|
|
18611
|
-
walkAst(writeTarget, (child) => {
|
|
18612
|
-
if (isNodeOfType(child, "Identifier") && hasSameIdentifierBinding(child, identifier)) {
|
|
18613
|
-
containsBinding = true;
|
|
18614
|
-
return false;
|
|
18615
|
-
}
|
|
18616
|
-
});
|
|
18617
|
-
return containsBinding;
|
|
18618
|
-
};
|
|
18619
|
-
const hasWriteBeforeQuery = (body, identifier) => {
|
|
18620
|
-
const queryStart = getRangeStart(identifier);
|
|
18621
|
-
if (queryStart === null) return true;
|
|
18622
|
-
let hasEarlierWrite = false;
|
|
18623
|
-
walkAst(body, (child) => {
|
|
18624
|
-
if (hasEarlierWrite) return false;
|
|
18625
|
-
const childStart = getRangeStart(child);
|
|
18626
|
-
if (childStart !== null && childStart >= queryStart) return false;
|
|
18627
|
-
if (child !== body && isFunctionLike$2(child)) return false;
|
|
18628
|
-
const writeTarget = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement") ? child.left : null;
|
|
18629
|
-
if (writeTarget && writeTargetContainsIdentifierBinding(writeTarget, identifier)) {
|
|
18630
|
-
hasEarlierWrite = true;
|
|
18631
|
-
return false;
|
|
18632
|
-
}
|
|
18633
|
-
});
|
|
18634
|
-
return hasEarlierWrite;
|
|
18635
|
-
};
|
|
18636
|
-
const isProtectedByRelationalLoopGuard = (identifier) => {
|
|
18637
|
-
let descendant = identifier;
|
|
18638
|
-
let ancestor = identifier.parent;
|
|
18639
|
-
while (ancestor) {
|
|
18640
|
-
if (isFunctionLike$2(ancestor)) return false;
|
|
18641
|
-
if ((isNodeOfType(ancestor, "ForStatement") || isNodeOfType(ancestor, "WhileStatement")) && ancestor.body === descendant && testProvesIdentifierIsNotNaN(ancestor.test, identifier)) return !hasWriteBeforeQuery(ancestor.body, identifier);
|
|
18642
|
-
descendant = ancestor;
|
|
18643
|
-
ancestor = ancestor.parent;
|
|
18644
|
-
}
|
|
18645
|
-
return false;
|
|
18646
|
-
};
|
|
18647
|
-
const isKnownSafeIndexOfQuery = (query) => {
|
|
18648
|
-
if (!query) return false;
|
|
18649
|
-
const strippedQuery = stripParenExpression(query);
|
|
18650
|
-
if (isNodeOfType(strippedQuery, "Literal")) return typeof strippedQuery.value !== "number" || Number.isFinite(strippedQuery.value);
|
|
18651
|
-
if (!isNodeOfType(strippedQuery, "Identifier")) return false;
|
|
18652
|
-
if (isNativeIterationIndex(strippedQuery)) return true;
|
|
18653
|
-
return isProtectedByRelationalLoopGuard(strippedQuery);
|
|
18654
|
-
};
|
|
18655
|
-
const findSameFileTypeAlias = (reference, typeName) => {
|
|
18656
|
-
const program = findProgramRoot(reference);
|
|
18657
|
-
if (!program) return null;
|
|
18658
|
-
for (const statement of program.body) {
|
|
18659
|
-
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
18660
|
-
if (declaration && isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === typeName) return declaration;
|
|
18661
|
-
}
|
|
18662
|
-
return null;
|
|
18663
|
-
};
|
|
18664
|
-
const hasDeclaredMembershipMethod = (members, methodName) => members.some((member) => (isNodeOfType(member, "TSMethodSignature") || isNodeOfType(member, "TSPropertySignature")) && isNodeOfType(member.key, "Identifier") && member.key.name === methodName);
|
|
18665
|
-
const isKnownUserlandMembershipReceiver = (receiver, methodName) => {
|
|
18666
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
18667
|
-
const declaredType = getIdentifierDeclaredType(receiver);
|
|
18668
|
-
if (!declaredType) return false;
|
|
18669
|
-
if (isNodeOfType(declaredType, "TSTypeLiteral")) return hasDeclaredMembershipMethod(declaredType.members ?? [], methodName);
|
|
18670
|
-
if (!isNodeOfType(declaredType, "TSTypeReference") || !isNodeOfType(declaredType.typeName, "Identifier")) return false;
|
|
18671
|
-
const typeAlias = findSameFileTypeAlias(receiver, declaredType.typeName.name);
|
|
18672
|
-
if (typeAlias && isNodeOfType(typeAlias.typeAnnotation, "TSTypeLiteral")) return hasDeclaredMembershipMethod(typeAlias.typeAnnotation.members ?? [], methodName);
|
|
18673
|
-
const program = findProgramRoot(receiver);
|
|
18674
|
-
if (!program) return false;
|
|
18675
|
-
for (const statement of program.body) {
|
|
18676
|
-
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
18677
|
-
if (declaration && isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === declaredType.typeName.name) return hasDeclaredMembershipMethod(declaration.body.body, methodName);
|
|
18678
|
-
}
|
|
18679
|
-
return false;
|
|
18680
|
-
};
|
|
18681
|
-
const findTypeParameter = (reference, typeName) => {
|
|
18682
|
-
let ancestor = reference.parent;
|
|
18683
|
-
while (ancestor) {
|
|
18684
|
-
if (isFunctionLike$2(ancestor) || isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) {
|
|
18685
|
-
const matchingTypeParameter = ancestor.typeParameters?.params?.find((typeParameter) => isNodeOfType(typeParameter, "TSTypeParameter") && isNodeOfType(typeParameter.name, "Identifier") && typeParameter.name.name === typeName);
|
|
18686
|
-
if (matchingTypeParameter && isNodeOfType(matchingTypeParameter, "TSTypeParameter")) return matchingTypeParameter;
|
|
18687
|
-
}
|
|
18688
|
-
ancestor = ancestor.parent;
|
|
18689
|
-
}
|
|
18690
|
-
return null;
|
|
18691
|
-
};
|
|
18692
|
-
const POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES = new Set(["NonNullable", "PropertyKey"]);
|
|
18693
|
-
const buildTypeAliasArguments = (typeAlias, typeReference, inheritedArguments) => {
|
|
18694
|
-
const typeArguments = new Map(inheritedArguments);
|
|
18695
|
-
for (const [index, typeParameter] of (typeAlias.typeParameters?.params ?? []).entries()) {
|
|
18696
|
-
if (!isNodeOfType(typeParameter, "TSTypeParameter")) continue;
|
|
18697
|
-
if (!isNodeOfType(typeParameter.name, "Identifier")) continue;
|
|
18698
|
-
const argument = typeReference.typeArguments?.params?.[index] ?? typeParameter.default;
|
|
18699
|
-
if (argument) typeArguments.set(typeParameter.name.name, argument);
|
|
18700
|
-
}
|
|
18701
|
-
return typeArguments;
|
|
18702
|
-
};
|
|
18703
|
-
const typeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
|
|
18704
|
-
if (!typeNode) return false;
|
|
18705
|
-
if (isNodeOfType(typeNode, "TSNumberKeyword") || isNodeOfType(typeNode, "TSAnyKeyword") || isNodeOfType(typeNode, "TSUnknownKeyword")) return true;
|
|
18706
|
-
if (isNodeOfType(typeNode, "TSStringKeyword") || isNodeOfType(typeNode, "TSBooleanKeyword") || isNodeOfType(typeNode, "TSBigIntKeyword") || isNodeOfType(typeNode, "TSSymbolKeyword") || isNodeOfType(typeNode, "TSNullKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword") || isNodeOfType(typeNode, "TSObjectKeyword") || isNodeOfType(typeNode, "TSLiteralType") || isNodeOfType(typeNode, "TSFunctionType")) return false;
|
|
18707
|
-
if (isNodeOfType(typeNode, "TSTypeLiteral")) return (typeNode.members?.length ?? 0) === 0;
|
|
18708
|
-
if (isNodeOfType(typeNode, "TSTypeOperator") && typeNode.operator === "keyof") return false;
|
|
18709
|
-
if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => typeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
|
|
18710
|
-
if (isNodeOfType(typeNode, "TSOptionalType") || isNodeOfType(typeNode, "TSRestType")) return typeCanHaveSameValueZeroDifference(typeNode.typeAnnotation, reference, activeTypeNodes, typeArguments);
|
|
18711
|
-
if (isNodeOfType(typeNode, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
|
|
18712
|
-
if (!isNodeOfType(typeNode, "TSTypeReference")) return true;
|
|
18713
|
-
if (!isNodeOfType(typeNode.typeName, "Identifier")) return false;
|
|
18714
|
-
const substitutedType = typeArguments.get(typeNode.typeName.name);
|
|
18715
|
-
if (substitutedType) {
|
|
18716
|
-
const remainingArguments = new Map(typeArguments);
|
|
18717
|
-
remainingArguments.delete(typeNode.typeName.name);
|
|
18718
|
-
return typeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
|
|
18719
|
-
}
|
|
18720
|
-
const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
|
|
18721
|
-
if (typeParameter) {
|
|
18722
|
-
if (!typeParameter.constraint || activeTypeNodes.has(typeParameter)) return true;
|
|
18723
|
-
activeTypeNodes.add(typeParameter);
|
|
18724
|
-
const constraintCanDiffer = typeCanHaveSameValueZeroDifference(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
|
|
18725
|
-
activeTypeNodes.delete(typeParameter);
|
|
18726
|
-
return constraintCanDiffer;
|
|
18727
|
-
}
|
|
18728
|
-
const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
|
|
18729
|
-
if (!typeAlias) return POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES.has(typeNode.typeName.name) || /(?:number|numeric)/i.test(typeNode.typeName.name);
|
|
18730
|
-
if (activeTypeNodes.has(typeAlias)) return true;
|
|
18731
|
-
activeTypeNodes.add(typeAlias);
|
|
18732
|
-
const canDiffer = typeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
|
|
18733
|
-
activeTypeNodes.delete(typeAlias);
|
|
18734
|
-
return canDiffer;
|
|
18735
|
-
};
|
|
18736
|
-
const arrayTypeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
|
|
18737
|
-
if (!typeNode) return false;
|
|
18738
|
-
if (isNodeOfType(typeNode, "TSArrayType")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
|
|
18739
|
-
if (isNodeOfType(typeNode, "TSTupleType")) return typeNode.elementTypes.some((elementType) => {
|
|
18740
|
-
if (isNodeOfType(elementType, "TSRestType")) return arrayTypeCanHaveSameValueZeroDifference(elementType.typeAnnotation, reference, activeTypeNodes, typeArguments);
|
|
18741
|
-
if (isNodeOfType(elementType, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(elementType.elementType, reference, activeTypeNodes, typeArguments);
|
|
18742
|
-
return typeCanHaveSameValueZeroDifference(elementType, reference, activeTypeNodes, typeArguments);
|
|
18743
|
-
});
|
|
18744
|
-
if (isNodeOfType(typeNode, "TSTypeOperator")) return arrayTypeCanHaveSameValueZeroDifference(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
|
|
18745
|
-
if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => arrayTypeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
|
|
18746
|
-
if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
|
|
18747
|
-
const substitutedType = typeArguments.get(typeNode.typeName.name);
|
|
18748
|
-
if (substitutedType) {
|
|
18749
|
-
const remainingArguments = new Map(typeArguments);
|
|
18750
|
-
remainingArguments.delete(typeNode.typeName.name);
|
|
18751
|
-
return arrayTypeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
|
|
18752
|
-
}
|
|
18753
|
-
if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return typeCanHaveSameValueZeroDifference(typeNode.typeArguments?.params?.[0] ?? null, reference, activeTypeNodes, typeArguments);
|
|
18754
|
-
const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
|
|
18755
|
-
if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
|
|
18756
|
-
activeTypeNodes.add(typeAlias);
|
|
18757
|
-
const canDiffer = arrayTypeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
|
|
18758
|
-
activeTypeNodes.delete(typeAlias);
|
|
18759
|
-
return canDiffer;
|
|
18760
|
-
};
|
|
18761
|
-
const isKnownArrayType = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
|
|
18762
|
-
if (!typeNode) return false;
|
|
18763
|
-
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return true;
|
|
18764
|
-
if (isNodeOfType(typeNode, "TSTypeOperator")) return isKnownArrayType(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
|
|
18765
|
-
if (isNodeOfType(typeNode, "TSUnionType")) {
|
|
18766
|
-
const nonNullishTypes = typeNode.types.filter((memberType) => !isNodeOfType(memberType, "TSNullKeyword") && !isNodeOfType(memberType, "TSUndefinedKeyword"));
|
|
18767
|
-
return nonNullishTypes.length > 0 && nonNullishTypes.every((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
|
|
18768
|
-
}
|
|
18769
|
-
if (isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
|
|
18770
|
-
if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
|
|
18771
|
-
const substitutedType = typeArguments.get(typeNode.typeName.name);
|
|
18772
|
-
if (substitutedType) {
|
|
18773
|
-
const remainingArguments = new Map(typeArguments);
|
|
18774
|
-
remainingArguments.delete(typeNode.typeName.name);
|
|
18775
|
-
return isKnownArrayType(substitutedType, reference, activeTypeNodes, remainingArguments);
|
|
18776
|
-
}
|
|
18777
|
-
if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return true;
|
|
18778
|
-
const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
|
|
18779
|
-
if (typeParameter?.constraint) {
|
|
18780
|
-
if (activeTypeNodes.has(typeParameter)) return false;
|
|
18781
|
-
activeTypeNodes.add(typeParameter);
|
|
18782
|
-
const isConstraintArray = isKnownArrayType(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
|
|
18783
|
-
activeTypeNodes.delete(typeParameter);
|
|
18784
|
-
return isConstraintArray;
|
|
18785
|
-
}
|
|
18786
|
-
const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
|
|
18787
|
-
if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
|
|
18788
|
-
activeTypeNodes.add(typeAlias);
|
|
18789
|
-
const isArray = isKnownArrayType(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
|
|
18790
|
-
activeTypeNodes.delete(typeAlias);
|
|
18791
|
-
return isArray;
|
|
18792
|
-
};
|
|
18793
|
-
const isKnownNativeArrayReceiver = (receiver) => {
|
|
18794
|
-
if (isNodeOfType(receiver, "ArrayExpression")) return true;
|
|
18795
|
-
if (isNodeOfType(receiver, "Identifier") && isKnownArrayType(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set())) return true;
|
|
18796
|
-
const initializer = getResolvedInitializer(receiver)?.initializer;
|
|
18797
|
-
if (!initializer) return false;
|
|
18798
|
-
const strippedInitializer = stripParenExpression(initializer);
|
|
18799
|
-
if (isNodeOfType(strippedInitializer, "ArrayExpression")) return true;
|
|
18800
|
-
if (isNodeOfType(strippedInitializer, "NewExpression") && isNodeOfType(strippedInitializer.callee, "Identifier") && strippedInitializer.callee.name === "Array" && findVariableInitializer(strippedInitializer.callee, strippedInitializer.callee.name) === null) return true;
|
|
18801
|
-
return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
|
|
18802
|
-
};
|
|
18803
|
-
const isKnownDenseArrayReceiver = (receiver) => {
|
|
18804
|
-
const initializer = isNodeOfType(receiver, "Identifier") ? getResolvedInitializer(receiver)?.initializer : receiver;
|
|
18805
|
-
if (!initializer) return false;
|
|
18806
|
-
const strippedInitializer = stripParenExpression(initializer);
|
|
18807
|
-
if (isNodeOfType(strippedInitializer, "ArrayExpression")) return (strippedInitializer.elements ?? []).every((element) => element !== null && !isNodeOfType(element, "SpreadElement"));
|
|
18808
|
-
return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
|
|
18809
|
-
};
|
|
18810
|
-
const isKnownUnsafeIndexOfQuery = (query, receiver) => {
|
|
18811
|
-
if (!query) return true;
|
|
18812
|
-
const strippedQuery = stripParenExpression(query);
|
|
18813
|
-
if (isNodeOfType(strippedQuery, "Identifier")) {
|
|
18814
|
-
if (strippedQuery.name === "undefined" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return !isKnownDenseArrayReceiver(receiver);
|
|
18815
|
-
if (strippedQuery.name === "NaN" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return true;
|
|
18816
|
-
return typeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(strippedQuery), strippedQuery, /* @__PURE__ */ new Set());
|
|
18817
|
-
}
|
|
18818
|
-
if (isNodeOfType(strippedQuery, "MemberExpression")) return !strippedQuery.computed && isNodeOfType(strippedQuery.object, "Identifier") && strippedQuery.object.name === "Number" && isNodeOfType(strippedQuery.property, "Identifier") && strippedQuery.property.name === "NaN" && findVariableInitializer(strippedQuery.object, strippedQuery.object.name) === null;
|
|
18819
|
-
return true;
|
|
18820
|
-
};
|
|
18821
|
-
const isKnownUnsafeIndexOfReceiver = (receiver) => {
|
|
18822
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
18823
|
-
return arrayTypeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set());
|
|
18824
|
-
};
|
|
18825
18356
|
const ITERATION_CALLBACK_METHOD_NAMES = new Set([
|
|
18826
18357
|
"forEach",
|
|
18827
18358
|
"map",
|
|
@@ -18950,22 +18481,16 @@ const jsSetMapLookups = defineRule({
|
|
|
18950
18481
|
if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
|
|
18951
18482
|
const methodName = node.callee.property.name;
|
|
18952
18483
|
if (methodName !== "includes" && methodName !== "indexOf") return;
|
|
18953
|
-
if (methodName === "
|
|
18954
|
-
if (methodName === "indexOf" && (node.arguments.length !== 1 || isNodeOfType(node.arguments[0], "SpreadElement") || !isIndexOfResultUsedAsMembershipTest(node))) return;
|
|
18484
|
+
if (methodName === "indexOf" && !isIndexOfResultUsedAsMembershipTest(node)) return;
|
|
18955
18485
|
const rawReceiver = node.callee.object;
|
|
18956
18486
|
if (!rawReceiver) return;
|
|
18957
18487
|
const receiver = stripParenExpression(rawReceiver);
|
|
18958
|
-
const isKnownNativeArray = isKnownNativeArrayReceiver(receiver);
|
|
18959
|
-
if (isKnownUserlandMembershipReceiver(receiver, methodName)) return;
|
|
18960
|
-
if (methodName === "includes" && node.arguments.length === 2 && !isKnownNativeArray) return;
|
|
18961
|
-
const query = node.arguments[0];
|
|
18962
|
-
if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
|
|
18963
18488
|
if (isLikelyStringReceiver(receiver)) return;
|
|
18964
18489
|
if (isSmallInlineLiteralArray(receiver)) return;
|
|
18965
18490
|
if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
|
|
18966
18491
|
if (isSmallFixedListMember(receiver)) return;
|
|
18967
|
-
if (isSubstringSearchLiteral(
|
|
18968
|
-
if (isIndexedArrayElementWithStringArgument(receiver,
|
|
18492
|
+
if (isSubstringSearchLiteral(node.arguments?.[0])) return;
|
|
18493
|
+
if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
|
|
18969
18494
|
const resolvedInitializer = getResolvedInitializer(receiver);
|
|
18970
18495
|
if (resolvedInitializer) {
|
|
18971
18496
|
if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
|
|
@@ -20174,7 +19699,7 @@ const jsxMaxDepth = defineRule({
|
|
|
20174
19699
|
});
|
|
20175
19700
|
//#endregion
|
|
20176
19701
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
20177
|
-
const MESSAGE$
|
|
19702
|
+
const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
20178
19703
|
const LITERAL_TEXT_TAGS = new Set([
|
|
20179
19704
|
"code",
|
|
20180
19705
|
"pre",
|
|
@@ -20244,7 +19769,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
20244
19769
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
20245
19770
|
context.report({
|
|
20246
19771
|
node,
|
|
20247
|
-
message: MESSAGE$
|
|
19772
|
+
message: MESSAGE$47
|
|
20248
19773
|
});
|
|
20249
19774
|
} })
|
|
20250
19775
|
});
|
|
@@ -20275,7 +19800,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
20275
19800
|
};
|
|
20276
19801
|
//#endregion
|
|
20277
19802
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
20278
|
-
const MESSAGE$
|
|
19803
|
+
const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
20279
19804
|
const CONTEXT_MODULES$1 = [
|
|
20280
19805
|
"react",
|
|
20281
19806
|
"use-context-selector",
|
|
@@ -20373,7 +19898,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
20373
19898
|
if (!isConstructedValue(innerExpression)) continue;
|
|
20374
19899
|
context.report({
|
|
20375
19900
|
node: attribute,
|
|
20376
|
-
message: MESSAGE$
|
|
19901
|
+
message: MESSAGE$46
|
|
20377
19902
|
});
|
|
20378
19903
|
}
|
|
20379
19904
|
}
|
|
@@ -21228,7 +20753,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
21228
20753
|
];
|
|
21229
20754
|
//#endregion
|
|
21230
20755
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
21231
|
-
const MESSAGE$
|
|
20756
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
21232
20757
|
const isDataArrayPropName = (propName) => {
|
|
21233
20758
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
21234
20759
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -21315,7 +20840,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
21315
20840
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
21316
20841
|
context.report({
|
|
21317
20842
|
node,
|
|
21318
|
-
message: MESSAGE$
|
|
20843
|
+
message: MESSAGE$45
|
|
21319
20844
|
});
|
|
21320
20845
|
}
|
|
21321
20846
|
};
|
|
@@ -21573,7 +21098,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
21573
21098
|
]);
|
|
21574
21099
|
//#endregion
|
|
21575
21100
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
21576
|
-
const MESSAGE$
|
|
21101
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
21577
21102
|
const isAccessorPredicateName = (propName) => {
|
|
21578
21103
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
21579
21104
|
if (propName.length <= prefix.length) continue;
|
|
@@ -21779,7 +21304,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
21779
21304
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
21780
21305
|
context.report({
|
|
21781
21306
|
node,
|
|
21782
|
-
message: MESSAGE$
|
|
21307
|
+
message: MESSAGE$44
|
|
21783
21308
|
});
|
|
21784
21309
|
}
|
|
21785
21310
|
};
|
|
@@ -21999,7 +21524,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
21999
21524
|
];
|
|
22000
21525
|
//#endregion
|
|
22001
21526
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
22002
|
-
const MESSAGE$
|
|
21527
|
+
const MESSAGE$43 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
22003
21528
|
const isConfigObjectPropName = (propName) => {
|
|
22004
21529
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
22005
21530
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -22088,7 +21613,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22088
21613
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
22089
21614
|
context.report({
|
|
22090
21615
|
node,
|
|
22091
|
-
message: MESSAGE$
|
|
21616
|
+
message: MESSAGE$43
|
|
22092
21617
|
});
|
|
22093
21618
|
}
|
|
22094
21619
|
};
|
|
@@ -22096,7 +21621,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22096
21621
|
});
|
|
22097
21622
|
//#endregion
|
|
22098
21623
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
22099
|
-
const MESSAGE$
|
|
21624
|
+
const MESSAGE$42 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
22100
21625
|
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;
|
|
22101
21626
|
const resolveSettings$29 = (settings) => {
|
|
22102
21627
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -22134,7 +21659,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
22134
21659
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
22135
21660
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
22136
21661
|
node: attribute,
|
|
22137
|
-
message: MESSAGE$
|
|
21662
|
+
message: MESSAGE$42
|
|
22138
21663
|
});
|
|
22139
21664
|
}
|
|
22140
21665
|
} };
|
|
@@ -22754,7 +22279,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
22754
22279
|
});
|
|
22755
22280
|
//#endregion
|
|
22756
22281
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
22757
|
-
const MESSAGE$
|
|
22282
|
+
const MESSAGE$41 = "You can't tell what props reach this element when you spread them.";
|
|
22758
22283
|
const resolveSettings$25 = (settings) => {
|
|
22759
22284
|
const reactDoctor = settings?.["react-doctor"];
|
|
22760
22285
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -22797,7 +22322,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
22797
22322
|
}
|
|
22798
22323
|
context.report({
|
|
22799
22324
|
node: attribute,
|
|
22800
|
-
message: MESSAGE$
|
|
22325
|
+
message: MESSAGE$41
|
|
22801
22326
|
});
|
|
22802
22327
|
didReportInFile = true;
|
|
22803
22328
|
return;
|
|
@@ -23073,7 +22598,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
23073
22598
|
});
|
|
23074
22599
|
//#endregion
|
|
23075
22600
|
//#region src/plugin/rules/a11y/lang.ts
|
|
23076
|
-
const MESSAGE$
|
|
22601
|
+
const MESSAGE$40 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
|
|
23077
22602
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
23078
22603
|
"aa",
|
|
23079
22604
|
"ab",
|
|
@@ -23553,7 +23078,7 @@ const lang = defineRule({
|
|
|
23553
23078
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
23554
23079
|
context.report({
|
|
23555
23080
|
node: langAttr,
|
|
23556
|
-
message: MESSAGE$
|
|
23081
|
+
message: MESSAGE$40
|
|
23557
23082
|
});
|
|
23558
23083
|
return;
|
|
23559
23084
|
}
|
|
@@ -23562,7 +23087,7 @@ const lang = defineRule({
|
|
|
23562
23087
|
if (value === null) return;
|
|
23563
23088
|
if (!isValidLangTag(value)) context.report({
|
|
23564
23089
|
node: langAttr,
|
|
23565
|
-
message: MESSAGE$
|
|
23090
|
+
message: MESSAGE$40
|
|
23566
23091
|
});
|
|
23567
23092
|
} })
|
|
23568
23093
|
});
|
|
@@ -23613,7 +23138,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
23613
23138
|
});
|
|
23614
23139
|
//#endregion
|
|
23615
23140
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
23616
|
-
const MESSAGE$
|
|
23141
|
+
const MESSAGE$39 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
23617
23142
|
const DEFAULT_AUDIO = ["audio"];
|
|
23618
23143
|
const DEFAULT_VIDEO = ["video"];
|
|
23619
23144
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -23702,7 +23227,7 @@ const mediaHasCaption = defineRule({
|
|
|
23702
23227
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
23703
23228
|
context.report({
|
|
23704
23229
|
node: node.name,
|
|
23705
|
-
message: MESSAGE$
|
|
23230
|
+
message: MESSAGE$39
|
|
23706
23231
|
});
|
|
23707
23232
|
return;
|
|
23708
23233
|
}
|
|
@@ -23721,7 +23246,7 @@ const mediaHasCaption = defineRule({
|
|
|
23721
23246
|
return kindValue.value.toLowerCase() === "captions";
|
|
23722
23247
|
})) context.report({
|
|
23723
23248
|
node: node.name,
|
|
23724
|
-
message: MESSAGE$
|
|
23249
|
+
message: MESSAGE$39
|
|
23725
23250
|
});
|
|
23726
23251
|
} };
|
|
23727
23252
|
}
|
|
@@ -25264,7 +24789,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
25264
24789
|
});
|
|
25265
24790
|
//#endregion
|
|
25266
24791
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
25267
|
-
const MESSAGE$
|
|
24792
|
+
const MESSAGE$38 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
25268
24793
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
25269
24794
|
const noAccessKey = defineRule({
|
|
25270
24795
|
id: "no-access-key",
|
|
@@ -25283,7 +24808,7 @@ const noAccessKey = defineRule({
|
|
|
25283
24808
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
25284
24809
|
context.report({
|
|
25285
24810
|
node: accessKey,
|
|
25286
|
-
message: MESSAGE$
|
|
24811
|
+
message: MESSAGE$38
|
|
25287
24812
|
});
|
|
25288
24813
|
return;
|
|
25289
24814
|
}
|
|
@@ -25293,7 +24818,7 @@ const noAccessKey = defineRule({
|
|
|
25293
24818
|
if (isUndefinedIdentifier(expression)) return;
|
|
25294
24819
|
context.report({
|
|
25295
24820
|
node: accessKey,
|
|
25296
|
-
message: MESSAGE$
|
|
24821
|
+
message: MESSAGE$38
|
|
25297
24822
|
});
|
|
25298
24823
|
}
|
|
25299
24824
|
} };
|
|
@@ -25598,11 +25123,8 @@ const unwrapUseCallback = (node) => {
|
|
|
25598
25123
|
const callee = node.callee;
|
|
25599
25124
|
return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
|
|
25600
25125
|
};
|
|
25601
|
-
const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
|
|
25602
25126
|
const resolveToFunction = (ref) => {
|
|
25603
|
-
const
|
|
25604
|
-
if (!definition || hasParameterDefinition(ref)) return null;
|
|
25605
|
-
const definitionNode = definition.node;
|
|
25127
|
+
const definitionNode = ref.resolved?.defs[0]?.node;
|
|
25606
25128
|
if (!definitionNode) return null;
|
|
25607
25129
|
if (isFunctionLike$2(definitionNode)) return definitionNode;
|
|
25608
25130
|
if (isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
@@ -26012,7 +25534,7 @@ const isCleanupReturnArgument = (analysis, node) => {
|
|
|
26012
25534
|
if (isNodeOfType(node, "MemberExpression")) return true;
|
|
26013
25535
|
if (isNodeOfType(node, "Identifier")) {
|
|
26014
25536
|
const ref = getRef(analysis, node);
|
|
26015
|
-
if (ref &&
|
|
25537
|
+
if (ref && resolveToFunction(ref)) return true;
|
|
26016
25538
|
}
|
|
26017
25539
|
if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
|
|
26018
25540
|
return false;
|
|
@@ -26484,7 +26006,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
26484
26006
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
26485
26007
|
const reference = getRef(analysis, candidate);
|
|
26486
26008
|
if (!reference) return null;
|
|
26487
|
-
if (reference.resolved?.defs.some((definition) => definition.type === "ImportBinding")) return null;
|
|
26009
|
+
if (reference.resolved?.defs.some((definition) => definition.type === "Parameter" || definition.type === "ImportBinding")) return null;
|
|
26488
26010
|
const resolved = resolveToFunction(reference);
|
|
26489
26011
|
if (resolved) return resolved;
|
|
26490
26012
|
const definitionNode = reference.resolved?.defs[0]?.node;
|
|
@@ -27148,7 +26670,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
27148
26670
|
});
|
|
27149
26671
|
//#endregion
|
|
27150
26672
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
27151
|
-
const MESSAGE$
|
|
26673
|
+
const MESSAGE$37 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
|
|
27152
26674
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
27153
26675
|
"button",
|
|
27154
26676
|
"embed",
|
|
@@ -27260,7 +26782,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
27260
26782
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
27261
26783
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
27262
26784
|
node: ariaHidden,
|
|
27263
|
-
message: MESSAGE$
|
|
26785
|
+
message: MESSAGE$37
|
|
27264
26786
|
});
|
|
27265
26787
|
} })
|
|
27266
26788
|
});
|
|
@@ -28182,7 +27704,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
28182
27704
|
};
|
|
28183
27705
|
//#endregion
|
|
28184
27706
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
28185
|
-
const MESSAGE$
|
|
27707
|
+
const MESSAGE$36 = "Your users can see & submit the wrong data when this list reorders.";
|
|
28186
27708
|
const SECOND_INDEX_METHODS = new Set([
|
|
28187
27709
|
"every",
|
|
28188
27710
|
"filter",
|
|
@@ -28301,14 +27823,14 @@ const noArrayIndexKey = defineRule({
|
|
|
28301
27823
|
if (propName !== "key") continue;
|
|
28302
27824
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
28303
27825
|
node: property,
|
|
28304
|
-
message: MESSAGE$
|
|
27826
|
+
message: MESSAGE$36
|
|
28305
27827
|
});
|
|
28306
27828
|
}
|
|
28307
27829
|
} })
|
|
28308
27830
|
});
|
|
28309
27831
|
//#endregion
|
|
28310
27832
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
28311
|
-
const MESSAGE$
|
|
27833
|
+
const MESSAGE$35 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
|
|
28312
27834
|
const noAsyncEffectCallback = defineRule({
|
|
28313
27835
|
id: "no-async-effect-callback",
|
|
28314
27836
|
title: "Async effect callback",
|
|
@@ -28326,13 +27848,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
28326
27848
|
if (!callback.async) return;
|
|
28327
27849
|
context.report({
|
|
28328
27850
|
node: callback,
|
|
28329
|
-
message: MESSAGE$
|
|
27851
|
+
message: MESSAGE$35
|
|
28330
27852
|
});
|
|
28331
27853
|
} })
|
|
28332
27854
|
});
|
|
28333
27855
|
//#endregion
|
|
28334
27856
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
28335
|
-
const MESSAGE$
|
|
27857
|
+
const MESSAGE$34 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
28336
27858
|
const resolveSettings$21 = (settings) => {
|
|
28337
27859
|
const reactDoctor = settings?.["react-doctor"];
|
|
28338
27860
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -28429,7 +27951,7 @@ const noAutofocus = defineRule({
|
|
|
28429
27951
|
if (isConditionallyRendered(node)) return;
|
|
28430
27952
|
context.report({
|
|
28431
27953
|
node: autoFocusAttribute,
|
|
28432
|
-
message: MESSAGE$
|
|
27954
|
+
message: MESSAGE$34
|
|
28433
27955
|
});
|
|
28434
27956
|
} };
|
|
28435
27957
|
}
|
|
@@ -29402,7 +28924,7 @@ const noChainStateUpdates = defineRule({
|
|
|
29402
28924
|
});
|
|
29403
28925
|
//#endregion
|
|
29404
28926
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
29405
|
-
const MESSAGE$
|
|
28927
|
+
const MESSAGE$33 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
29406
28928
|
const noChildrenProp = defineRule({
|
|
29407
28929
|
id: "no-children-prop",
|
|
29408
28930
|
title: "Children passed as a prop",
|
|
@@ -29414,7 +28936,7 @@ const noChildrenProp = defineRule({
|
|
|
29414
28936
|
if (node.name.name !== "children") return;
|
|
29415
28937
|
context.report({
|
|
29416
28938
|
node: node.name,
|
|
29417
|
-
message: MESSAGE$
|
|
28939
|
+
message: MESSAGE$33
|
|
29418
28940
|
});
|
|
29419
28941
|
},
|
|
29420
28942
|
CallExpression(node) {
|
|
@@ -29427,7 +28949,7 @@ const noChildrenProp = defineRule({
|
|
|
29427
28949
|
const propertyKey = property.key;
|
|
29428
28950
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
29429
28951
|
node: propertyKey,
|
|
29430
|
-
message: MESSAGE$
|
|
28952
|
+
message: MESSAGE$33
|
|
29431
28953
|
});
|
|
29432
28954
|
}
|
|
29433
28955
|
}
|
|
@@ -29435,7 +28957,7 @@ const noChildrenProp = defineRule({
|
|
|
29435
28957
|
});
|
|
29436
28958
|
//#endregion
|
|
29437
28959
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
29438
|
-
const MESSAGE$
|
|
28960
|
+
const MESSAGE$32 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
29439
28961
|
const noCloneElement = defineRule({
|
|
29440
28962
|
id: "no-clone-element",
|
|
29441
28963
|
title: "cloneElement makes child props fragile",
|
|
@@ -29448,7 +28970,7 @@ const noCloneElement = defineRule({
|
|
|
29448
28970
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
29449
28971
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
29450
28972
|
node: callee,
|
|
29451
|
-
message: MESSAGE$
|
|
28973
|
+
message: MESSAGE$32
|
|
29452
28974
|
});
|
|
29453
28975
|
return;
|
|
29454
28976
|
}
|
|
@@ -29461,7 +28983,7 @@ const noCloneElement = defineRule({
|
|
|
29461
28983
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
29462
28984
|
context.report({
|
|
29463
28985
|
node: callee,
|
|
29464
|
-
message: MESSAGE$
|
|
28986
|
+
message: MESSAGE$32
|
|
29465
28987
|
});
|
|
29466
28988
|
}
|
|
29467
28989
|
} })
|
|
@@ -29481,7 +29003,7 @@ const getCallMethodName = (callee) => {
|
|
|
29481
29003
|
};
|
|
29482
29004
|
//#endregion
|
|
29483
29005
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
29484
|
-
const MESSAGE$
|
|
29006
|
+
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
29485
29007
|
const CONTEXT_MODULES = [
|
|
29486
29008
|
"react",
|
|
29487
29009
|
"use-context-selector",
|
|
@@ -29529,13 +29051,13 @@ const noCreateContextInRender = defineRule({
|
|
|
29529
29051
|
if (!componentOrHookName) return;
|
|
29530
29052
|
context.report({
|
|
29531
29053
|
node,
|
|
29532
|
-
message: `${MESSAGE$
|
|
29054
|
+
message: `${MESSAGE$31} (called inside "${componentOrHookName}")`
|
|
29533
29055
|
});
|
|
29534
29056
|
} })
|
|
29535
29057
|
});
|
|
29536
29058
|
//#endregion
|
|
29537
29059
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
29538
|
-
const MESSAGE$
|
|
29060
|
+
const MESSAGE$30 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
|
|
29539
29061
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
29540
29062
|
const parent = functionNode.parent;
|
|
29541
29063
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -29565,7 +29087,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
29565
29087
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
|
|
29566
29088
|
context.report({
|
|
29567
29089
|
node,
|
|
29568
|
-
message: MESSAGE$
|
|
29090
|
+
message: MESSAGE$30
|
|
29569
29091
|
});
|
|
29570
29092
|
} })
|
|
29571
29093
|
});
|
|
@@ -29705,7 +29227,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
29705
29227
|
});
|
|
29706
29228
|
//#endregion
|
|
29707
29229
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
29708
|
-
const MESSAGE$
|
|
29230
|
+
const MESSAGE$29 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
29709
29231
|
const noDanger = defineRule({
|
|
29710
29232
|
id: "no-danger",
|
|
29711
29233
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -29719,7 +29241,7 @@ const noDanger = defineRule({
|
|
|
29719
29241
|
if (!propAttribute) return;
|
|
29720
29242
|
context.report({
|
|
29721
29243
|
node: propAttribute.name,
|
|
29722
|
-
message: MESSAGE$
|
|
29244
|
+
message: MESSAGE$29
|
|
29723
29245
|
});
|
|
29724
29246
|
},
|
|
29725
29247
|
CallExpression(node) {
|
|
@@ -29731,7 +29253,7 @@ const noDanger = defineRule({
|
|
|
29731
29253
|
const propertyKey = property.key;
|
|
29732
29254
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
29733
29255
|
node: propertyKey,
|
|
29734
|
-
message: MESSAGE$
|
|
29256
|
+
message: MESSAGE$29
|
|
29735
29257
|
});
|
|
29736
29258
|
}
|
|
29737
29259
|
}
|
|
@@ -29754,7 +29276,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
29754
29276
|
};
|
|
29755
29277
|
//#endregion
|
|
29756
29278
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
29757
|
-
const MESSAGE$
|
|
29279
|
+
const MESSAGE$28 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
29758
29280
|
const mergePropsShape = (target, source) => {
|
|
29759
29281
|
target.hasDangerously ||= source.hasDangerously;
|
|
29760
29282
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -29829,7 +29351,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29829
29351
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
29830
29352
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
29831
29353
|
node: opening,
|
|
29832
|
-
message: MESSAGE$
|
|
29354
|
+
message: MESSAGE$28
|
|
29833
29355
|
});
|
|
29834
29356
|
},
|
|
29835
29357
|
CallExpression(node) {
|
|
@@ -29842,7 +29364,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29842
29364
|
const positionalChildren = node.arguments.slice(2);
|
|
29843
29365
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
29844
29366
|
node,
|
|
29845
|
-
message: MESSAGE$
|
|
29367
|
+
message: MESSAGE$28
|
|
29846
29368
|
});
|
|
29847
29369
|
}
|
|
29848
29370
|
})
|
|
@@ -30807,7 +30329,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
30807
30329
|
//#endregion
|
|
30808
30330
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
30809
30331
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
30810
|
-
const MESSAGE$
|
|
30332
|
+
const MESSAGE$27 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
30811
30333
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
30812
30334
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
30813
30335
|
let ancestor = setStateCall.parent;
|
|
@@ -30935,7 +30457,7 @@ const noDidMountSetState = defineRule({
|
|
|
30935
30457
|
}
|
|
30936
30458
|
context.report({
|
|
30937
30459
|
node: node.callee,
|
|
30938
|
-
message: MESSAGE$
|
|
30460
|
+
message: MESSAGE$27
|
|
30939
30461
|
});
|
|
30940
30462
|
} };
|
|
30941
30463
|
}
|
|
@@ -30943,7 +30465,7 @@ const noDidMountSetState = defineRule({
|
|
|
30943
30465
|
//#endregion
|
|
30944
30466
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
30945
30467
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
30946
|
-
const MESSAGE$
|
|
30468
|
+
const MESSAGE$26 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
30947
30469
|
const EQUALITY_OPERATORS = new Set([
|
|
30948
30470
|
"==",
|
|
30949
30471
|
"===",
|
|
@@ -31117,7 +30639,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
31117
30639
|
if (isInsideDiffGuard(node)) return;
|
|
31118
30640
|
context.report({
|
|
31119
30641
|
node: node.callee,
|
|
31120
|
-
message: MESSAGE$
|
|
30642
|
+
message: MESSAGE$26
|
|
31121
30643
|
});
|
|
31122
30644
|
} };
|
|
31123
30645
|
}
|
|
@@ -31140,7 +30662,7 @@ const isStateMemberExpression = (node) => {
|
|
|
31140
30662
|
};
|
|
31141
30663
|
//#endregion
|
|
31142
30664
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
31143
|
-
const MESSAGE$
|
|
30665
|
+
const MESSAGE$25 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
|
|
31144
30666
|
const shouldIgnoreMutation = (node) => {
|
|
31145
30667
|
let isConstructor = false;
|
|
31146
30668
|
let isInsideCallExpression = false;
|
|
@@ -31162,7 +30684,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
31162
30684
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
31163
30685
|
context.report({
|
|
31164
30686
|
node: reportNode,
|
|
31165
|
-
message: MESSAGE$
|
|
30687
|
+
message: MESSAGE$25
|
|
31166
30688
|
});
|
|
31167
30689
|
};
|
|
31168
30690
|
const noDirectMutationState = defineRule({
|
|
@@ -31513,7 +31035,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
31513
31035
|
});
|
|
31514
31036
|
//#endregion
|
|
31515
31037
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
31516
|
-
const MESSAGE$
|
|
31038
|
+
const MESSAGE$24 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
31517
31039
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
31518
31040
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
31519
31041
|
const noDocumentWrite = defineRule({
|
|
@@ -31530,7 +31052,7 @@ const noDocumentWrite = defineRule({
|
|
|
31530
31052
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
31531
31053
|
context.report({
|
|
31532
31054
|
node,
|
|
31533
|
-
message: MESSAGE$
|
|
31055
|
+
message: MESSAGE$24
|
|
31534
31056
|
});
|
|
31535
31057
|
} })
|
|
31536
31058
|
});
|
|
@@ -33748,7 +33270,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
33748
33270
|
"ReactDOM",
|
|
33749
33271
|
"ReactDom"
|
|
33750
33272
|
]);
|
|
33751
|
-
const MESSAGE$
|
|
33273
|
+
const MESSAGE$23 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
33752
33274
|
const noFindDomNode = defineRule({
|
|
33753
33275
|
id: "no-find-dom-node",
|
|
33754
33276
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -33759,7 +33281,7 @@ const noFindDomNode = defineRule({
|
|
|
33759
33281
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
33760
33282
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
33761
33283
|
node: callee,
|
|
33762
|
-
message: MESSAGE$
|
|
33284
|
+
message: MESSAGE$23
|
|
33763
33285
|
});
|
|
33764
33286
|
return;
|
|
33765
33287
|
}
|
|
@@ -33770,7 +33292,7 @@ const noFindDomNode = defineRule({
|
|
|
33770
33292
|
if (callee.property.name !== "findDOMNode") return;
|
|
33771
33293
|
context.report({
|
|
33772
33294
|
node: callee.property,
|
|
33773
|
-
message: MESSAGE$
|
|
33295
|
+
message: MESSAGE$23
|
|
33774
33296
|
});
|
|
33775
33297
|
}
|
|
33776
33298
|
} })
|
|
@@ -33979,6 +33501,13 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
33979
33501
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
33980
33502
|
};
|
|
33981
33503
|
//#endregion
|
|
33504
|
+
//#region src/plugin/utils/is-type-only-import.ts
|
|
33505
|
+
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
33506
|
+
if (!specifiers || specifiers.length === 0) return false;
|
|
33507
|
+
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
33508
|
+
};
|
|
33509
|
+
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
33510
|
+
//#endregion
|
|
33982
33511
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
33983
33512
|
const isLibraryDevPage = (filename) => {
|
|
33984
33513
|
if (!filename) return false;
|
|
@@ -34677,7 +34206,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34677
34206
|
});
|
|
34678
34207
|
//#endregion
|
|
34679
34208
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
34680
|
-
const MESSAGE$
|
|
34209
|
+
const MESSAGE$22 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
|
|
34681
34210
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
34682
34211
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
34683
34212
|
title: "Lazy image with high fetchPriority",
|
|
@@ -34691,7 +34220,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
34691
34220
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
34692
34221
|
context.report({
|
|
34693
34222
|
node: node.name,
|
|
34694
|
-
message: MESSAGE$
|
|
34223
|
+
message: MESSAGE$22
|
|
34695
34224
|
});
|
|
34696
34225
|
} })
|
|
34697
34226
|
});
|
|
@@ -34880,7 +34409,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
34880
34409
|
});
|
|
34881
34410
|
//#endregion
|
|
34882
34411
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
34883
|
-
const MESSAGE$
|
|
34412
|
+
const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
34884
34413
|
const REACT_USE_REF_OPTIONS = {
|
|
34885
34414
|
allowGlobalReactNamespace: false,
|
|
34886
34415
|
allowUnboundBareCalls: false
|
|
@@ -34981,7 +34510,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34981
34510
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
34982
34511
|
context.report({
|
|
34983
34512
|
node: indeterminateAttribute,
|
|
34984
|
-
message: MESSAGE$
|
|
34513
|
+
message: MESSAGE$21
|
|
34985
34514
|
});
|
|
34986
34515
|
},
|
|
34987
34516
|
CallExpression(node) {
|
|
@@ -34990,7 +34519,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34990
34519
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
34991
34520
|
context.report({
|
|
34992
34521
|
node,
|
|
34993
|
-
message: MESSAGE$
|
|
34522
|
+
message: MESSAGE$21
|
|
34994
34523
|
});
|
|
34995
34524
|
}
|
|
34996
34525
|
};
|
|
@@ -35278,7 +34807,7 @@ const noIsMounted = defineRule({
|
|
|
35278
34807
|
});
|
|
35279
34808
|
//#endregion
|
|
35280
34809
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
35281
|
-
const MESSAGE$
|
|
34810
|
+
const MESSAGE$20 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
|
|
35282
34811
|
const isJsonMethodCall = (node, method) => {
|
|
35283
34812
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
35284
34813
|
const callee = node.callee;
|
|
@@ -35342,13 +34871,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
35342
34871
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
35343
34872
|
context.report({
|
|
35344
34873
|
node,
|
|
35345
|
-
message: MESSAGE$
|
|
34874
|
+
message: MESSAGE$20
|
|
35346
34875
|
});
|
|
35347
34876
|
} })
|
|
35348
34877
|
});
|
|
35349
34878
|
//#endregion
|
|
35350
34879
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
35351
|
-
const MESSAGE$
|
|
34880
|
+
const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
35352
34881
|
const isJsxElementTypeReference = (node) => {
|
|
35353
34882
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
35354
34883
|
const typeName = node.typeName;
|
|
@@ -35402,7 +34931,7 @@ const noJsxElementType = defineRule({
|
|
|
35402
34931
|
if (isJsxImported) return;
|
|
35403
34932
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
35404
34933
|
node: typeAnnotation,
|
|
35405
|
-
message: MESSAGE$
|
|
34934
|
+
message: MESSAGE$19
|
|
35406
34935
|
});
|
|
35407
34936
|
}
|
|
35408
34937
|
};
|
|
@@ -35634,221 +35163,6 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
35634
35163
|
}
|
|
35635
35164
|
});
|
|
35636
35165
|
//#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
|
|
35852
35166
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
35853
35167
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
35854
35168
|
"childContextTypes",
|
|
@@ -35859,6 +35173,15 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
35859
35173
|
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.`;
|
|
35860
35174
|
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.";
|
|
35861
35175
|
};
|
|
35176
|
+
const isInsideClassBody = (node) => {
|
|
35177
|
+
let current = node.parent;
|
|
35178
|
+
while (current) {
|
|
35179
|
+
if (isNodeOfType(current, "ClassBody")) return true;
|
|
35180
|
+
if (isFunctionLike$2(current)) return false;
|
|
35181
|
+
current = current.parent;
|
|
35182
|
+
}
|
|
35183
|
+
return false;
|
|
35184
|
+
};
|
|
35862
35185
|
const noLegacyContextApi = defineRule({
|
|
35863
35186
|
id: "no-legacy-context-api",
|
|
35864
35187
|
title: "Legacy context API",
|
|
@@ -35873,7 +35196,6 @@ const noLegacyContextApi = defineRule({
|
|
|
35873
35196
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
35874
35197
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
35875
35198
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
35876
|
-
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
35877
35199
|
context.report({
|
|
35878
35200
|
node: memberNode.key,
|
|
35879
35201
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -35881,8 +35203,6 @@ const noLegacyContextApi = defineRule({
|
|
|
35881
35203
|
};
|
|
35882
35204
|
return {
|
|
35883
35205
|
ClassBody(node) {
|
|
35884
|
-
const classNode = node.parent;
|
|
35885
|
-
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
35886
35206
|
for (const member of node.body ?? []) checkMember(member);
|
|
35887
35207
|
},
|
|
35888
35208
|
AssignmentExpression(node) {
|
|
@@ -35892,11 +35212,9 @@ const noLegacyContextApi = defineRule({
|
|
|
35892
35212
|
if (left.computed) return;
|
|
35893
35213
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
35894
35214
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
35895
|
-
if (left.
|
|
35896
|
-
|
|
35897
|
-
if (
|
|
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;
|
|
35215
|
+
if (!isNodeOfType(left.object, "Identifier")) return;
|
|
35216
|
+
if (!isUppercaseName(left.object.name)) return;
|
|
35217
|
+
if (isInsideClassBody(node)) return;
|
|
35900
35218
|
context.report({
|
|
35901
35219
|
node: left,
|
|
35902
35220
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -36718,7 +36036,7 @@ const noMoment = defineRule({
|
|
|
36718
36036
|
});
|
|
36719
36037
|
//#endregion
|
|
36720
36038
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
36721
|
-
const MESSAGE$
|
|
36039
|
+
const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
36722
36040
|
const resolveSettings$16 = (settings) => {
|
|
36723
36041
|
const reactDoctor = settings?.["react-doctor"];
|
|
36724
36042
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -37077,7 +36395,7 @@ const noMultiComp = defineRule({
|
|
|
37077
36395
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
37078
36396
|
for (const component of flagged.slice(1)) context.report({
|
|
37079
36397
|
node: component.reportNode,
|
|
37080
|
-
message: MESSAGE$
|
|
36398
|
+
message: MESSAGE$18
|
|
37081
36399
|
});
|
|
37082
36400
|
} };
|
|
37083
36401
|
}
|
|
@@ -37290,7 +36608,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
37290
36608
|
};
|
|
37291
36609
|
//#endregion
|
|
37292
36610
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
37293
|
-
const MESSAGE$
|
|
36611
|
+
const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
|
|
37294
36612
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
37295
36613
|
"copyWithin",
|
|
37296
36614
|
"fill",
|
|
@@ -37499,7 +36817,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37499
36817
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
37500
36818
|
context.report({
|
|
37501
36819
|
node: options.crossFileConsumerCallSite,
|
|
37502
|
-
message: `${MESSAGE$
|
|
36820
|
+
message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
37503
36821
|
});
|
|
37504
36822
|
return;
|
|
37505
36823
|
}
|
|
@@ -37508,7 +36826,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37508
36826
|
reportedNodes.add(mutation.node);
|
|
37509
36827
|
context.report({
|
|
37510
36828
|
node: mutation.node,
|
|
37511
|
-
message: MESSAGE$
|
|
36829
|
+
message: MESSAGE$17
|
|
37512
36830
|
});
|
|
37513
36831
|
}
|
|
37514
36832
|
};
|
|
@@ -37915,7 +37233,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
37915
37233
|
});
|
|
37916
37234
|
//#endregion
|
|
37917
37235
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
37918
|
-
const MESSAGE$
|
|
37236
|
+
const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
37919
37237
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
37920
37238
|
"onKeyDown",
|
|
37921
37239
|
"onKeyUp",
|
|
@@ -38034,7 +37352,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38034
37352
|
if (numeric === null) {
|
|
38035
37353
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
38036
37354
|
node: tabIndex,
|
|
38037
|
-
message: MESSAGE$
|
|
37355
|
+
message: MESSAGE$16
|
|
38038
37356
|
});
|
|
38039
37357
|
return;
|
|
38040
37358
|
}
|
|
@@ -38056,7 +37374,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38056
37374
|
if (!roleAttribute) {
|
|
38057
37375
|
context.report({
|
|
38058
37376
|
node: tabIndex,
|
|
38059
|
-
message: MESSAGE$
|
|
37377
|
+
message: MESSAGE$16
|
|
38060
37378
|
});
|
|
38061
37379
|
return;
|
|
38062
37380
|
}
|
|
@@ -38070,7 +37388,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38070
37388
|
}
|
|
38071
37389
|
context.report({
|
|
38072
37390
|
node: tabIndex,
|
|
38073
|
-
message: MESSAGE$
|
|
37391
|
+
message: MESSAGE$16
|
|
38074
37392
|
});
|
|
38075
37393
|
} };
|
|
38076
37394
|
}
|
|
@@ -38497,6 +37815,8 @@ const getWrapperHookWrappedFunction = (initializer) => {
|
|
|
38497
37815
|
if (!wrapped || !isFunctionLike$2(wrapped)) return null;
|
|
38498
37816
|
return wrapped;
|
|
38499
37817
|
};
|
|
37818
|
+
const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
|
|
37819
|
+
const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
|
|
38500
37820
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
38501
37821
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
38502
37822
|
if (!isProp(analysis, innerRef)) return false;
|
|
@@ -38817,7 +38137,7 @@ const noPassDataToParent = defineRule({
|
|
|
38817
38137
|
if (isConstant(argRef)) return false;
|
|
38818
38138
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
38819
38139
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
38820
|
-
if (
|
|
38140
|
+
if (resolvesToFunctionBinding(argRef)) return false;
|
|
38821
38141
|
const argIdentifier = argRef.identifier;
|
|
38822
38142
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
38823
38143
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
@@ -39489,6 +38809,174 @@ const noPropCallbackInRender = defineRule({
|
|
|
39489
38809
|
} })
|
|
39490
38810
|
});
|
|
39491
38811
|
//#endregion
|
|
38812
|
+
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
38813
|
+
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
38814
|
+
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
38815
|
+
const expression = stripParenExpression(node);
|
|
38816
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
38817
|
+
const propertyName = getStaticPropertyName(expression);
|
|
38818
|
+
const receiver = stripParenExpression(expression.object);
|
|
38819
|
+
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
38820
|
+
}
|
|
38821
|
+
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
38822
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
38823
|
+
const symbol = scopes.symbolFor(expression);
|
|
38824
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
38825
|
+
visitedSymbolIds.add(symbol.id);
|
|
38826
|
+
if (isImportedFromReact(symbol)) {
|
|
38827
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
38828
|
+
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
38829
|
+
}
|
|
38830
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
38831
|
+
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
38832
|
+
};
|
|
38833
|
+
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
38834
|
+
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
38835
|
+
visitedClassNodes.add(classNode);
|
|
38836
|
+
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
38837
|
+
};
|
|
38838
|
+
//#endregion
|
|
38839
|
+
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
38840
|
+
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
38841
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
38842
|
+
let containsReactHookCall = false;
|
|
38843
|
+
walkAst(functionNode.body, (node) => {
|
|
38844
|
+
if (containsReactHookCall) return false;
|
|
38845
|
+
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
38846
|
+
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
38847
|
+
containsReactHookCall = true;
|
|
38848
|
+
return false;
|
|
38849
|
+
}
|
|
38850
|
+
});
|
|
38851
|
+
return containsReactHookCall;
|
|
38852
|
+
};
|
|
38853
|
+
//#endregion
|
|
38854
|
+
//#region src/plugin/utils/function-returns-props-children.ts
|
|
38855
|
+
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
38856
|
+
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
38857
|
+
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
38858
|
+
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
38859
|
+
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
38860
|
+
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
38861
|
+
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
38862
|
+
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
38863
|
+
const propertyValue = stripParenExpression(property.value);
|
|
38864
|
+
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
38865
|
+
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
38866
|
+
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
38867
|
+
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
38868
|
+
}
|
|
38869
|
+
}
|
|
38870
|
+
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
38871
|
+
const candidate = stripParenExpression(expression);
|
|
38872
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
38873
|
+
const symbol = scopes.symbolFor(candidate);
|
|
38874
|
+
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
38875
|
+
}
|
|
38876
|
+
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
38877
|
+
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
38878
|
+
const receiver = stripParenExpression(candidate.object);
|
|
38879
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
38880
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
38881
|
+
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
38882
|
+
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
38883
|
+
}, controlFlow);
|
|
38884
|
+
};
|
|
38885
|
+
//#endregion
|
|
38886
|
+
//#region src/plugin/utils/function-returns-only-null.ts
|
|
38887
|
+
const isNullExpression = (expression) => {
|
|
38888
|
+
const candidate = stripParenExpression(expression);
|
|
38889
|
+
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
38890
|
+
};
|
|
38891
|
+
const functionReturnsOnlyNull = (functionNode) => {
|
|
38892
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
38893
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
38894
|
+
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
38895
|
+
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
38896
|
+
};
|
|
38897
|
+
//#endregion
|
|
38898
|
+
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
38899
|
+
const findFactoryRoot = (node) => {
|
|
38900
|
+
const candidate = stripParenExpression(node);
|
|
38901
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
38902
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
38903
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
38904
|
+
return null;
|
|
38905
|
+
};
|
|
38906
|
+
const findFactoryPropertyName = (node) => {
|
|
38907
|
+
const candidate = stripParenExpression(node);
|
|
38908
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
38909
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
38910
|
+
return null;
|
|
38911
|
+
};
|
|
38912
|
+
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
38913
|
+
const candidate = stripParenExpression(expression);
|
|
38914
|
+
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
38915
|
+
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
38916
|
+
if (!factoryRoot) return false;
|
|
38917
|
+
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
38918
|
+
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
38919
|
+
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
38920
|
+
if (!symbol || symbol.kind !== "import") return false;
|
|
38921
|
+
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
38922
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
38923
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
38924
|
+
};
|
|
38925
|
+
//#endregion
|
|
38926
|
+
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
38927
|
+
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
38928
|
+
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
38929
|
+
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
38930
|
+
const candidate = stripParenExpression(expression);
|
|
38931
|
+
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
38932
|
+
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
38933
|
+
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
38934
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
38935
|
+
const symbol = scopes.symbolFor(candidate);
|
|
38936
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
38937
|
+
visitedSymbolIds.add(symbol.id);
|
|
38938
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
38939
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
38940
|
+
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
38941
|
+
}
|
|
38942
|
+
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
38943
|
+
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
38944
|
+
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
38945
|
+
const wrappedComponent = candidate.arguments[0];
|
|
38946
|
+
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
38947
|
+
}
|
|
38948
|
+
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
38949
|
+
const factory = candidate.arguments[0];
|
|
38950
|
+
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
38951
|
+
const unwrappedFactory = stripParenExpression(factory);
|
|
38952
|
+
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
38953
|
+
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
38954
|
+
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
38955
|
+
const returnedExpression = returnStatements[0]?.argument;
|
|
38956
|
+
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
38957
|
+
};
|
|
38958
|
+
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
38959
|
+
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
38960
|
+
for (const candidateSymbol of candidateSymbols) {
|
|
38961
|
+
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
38962
|
+
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
38963
|
+
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
38964
|
+
continue;
|
|
38965
|
+
}
|
|
38966
|
+
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
38967
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
38968
|
+
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
38969
|
+
continue;
|
|
38970
|
+
}
|
|
38971
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
38972
|
+
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
38973
|
+
continue;
|
|
38974
|
+
}
|
|
38975
|
+
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
38976
|
+
}
|
|
38977
|
+
return false;
|
|
38978
|
+
};
|
|
38979
|
+
//#endregion
|
|
39492
38980
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
39493
38981
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
39494
38982
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -39696,7 +39184,7 @@ const noRandomKey = defineRule({
|
|
|
39696
39184
|
});
|
|
39697
39185
|
//#endregion
|
|
39698
39186
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
39699
|
-
const MESSAGE$
|
|
39187
|
+
const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
39700
39188
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
39701
39189
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
39702
39190
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -39722,13 +39210,13 @@ const noReactChildren = defineRule({
|
|
|
39722
39210
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
39723
39211
|
context.report({
|
|
39724
39212
|
node: calleeOuter,
|
|
39725
|
-
message: MESSAGE$
|
|
39213
|
+
message: MESSAGE$15
|
|
39726
39214
|
});
|
|
39727
39215
|
return;
|
|
39728
39216
|
}
|
|
39729
39217
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
39730
39218
|
node: calleeOuter,
|
|
39731
|
-
message: MESSAGE$
|
|
39219
|
+
message: MESSAGE$15
|
|
39732
39220
|
});
|
|
39733
39221
|
} })
|
|
39734
39222
|
});
|
|
@@ -40409,7 +39897,7 @@ const noRenderPropChildren = defineRule({
|
|
|
40409
39897
|
});
|
|
40410
39898
|
//#endregion
|
|
40411
39899
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
40412
|
-
const MESSAGE$
|
|
39900
|
+
const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
40413
39901
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
40414
39902
|
const isUsedAsReturnValue = (parent) => {
|
|
40415
39903
|
if (!parent) return false;
|
|
@@ -40427,7 +39915,7 @@ const noRenderReturnValue = defineRule({
|
|
|
40427
39915
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
40428
39916
|
context.report({
|
|
40429
39917
|
node: node.callee,
|
|
40430
|
-
message: MESSAGE$
|
|
39918
|
+
message: MESSAGE$14
|
|
40431
39919
|
});
|
|
40432
39920
|
} })
|
|
40433
39921
|
});
|
|
@@ -41236,7 +40724,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
41236
40724
|
});
|
|
41237
40725
|
//#endregion
|
|
41238
40726
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
41239
|
-
const MESSAGE$
|
|
40727
|
+
const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
41240
40728
|
const noSetState = defineRule({
|
|
41241
40729
|
id: "no-set-state",
|
|
41242
40730
|
title: "Local class state forbidden",
|
|
@@ -41251,7 +40739,7 @@ const noSetState = defineRule({
|
|
|
41251
40739
|
if (!getParentComponent(node)) return;
|
|
41252
40740
|
context.report({
|
|
41253
40741
|
node: node.callee,
|
|
41254
|
-
message: MESSAGE$
|
|
40742
|
+
message: MESSAGE$13
|
|
41255
40743
|
});
|
|
41256
40744
|
} })
|
|
41257
40745
|
});
|
|
@@ -41621,7 +41109,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
41621
41109
|
};
|
|
41622
41110
|
//#endregion
|
|
41623
41111
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
41624
|
-
const MESSAGE$
|
|
41112
|
+
const MESSAGE$12 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
|
|
41625
41113
|
const DEFAULT_HANDLERS = [
|
|
41626
41114
|
"onClick",
|
|
41627
41115
|
"onMouseDown",
|
|
@@ -41736,7 +41224,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41736
41224
|
if (!roleAttribute || !roleAttribute.value) {
|
|
41737
41225
|
context.report({
|
|
41738
41226
|
node: node.name,
|
|
41739
|
-
message: MESSAGE$
|
|
41227
|
+
message: MESSAGE$12
|
|
41740
41228
|
});
|
|
41741
41229
|
return;
|
|
41742
41230
|
}
|
|
@@ -41746,7 +41234,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41746
41234
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
41747
41235
|
context.report({
|
|
41748
41236
|
node: node.name,
|
|
41749
|
-
message: MESSAGE$
|
|
41237
|
+
message: MESSAGE$12
|
|
41750
41238
|
});
|
|
41751
41239
|
return;
|
|
41752
41240
|
}
|
|
@@ -41754,7 +41242,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41754
41242
|
if (!settings.allowExpressionValues) {
|
|
41755
41243
|
context.report({
|
|
41756
41244
|
node: node.name,
|
|
41757
|
-
message: MESSAGE$
|
|
41245
|
+
message: MESSAGE$12
|
|
41758
41246
|
});
|
|
41759
41247
|
return;
|
|
41760
41248
|
}
|
|
@@ -41762,7 +41250,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41762
41250
|
if (isStaticNullishExpression(expression)) {
|
|
41763
41251
|
context.report({
|
|
41764
41252
|
node: node.name,
|
|
41765
|
-
message: MESSAGE$
|
|
41253
|
+
message: MESSAGE$12
|
|
41766
41254
|
});
|
|
41767
41255
|
return;
|
|
41768
41256
|
}
|
|
@@ -41772,7 +41260,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41772
41260
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
41773
41261
|
context.report({
|
|
41774
41262
|
node: node.name,
|
|
41775
|
-
message: MESSAGE$
|
|
41263
|
+
message: MESSAGE$12
|
|
41776
41264
|
});
|
|
41777
41265
|
return;
|
|
41778
41266
|
}
|
|
@@ -41780,7 +41268,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41780
41268
|
}
|
|
41781
41269
|
context.report({
|
|
41782
41270
|
node: node.name,
|
|
41783
|
-
message: MESSAGE$
|
|
41271
|
+
message: MESSAGE$12
|
|
41784
41272
|
});
|
|
41785
41273
|
} };
|
|
41786
41274
|
}
|
|
@@ -41886,7 +41374,7 @@ const noStringRefs = defineRule({
|
|
|
41886
41374
|
});
|
|
41887
41375
|
//#endregion
|
|
41888
41376
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
41889
|
-
const MESSAGE$
|
|
41377
|
+
const MESSAGE$11 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
|
|
41890
41378
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
41891
41379
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
41892
41380
|
const noSyncXhr = defineRule({
|
|
@@ -41907,7 +41395,7 @@ const noSyncXhr = defineRule({
|
|
|
41907
41395
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
41908
41396
|
context.report({
|
|
41909
41397
|
node,
|
|
41910
|
-
message: MESSAGE$
|
|
41398
|
+
message: MESSAGE$11
|
|
41911
41399
|
});
|
|
41912
41400
|
};
|
|
41913
41401
|
return {
|
|
@@ -41932,7 +41420,7 @@ const noSyncXhr = defineRule({
|
|
|
41932
41420
|
});
|
|
41933
41421
|
//#endregion
|
|
41934
41422
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
41935
|
-
const MESSAGE$
|
|
41423
|
+
const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
|
|
41936
41424
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
41937
41425
|
let ancestor = node.parent;
|
|
41938
41426
|
while (ancestor) {
|
|
@@ -42020,7 +41508,7 @@ const noThisInSfc = defineRule({
|
|
|
42020
41508
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
42021
41509
|
context.report({
|
|
42022
41510
|
node,
|
|
42023
|
-
message: MESSAGE$
|
|
41511
|
+
message: MESSAGE$10
|
|
42024
41512
|
});
|
|
42025
41513
|
} };
|
|
42026
41514
|
}
|
|
@@ -43901,7 +43389,6 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43901
43389
|
severity: "warn",
|
|
43902
43390
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
43903
43391
|
category: "Performance",
|
|
43904
|
-
tags: ["react-jsx-only"],
|
|
43905
43392
|
create: (context) => {
|
|
43906
43393
|
const settings = resolveSettings$8(context.settings);
|
|
43907
43394
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -44168,7 +43655,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
44168
43655
|
//#endregion
|
|
44169
43656
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
44170
43657
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
44171
|
-
const MESSAGE$
|
|
43658
|
+
const MESSAGE$9 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
44172
43659
|
const resolveSettings$7 = (settings) => {
|
|
44173
43660
|
const reactDoctor = settings?.["react-doctor"];
|
|
44174
43661
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -44202,7 +43689,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
44202
43689
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
44203
43690
|
context.report({
|
|
44204
43691
|
node: node.callee,
|
|
44205
|
-
message: MESSAGE$
|
|
43692
|
+
message: MESSAGE$9
|
|
44206
43693
|
});
|
|
44207
43694
|
} };
|
|
44208
43695
|
}
|
|
@@ -45198,7 +44685,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
45198
44685
|
});
|
|
45199
44686
|
//#endregion
|
|
45200
44687
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
45201
|
-
const MESSAGE$
|
|
44688
|
+
const MESSAGE$8 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
|
|
45202
44689
|
const preactPreferOndblclick = defineRule({
|
|
45203
44690
|
id: "preact-prefer-ondblclick",
|
|
45204
44691
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -45213,7 +44700,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
45213
44700
|
if (!onDoubleClickAttribute) return;
|
|
45214
44701
|
context.report({
|
|
45215
44702
|
node: onDoubleClickAttribute,
|
|
45216
|
-
message: MESSAGE$
|
|
44703
|
+
message: MESSAGE$8
|
|
45217
44704
|
});
|
|
45218
44705
|
} })
|
|
45219
44706
|
});
|
|
@@ -45464,7 +44951,7 @@ const preferExplicitVariants = defineRule({
|
|
|
45464
44951
|
});
|
|
45465
44952
|
//#endregion
|
|
45466
44953
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
45467
|
-
const MESSAGE$
|
|
44954
|
+
const MESSAGE$7 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
|
|
45468
44955
|
const resolveSettings$4 = (settings) => {
|
|
45469
44956
|
const reactDoctor = settings?.["react-doctor"];
|
|
45470
44957
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -45503,7 +44990,7 @@ const preferFunctionComponent = defineRule({
|
|
|
45503
44990
|
const reportNode = node.id ?? node;
|
|
45504
44991
|
context.report({
|
|
45505
44992
|
node: reportNode,
|
|
45506
|
-
message: MESSAGE$
|
|
44993
|
+
message: MESSAGE$7
|
|
45507
44994
|
});
|
|
45508
44995
|
};
|
|
45509
44996
|
return {
|
|
@@ -47081,258 +46568,7 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
47081
46568
|
}
|
|
47082
46569
|
});
|
|
47083
46570
|
//#endregion
|
|
47084
|
-
//#region src/plugin/utils/is-node-conditionally-executed.ts
|
|
47085
|
-
const isNodeConditionallyExecuted = (node, boundary) => {
|
|
47086
|
-
let child = node;
|
|
47087
|
-
let parent = child.parent ?? null;
|
|
47088
|
-
while (parent && parent !== boundary) {
|
|
47089
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) return true;
|
|
47090
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) return true;
|
|
47091
|
-
if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) return true;
|
|
47092
|
-
child = parent;
|
|
47093
|
-
parent = child.parent ?? null;
|
|
47094
|
-
}
|
|
47095
|
-
return false;
|
|
47096
|
-
};
|
|
47097
|
-
//#endregion
|
|
47098
|
-
//#region src/plugin/rules/tanstack-query/utils/resolve-tanstack-query-hook-name.ts
|
|
47099
|
-
const resolveTanstackNamespaceHookName = (memberExpression, contextNode, scopes) => {
|
|
47100
|
-
const hookName = getStaticPropertyKeyName(memberExpression, { allowComputedString: true });
|
|
47101
|
-
const namespaceObject = stripParenExpression(memberExpression.object);
|
|
47102
|
-
if (!hookName || !TANSTACK_QUERY_HOOKS.has(hookName)) return null;
|
|
47103
|
-
if (!isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
47104
|
-
const resolvedNamespaceSymbol = resolveConstIdentifierAlias(namespaceObject, scopes);
|
|
47105
|
-
if (resolvedNamespaceSymbol?.kind !== "import") return null;
|
|
47106
|
-
const namespaceBinding = getImportBindingForName(contextNode, resolvedNamespaceSymbol.name);
|
|
47107
|
-
return namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source) ? hookName : null;
|
|
47108
|
-
};
|
|
47109
|
-
const resolveTanstackQueryHookName = (callExpression, scopes) => {
|
|
47110
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
47111
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
47112
|
-
const resolvedSymbol = resolveConstIdentifierAlias(callee, scopes);
|
|
47113
|
-
if (!resolvedSymbol) return null;
|
|
47114
|
-
if (resolvedSymbol.kind === "const" && resolvedSymbol.initializer) {
|
|
47115
|
-
const initializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47116
|
-
return isNodeOfType(initializer, "MemberExpression") ? resolveTanstackNamespaceHookName(initializer, callExpression, scopes) : null;
|
|
47117
|
-
}
|
|
47118
|
-
if (resolvedSymbol.kind !== "import") return null;
|
|
47119
|
-
const importBinding = getImportBindingForName(callExpression, resolvedSymbol.name);
|
|
47120
|
-
if (importBinding === null) return null;
|
|
47121
|
-
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
47122
|
-
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
47123
|
-
}
|
|
47124
|
-
if (isNodeOfType(callee, "MemberExpression")) return resolveTanstackNamespaceHookName(callee, callExpression, scopes);
|
|
47125
|
-
return null;
|
|
47126
|
-
};
|
|
47127
|
-
const resolveTanstackQueryHookNameFromInitializer = (initializer, scopes) => {
|
|
47128
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
47129
|
-
if (isNodeOfType(unwrappedInitializer, "CallExpression")) return resolveTanstackQueryHookName(unwrappedInitializer, scopes);
|
|
47130
|
-
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return null;
|
|
47131
|
-
const resolvedSymbol = resolveConstIdentifierAlias(unwrappedInitializer, scopes);
|
|
47132
|
-
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
47133
|
-
const resolvedInitializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47134
|
-
if (!isNodeOfType(resolvedInitializer, "CallExpression")) return null;
|
|
47135
|
-
return resolveTanstackQueryHookName(resolvedInitializer, scopes);
|
|
47136
|
-
};
|
|
47137
|
-
//#endregion
|
|
47138
46571
|
//#region src/plugin/rules/tanstack-query/query-no-query-in-effect.ts
|
|
47139
|
-
const isTanstackQueryResult = (expression, context) => Boolean(resolveTanstackQueryHookNameFromInitializer(expression, context.scopes));
|
|
47140
|
-
const isStaticRefetchMember = (memberExpression) => getStaticPropertyKeyName(memberExpression, { allowComputedString: true }) === "refetch";
|
|
47141
|
-
const resolveCalledFunction = (callee, context) => {
|
|
47142
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
47143
|
-
if (isFunctionLike$2(unwrappedCallee)) return unwrappedCallee;
|
|
47144
|
-
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
47145
|
-
const symbol = resolveConstIdentifierAlias(unwrappedCallee, context.scopes);
|
|
47146
|
-
if (!symbol) return null;
|
|
47147
|
-
const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
|
|
47148
|
-
return candidate && isFunctionLike$2(candidate) ? candidate : null;
|
|
47149
|
-
};
|
|
47150
|
-
const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
47151
|
-
if (!isFunctionLike$2(functionNode)) return true;
|
|
47152
|
-
if (functionNode.generator) return true;
|
|
47153
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47154
|
-
if (boundaryStart === null) return true;
|
|
47155
|
-
let hasSuspension = false;
|
|
47156
|
-
walkAst(functionNode, (node) => {
|
|
47157
|
-
if (node !== functionNode && isFunctionLike$2(node)) return false;
|
|
47158
|
-
if (!isNodeOfType(node, "AwaitExpression") || !isNodeReachableWithinFunction(node, context)) return;
|
|
47159
|
-
const suspensionStart = getRangeStart(node);
|
|
47160
|
-
if (suspensionStart !== null && suspensionStart < boundaryStart) {
|
|
47161
|
-
hasSuspension = true;
|
|
47162
|
-
return false;
|
|
47163
|
-
}
|
|
47164
|
-
});
|
|
47165
|
-
return hasSuspension;
|
|
47166
|
-
};
|
|
47167
|
-
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47168
|
-
let enclosingFunction = findEnclosingFunction(functionNode);
|
|
47169
|
-
while (enclosingFunction) {
|
|
47170
|
-
if (enclosingFunction === ancestor) return true;
|
|
47171
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
47172
|
-
}
|
|
47173
|
-
return false;
|
|
47174
|
-
};
|
|
47175
|
-
const isUnconditionallyExecuted = (node, functionNode, context) => context.cfg.isUnconditionalFromEntry(node) && !isNodeConditionallyExecuted(node, functionNode) && !(isNodeOfType(node, "MemberExpression") && isNodeOfType(node.parent, "AssignmentExpression") && node.parent.left === node && (node.parent.operator === "&&=" || node.parent.operator === "||=" || node.parent.operator === "??="));
|
|
47176
|
-
const functionInvokesTarget = (callerFunction, targetFunction, context, visitedFunctions, canCrossSuspension = false) => {
|
|
47177
|
-
if (visitedFunctions.has(callerFunction)) return false;
|
|
47178
|
-
visitedFunctions.add(callerFunction);
|
|
47179
|
-
let invokesTarget = false;
|
|
47180
|
-
walkAst(callerFunction, (node) => {
|
|
47181
|
-
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47182
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47183
|
-
if (!isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context) || !canCrossSuspension && hasSuspensionBefore(callerFunction, node, context)) return;
|
|
47184
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47185
|
-
if (calledFunction === targetFunction || calledFunction && functionInvokesTarget(calledFunction, targetFunction, context, visitedFunctions, canCrossSuspension)) {
|
|
47186
|
-
invokesTarget = true;
|
|
47187
|
-
return false;
|
|
47188
|
-
}
|
|
47189
|
-
});
|
|
47190
|
-
return invokesTarget;
|
|
47191
|
-
};
|
|
47192
|
-
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47193
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47194
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47195
|
-
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47196
|
-
let isInvokedBefore = false;
|
|
47197
|
-
walkAst(boundaryFunction, (node) => {
|
|
47198
|
-
if (node !== boundaryFunction && isFunctionLike$2(node)) return false;
|
|
47199
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47200
|
-
const callStart = getRangeStart(node);
|
|
47201
|
-
if (callStart === null || callStart >= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, boundaryFunction, context) || hasSuspensionBefore(boundaryFunction, node, context)) return;
|
|
47202
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47203
|
-
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set())) {
|
|
47204
|
-
isInvokedBefore = true;
|
|
47205
|
-
return false;
|
|
47206
|
-
}
|
|
47207
|
-
});
|
|
47208
|
-
return isInvokedBefore;
|
|
47209
|
-
};
|
|
47210
|
-
const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, context) => {
|
|
47211
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47212
|
-
if (boundaryStart === null) return false;
|
|
47213
|
-
let isInvokedAfter = false;
|
|
47214
|
-
walkAst(callerFunction, (node) => {
|
|
47215
|
-
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47216
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47217
|
-
const callStart = getRangeStart(node);
|
|
47218
|
-
if (callStart === null || callStart <= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context)) return;
|
|
47219
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47220
|
-
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set(), true)) {
|
|
47221
|
-
isInvokedAfter = true;
|
|
47222
|
-
return false;
|
|
47223
|
-
}
|
|
47224
|
-
});
|
|
47225
|
-
return isInvokedAfter;
|
|
47226
|
-
};
|
|
47227
|
-
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47228
|
-
const writeStart = getRangeStart(writeNode);
|
|
47229
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47230
|
-
const writeFunction = findEnclosingFunction(writeNode);
|
|
47231
|
-
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47232
|
-
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47233
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47234
|
-
const renderFunction = deferredExecutionFunction ? findEnclosingFunction(deferredExecutionFunction) : null;
|
|
47235
|
-
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47236
|
-
if (!writeFunction) return writeStart < boundaryStart;
|
|
47237
|
-
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
47238
|
-
if (Boolean(renderFunction && (writeFunction === renderFunction || isFunctionAncestor(writeFunction, renderFunction)))) return true;
|
|
47239
|
-
if (deferredExecutionFunction && boundaryFunction !== deferredExecutionFunction) {
|
|
47240
|
-
if (hasSuspensionBefore(boundaryFunction, boundary, context) && isFunctionInvokedBefore(boundaryFunction, writeNode, context)) return true;
|
|
47241
|
-
return isFunctionInvokedAfter(boundaryFunction, writeNode, writeFunction, context);
|
|
47242
|
-
}
|
|
47243
|
-
return writeStart < boundaryStart;
|
|
47244
|
-
}
|
|
47245
|
-
if (renderFunction && isFunctionAncestor(renderFunction, writeFunction) && !hasSuspensionBefore(writeFunction, writeNode, context) && functionInvokesTarget(renderFunction, writeFunction, context, /* @__PURE__ */ new Set())) return true;
|
|
47246
|
-
return !hasSuspensionBefore(writeFunction, writeNode, context) && isFunctionInvokedBefore(writeFunction, boundary, context);
|
|
47247
|
-
};
|
|
47248
|
-
const getStaticStringValue = (node) => {
|
|
47249
|
-
const unwrappedNode = stripParenExpression(node);
|
|
47250
|
-
if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "string") return unwrappedNode.value;
|
|
47251
|
-
if (isNodeOfType(unwrappedNode, "TemplateLiteral") && unwrappedNode.expressions.length === 0) return getStaticTemplateLiteralValue(unwrappedNode);
|
|
47252
|
-
return null;
|
|
47253
|
-
};
|
|
47254
|
-
const isSameRefetchMember = (target, candidate, context) => {
|
|
47255
|
-
const unwrappedTarget = stripParenExpression(target);
|
|
47256
|
-
const unwrappedCandidate = stripParenExpression(candidate);
|
|
47257
|
-
if (!isNodeOfType(unwrappedTarget, "Identifier") || !isNodeOfType(unwrappedCandidate, "MemberExpression") || !isStaticRefetchMember(unwrappedCandidate)) return false;
|
|
47258
|
-
const candidateTarget = stripParenExpression(unwrappedCandidate.object);
|
|
47259
|
-
if (!isNodeOfType(candidateTarget, "Identifier")) return false;
|
|
47260
|
-
const targetSymbol = resolveConstIdentifierAlias(unwrappedTarget, context.scopes);
|
|
47261
|
-
const candidateSymbol = resolveConstIdentifierAlias(candidateTarget, context.scopes);
|
|
47262
|
-
return Boolean(targetSymbol && candidateSymbol?.id === targetSymbol.id);
|
|
47263
|
-
};
|
|
47264
|
-
const getRefetchMutationTarget = (node, context) => {
|
|
47265
|
-
if (isNodeOfType(node, "MemberExpression") && isStaticRefetchMember(node)) {
|
|
47266
|
-
const parent = node.parent;
|
|
47267
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.left === node && isSameRefetchMember(node.object, parent.right, context)) return null;
|
|
47268
|
-
return isNodeOfType(parent, "AssignmentExpression") && parent.left === node || isNodeOfType(parent, "UpdateExpression") && parent.argument === node || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" ? node.object : null;
|
|
47269
|
-
}
|
|
47270
|
-
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
47271
|
-
const callee = stripParenExpression(node.callee);
|
|
47272
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47273
|
-
const receiver = stripParenExpression(callee.object);
|
|
47274
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || context.scopes.symbolFor(receiver)) return null;
|
|
47275
|
-
const methodName = getStaticPropertyKeyName(callee, { allowComputedString: true });
|
|
47276
|
-
const target = node.arguments[0];
|
|
47277
|
-
if (!target || isNodeOfType(target, "SpreadElement")) return null;
|
|
47278
|
-
if (methodName === "defineProperty") {
|
|
47279
|
-
const propertyKey = node.arguments[1];
|
|
47280
|
-
if (!propertyKey || getStaticStringValue(propertyKey) !== "refetch") return null;
|
|
47281
|
-
const descriptor = node.arguments[2];
|
|
47282
|
-
if (isNodeOfType(descriptor, "ObjectExpression")) {
|
|
47283
|
-
const valueProperty = descriptor.properties.find((property) => isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "value");
|
|
47284
|
-
if (isNodeOfType(valueProperty, "Property") && isSameRefetchMember(target, valueProperty.value, context)) return null;
|
|
47285
|
-
}
|
|
47286
|
-
return target;
|
|
47287
|
-
}
|
|
47288
|
-
if (methodName !== "assign") return null;
|
|
47289
|
-
let finalRefetchValue = null;
|
|
47290
|
-
for (const source of node.arguments.slice(1)) {
|
|
47291
|
-
if (!isNodeOfType(source, "ObjectExpression")) continue;
|
|
47292
|
-
for (const property of source.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "refetch") finalRefetchValue = property.value;
|
|
47293
|
-
}
|
|
47294
|
-
if (!finalRefetchValue || isSameRefetchMember(target, finalRefetchValue, context)) return null;
|
|
47295
|
-
return target;
|
|
47296
|
-
};
|
|
47297
|
-
const hasRefetchMemberWriteBefore = (expression, boundary, context, deferredExecutionFunction) => {
|
|
47298
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
47299
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47300
|
-
const resultSymbol = resolveConstIdentifierAlias(unwrappedExpression, context.scopes);
|
|
47301
|
-
if (!resultSymbol) return false;
|
|
47302
|
-
const program = findProgramRoot(expression);
|
|
47303
|
-
if (!program) return true;
|
|
47304
|
-
if (getRangeStart(boundary) === null) return true;
|
|
47305
|
-
let hasWrite = false;
|
|
47306
|
-
walkAst(program, (node) => {
|
|
47307
|
-
const mutationTarget = getRefetchMutationTarget(node, context);
|
|
47308
|
-
if (!mutationTarget) return;
|
|
47309
|
-
if (!isWriteExecutedBefore(node, boundary, context, deferredExecutionFunction)) return;
|
|
47310
|
-
const object = stripParenExpression(mutationTarget);
|
|
47311
|
-
if (!isNodeOfType(object, "Identifier")) return;
|
|
47312
|
-
if (resolveConstIdentifierAlias(object, context.scopes)?.id === resultSymbol.id) {
|
|
47313
|
-
hasWrite = true;
|
|
47314
|
-
return false;
|
|
47315
|
-
}
|
|
47316
|
-
});
|
|
47317
|
-
return hasWrite;
|
|
47318
|
-
};
|
|
47319
|
-
const isTanstackRefetchExpression = (expression, context, deferredExecutionFunction, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47320
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
47321
|
-
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return isStaticRefetchMember(unwrappedExpression) && isTanstackQueryResult(unwrappedExpression.object, context) && !hasRefetchMemberWriteBefore(unwrappedExpression.object, unwrappedExpression, context, deferredExecutionFunction);
|
|
47322
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47323
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
47324
|
-
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read") || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
47325
|
-
visitedSymbolIds.add(symbol.id);
|
|
47326
|
-
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
47327
|
-
if (isNodeOfType(bindingProperty, "Property") && getStaticPropertyKeyName(bindingProperty, { allowComputedString: true }) === "refetch") {
|
|
47328
|
-
const initializer = symbol.declarationNode.init;
|
|
47329
|
-
return Boolean(initializer && isTanstackQueryResult(initializer, context) && !hasRefetchMemberWriteBefore(initializer, symbol.declarationNode, context, null));
|
|
47330
|
-
}
|
|
47331
|
-
return Boolean(symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer && isTanstackRefetchExpression(symbol.initializer, context, null, visitedSymbolIds));
|
|
47332
|
-
};
|
|
47333
|
-
const isTanstackRefetchCall = (callExpression, context, effectCallback) => {
|
|
47334
|
-
return isTanstackRefetchExpression(callExpression.callee, context, effectCallback);
|
|
47335
|
-
};
|
|
47336
46572
|
const queryNoQueryInEffect = defineRule({
|
|
47337
46573
|
id: "query-no-query-in-effect",
|
|
47338
46574
|
title: "Query refetch inside useEffect",
|
|
@@ -47348,7 +46584,8 @@ const queryNoQueryInEffect = defineRule({
|
|
|
47348
46584
|
walkAst(callback, (child) => {
|
|
47349
46585
|
if (child !== callback && isFunctionLike$2(child) && !effectInvokedFunctions.has(child)) return false;
|
|
47350
46586
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
47351
|
-
|
|
46587
|
+
const callee = child.callee;
|
|
46588
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "refetch" || isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "refetch") context.report({
|
|
47352
46589
|
node: child,
|
|
47353
46590
|
message: "refetch() inside useEffect duplicates work React Query already does, causing extra fetches."
|
|
47354
46591
|
});
|
|
@@ -47357,6 +46594,28 @@ const queryNoQueryInEffect = defineRule({
|
|
|
47357
46594
|
});
|
|
47358
46595
|
//#endregion
|
|
47359
46596
|
//#region src/plugin/rules/tanstack-query/query-no-rest-destructuring.ts
|
|
46597
|
+
const resolveTanstackQueryHookName = (callExpression) => {
|
|
46598
|
+
const callee = callExpression.callee;
|
|
46599
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
46600
|
+
const importBinding = getImportBindingForName(callExpression, callee.name);
|
|
46601
|
+
if (importBinding === null) return TANSTACK_QUERY_HOOKS.has(callee.name) ? callee.name : null;
|
|
46602
|
+
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
46603
|
+
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
46604
|
+
}
|
|
46605
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && TANSTACK_QUERY_HOOKS.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) {
|
|
46606
|
+
const namespaceBinding = getImportBindingForName(callExpression, callee.object.name);
|
|
46607
|
+
if (namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source)) return callee.property.name;
|
|
46608
|
+
}
|
|
46609
|
+
return null;
|
|
46610
|
+
};
|
|
46611
|
+
const resolveHookNameFromInitializer = (initializer, scopes) => {
|
|
46612
|
+
if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
|
|
46613
|
+
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
46614
|
+
const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
|
|
46615
|
+
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
46616
|
+
if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
|
|
46617
|
+
return resolveTanstackQueryHookName(resolvedSymbol.initializer);
|
|
46618
|
+
};
|
|
47360
46619
|
const queryNoRestDestructuring = defineRule({
|
|
47361
46620
|
id: "query-no-rest-destructuring",
|
|
47362
46621
|
title: "Rest destructuring on query result",
|
|
@@ -47368,7 +46627,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
47368
46627
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
47369
46628
|
if (!node.init) return;
|
|
47370
46629
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
47371
|
-
const hookName =
|
|
46630
|
+
const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
|
|
47372
46631
|
if (!hookName) return;
|
|
47373
46632
|
context.report({
|
|
47374
46633
|
node: node.id,
|
|
@@ -47612,7 +46871,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
47612
46871
|
});
|
|
47613
46872
|
//#endregion
|
|
47614
46873
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
47615
|
-
const MESSAGE$
|
|
46874
|
+
const MESSAGE$6 = "This JSX crashes because `React` isn't in scope.";
|
|
47616
46875
|
const reactInJsxScope = defineRule({
|
|
47617
46876
|
id: "react-in-jsx-scope",
|
|
47618
46877
|
title: "React not in scope for JSX",
|
|
@@ -47624,190 +46883,19 @@ const reactInJsxScope = defineRule({
|
|
|
47624
46883
|
if (findVariableInitializer(node, "React")) return;
|
|
47625
46884
|
context.report({
|
|
47626
46885
|
node: node.name,
|
|
47627
|
-
message: MESSAGE$
|
|
46886
|
+
message: MESSAGE$6
|
|
47628
46887
|
});
|
|
47629
46888
|
},
|
|
47630
46889
|
JSXFragment(node) {
|
|
47631
46890
|
if (findVariableInitializer(node, "React")) return;
|
|
47632
46891
|
context.report({
|
|
47633
46892
|
node: node.openingFragment,
|
|
47634
|
-
message: MESSAGE$
|
|
46893
|
+
message: MESSAGE$6
|
|
47635
46894
|
});
|
|
47636
46895
|
}
|
|
47637
46896
|
})
|
|
47638
46897
|
});
|
|
47639
46898
|
//#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
|
|
47811
46899
|
//#region src/plugin/utils/collect-react-redux-selector-aliases.ts
|
|
47812
46900
|
const REACT_REDUX_MODULE = "react-redux";
|
|
47813
46901
|
const collectReactReduxSelectorAliases = (programRoot) => {
|
|
@@ -56235,6 +55323,22 @@ const isInsideClassComponent = (node) => {
|
|
|
56235
55323
|
}
|
|
56236
55324
|
return false;
|
|
56237
55325
|
};
|
|
55326
|
+
const hasShortCircuitAncestor = (descendant, ancestor) => {
|
|
55327
|
+
let current = descendant.parent;
|
|
55328
|
+
while (current && current !== ancestor) {
|
|
55329
|
+
if (isNodeOfType(current, "ConditionalExpression") && (isWithinRange(descendant, current.consequent) || isWithinRange(descendant, current.alternate))) return true;
|
|
55330
|
+
if (isNodeOfType(current, "LogicalExpression") && (current.operator === "&&" || current.operator === "||" || current.operator === "??") && isWithinRange(descendant, current.right)) return true;
|
|
55331
|
+
if (isNodeOfType(current, "AssignmentPattern") && isWithinRange(descendant, current.right)) return true;
|
|
55332
|
+
current = current.parent ?? null;
|
|
55333
|
+
}
|
|
55334
|
+
return false;
|
|
55335
|
+
};
|
|
55336
|
+
const isWithinRange = (descendant, sibling) => {
|
|
55337
|
+
const descendantSpan = descendant;
|
|
55338
|
+
const siblingSpan = sibling;
|
|
55339
|
+
if (typeof descendantSpan.start !== "number" || typeof siblingSpan.start !== "number" || typeof siblingSpan.end !== "number") return false;
|
|
55340
|
+
return descendantSpan.start >= siblingSpan.start && (descendantSpan.end ?? siblingSpan.end) <= siblingSpan.end;
|
|
55341
|
+
};
|
|
56238
55342
|
const isInsideTry = (descendant, ancestor) => {
|
|
56239
55343
|
let current = descendant.parent;
|
|
56240
55344
|
while (current && current !== ancestor) {
|
|
@@ -56426,7 +55530,7 @@ const rulesOfHooks = defineRule({
|
|
|
56426
55530
|
});
|
|
56427
55531
|
return;
|
|
56428
55532
|
}
|
|
56429
|
-
if (
|
|
55533
|
+
if (hasShortCircuitAncestor(node, enclosing.node)) {
|
|
56430
55534
|
context.report({
|
|
56431
55535
|
node: node.callee,
|
|
56432
55536
|
message: buildConditionalMessage(hookName)
|
|
@@ -63041,17 +62145,6 @@ const reactDoctorRules = [
|
|
|
63041
62145
|
requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
|
|
63042
62146
|
}
|
|
63043
62147
|
},
|
|
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
|
-
},
|
|
63055
62148
|
{
|
|
63056
62149
|
key: "react-doctor/redux-useselector-inline-derivation",
|
|
63057
62150
|
id: "redux-useselector-inline-derivation",
|