oxlint-plugin-react-doctor 0.7.7-dev.43327b5 → 0.7.7-dev.4fce1c1
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 +437 -1140
- 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",
|
|
@@ -8873,7 +8843,6 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
|
|
|
8873
8843
|
const LABEL_ELEMENT = "label";
|
|
8874
8844
|
const LABEL_COMPONENT_NAME = "Label";
|
|
8875
8845
|
const POLYMORPHIC_COMPONENT_PROP = "component";
|
|
8876
|
-
const TITLE_ATTRIBUTE = "title";
|
|
8877
8846
|
const WRAPPER_LABEL_PROP = "label";
|
|
8878
8847
|
const SELECT_ELEMENT = "select";
|
|
8879
8848
|
const DEFAULT_DEPTH = 5;
|
|
@@ -8919,96 +8888,6 @@ const hasNonEmptyPropValue = (attribute) => {
|
|
|
8919
8888
|
}
|
|
8920
8889
|
return true;
|
|
8921
8890
|
};
|
|
8922
|
-
const getLastJsxPropIgnoreCase = (attributes, targetProp) => {
|
|
8923
|
-
const targetPropLower = targetProp.toLowerCase();
|
|
8924
|
-
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
8925
|
-
const attribute = attributes[attributeIndex];
|
|
8926
|
-
if (!attribute || !isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
8927
|
-
if (getJsxAttributeName(attribute.name)?.toLowerCase() === targetPropLower) return attribute;
|
|
8928
|
-
}
|
|
8929
|
-
};
|
|
8930
|
-
const getStaticNativeTitleArrayValue = (expression, scopes) => {
|
|
8931
|
-
const elementValues = [];
|
|
8932
|
-
for (const rawElement of expression.elements) {
|
|
8933
|
-
if (rawElement === null) {
|
|
8934
|
-
elementValues.push("");
|
|
8935
|
-
continue;
|
|
8936
|
-
}
|
|
8937
|
-
if (isNodeOfType(rawElement, "SpreadElement")) return null;
|
|
8938
|
-
const element = stripParenExpression(rawElement);
|
|
8939
|
-
if (isNodeOfType(element, "Literal")) {
|
|
8940
|
-
elementValues.push(element.value === null ? "" : String(element.value));
|
|
8941
|
-
continue;
|
|
8942
|
-
}
|
|
8943
|
-
if (isNodeOfType(element, "TemplateLiteral")) {
|
|
8944
|
-
const staticValue = getStaticTemplateLiteralValue(element);
|
|
8945
|
-
if (staticValue === null) return null;
|
|
8946
|
-
elementValues.push(staticValue);
|
|
8947
|
-
continue;
|
|
8948
|
-
}
|
|
8949
|
-
if (isNodeOfType(element, "Identifier") && element.name === "undefined" && scopes.isGlobalReference(element) || isNodeOfType(element, "UnaryExpression") && element.operator === "void") {
|
|
8950
|
-
elementValues.push("");
|
|
8951
|
-
continue;
|
|
8952
|
-
}
|
|
8953
|
-
if (isNodeOfType(element, "ArrayExpression")) {
|
|
8954
|
-
const nestedValue = getStaticNativeTitleArrayValue(element, scopes);
|
|
8955
|
-
if (nestedValue === null) return null;
|
|
8956
|
-
elementValues.push(nestedValue);
|
|
8957
|
-
continue;
|
|
8958
|
-
}
|
|
8959
|
-
return null;
|
|
8960
|
-
}
|
|
8961
|
-
return elementValues.join(",");
|
|
8962
|
-
};
|
|
8963
|
-
const isGlobalSymbolExpression = (expression, scopes) => {
|
|
8964
|
-
if (isNodeOfType(expression, "Identifier")) return expression.name === "Symbol" && scopes.isGlobalReference(expression);
|
|
8965
|
-
if (!isNodeOfType(expression, "MemberExpression")) return false;
|
|
8966
|
-
const object = stripParenExpression(expression.object);
|
|
8967
|
-
return isNodeOfType(object, "Identifier") && object.name === "Symbol" && scopes.isGlobalReference(object);
|
|
8968
|
-
};
|
|
8969
|
-
const hasNonEmptyNativeTitleExpression = (rawExpression, scopes) => {
|
|
8970
|
-
const expression = stripParenExpression(rawExpression);
|
|
8971
|
-
if (isNodeOfType(expression, "Literal")) {
|
|
8972
|
-
if (typeof expression.value === "string") return expression.value.trim().length > 0;
|
|
8973
|
-
return expression.value !== null && typeof expression.value !== "boolean";
|
|
8974
|
-
}
|
|
8975
|
-
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
8976
|
-
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
8977
|
-
return staticValue === null || staticValue.trim().length > 0;
|
|
8978
|
-
}
|
|
8979
|
-
if (isNodeOfType(expression, "Identifier")) return expression.name !== "undefined" || !scopes.isGlobalReference(expression);
|
|
8980
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return false;
|
|
8981
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") return false;
|
|
8982
|
-
if (isNodeOfType(expression, "ArrowFunctionExpression")) return false;
|
|
8983
|
-
if (isNodeOfType(expression, "FunctionExpression")) return false;
|
|
8984
|
-
if (isNodeOfType(expression, "ClassExpression")) return false;
|
|
8985
|
-
if (isNodeOfType(expression, "ArrayExpression")) {
|
|
8986
|
-
const staticValue = getStaticNativeTitleArrayValue(expression, scopes);
|
|
8987
|
-
return staticValue === null || staticValue.trim().length > 0;
|
|
8988
|
-
}
|
|
8989
|
-
if (isGlobalSymbolExpression(expression, scopes)) return false;
|
|
8990
|
-
if (isNodeOfType(expression, "CallExpression") && isGlobalSymbolExpression(expression.callee, scopes)) return false;
|
|
8991
|
-
if (isNodeOfType(expression, "ConditionalExpression")) return hasNonEmptyNativeTitleExpression(expression.consequent, scopes) && hasNonEmptyNativeTitleExpression(expression.alternate, scopes);
|
|
8992
|
-
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
8993
|
-
const finalExpression = expression.expressions.at(-1);
|
|
8994
|
-
return finalExpression ? hasNonEmptyNativeTitleExpression(finalExpression, scopes) : false;
|
|
8995
|
-
}
|
|
8996
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
8997
|
-
const leftExpression = stripParenExpression(expression.left);
|
|
8998
|
-
if (!isNodeOfType(leftExpression, "Literal")) return expression.operator !== "&&";
|
|
8999
|
-
if (expression.operator === "??") return hasNonEmptyNativeTitleExpression(leftExpression.value === null ? expression.right : leftExpression, scopes);
|
|
9000
|
-
const leftValueIsTruthy = Boolean(leftExpression.value);
|
|
9001
|
-
if (expression.operator === "&&") return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? expression.right : leftExpression, scopes);
|
|
9002
|
-
return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? leftExpression : expression.right, scopes);
|
|
9003
|
-
}
|
|
9004
|
-
return true;
|
|
9005
|
-
};
|
|
9006
|
-
const hasNonEmptyNativeTitle = (attribute, scopes) => {
|
|
9007
|
-
if (!attribute?.value) return false;
|
|
9008
|
-
if (isNodeOfType(attribute.value, "Literal")) return typeof attribute.value.value === "string" && attribute.value.value.trim().length > 0;
|
|
9009
|
-
if (isNodeOfType(attribute.value, "JSXExpressionContainer")) return hasNonEmptyNativeTitleExpression(attribute.value.expression, scopes);
|
|
9010
|
-
return true;
|
|
9011
|
-
};
|
|
9012
8891
|
const toAttributeMatchKey = (kind, value) => {
|
|
9013
8892
|
const trimmedValue = value.trim();
|
|
9014
8893
|
return trimmedValue.length > 0 ? `${kind}:${trimmedValue}` : null;
|
|
@@ -9265,7 +9144,6 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9265
9144
|
if (typeValue === "submit" || typeValue === "reset") return;
|
|
9266
9145
|
if (typeValue === "button" && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "value"))) return;
|
|
9267
9146
|
}
|
|
9268
|
-
if (isDomElement && hasNonEmptyNativeTitle(getLastJsxPropIgnoreCase(opening.attributes, TITLE_ATTRIBUTE), context.scopes)) return;
|
|
9269
9147
|
if (supportsPlaceholderNameFallback(tagName, opening) && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "placeholder"))) return;
|
|
9270
9148
|
if (hasLabellingProp(opening.attributes, settings.labelAttributes)) return;
|
|
9271
9149
|
if (isInsideJsxAttribute(node)) return;
|
|
@@ -9285,7 +9163,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9285
9163
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
9286
9164
|
context.report({
|
|
9287
9165
|
node: candidate.opening,
|
|
9288
|
-
message: MESSAGE$
|
|
9166
|
+
message: MESSAGE$56
|
|
9289
9167
|
});
|
|
9290
9168
|
}
|
|
9291
9169
|
}
|
|
@@ -10046,7 +9924,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
10046
9924
|
});
|
|
10047
9925
|
//#endregion
|
|
10048
9926
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
10049
|
-
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.";
|
|
10050
9928
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
10051
9929
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
10052
9930
|
"aria-label",
|
|
@@ -10071,7 +9949,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
10071
9949
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
10072
9950
|
context.report({
|
|
10073
9951
|
node: node.name,
|
|
10074
|
-
message: MESSAGE$
|
|
9952
|
+
message: MESSAGE$55
|
|
10075
9953
|
});
|
|
10076
9954
|
} };
|
|
10077
9955
|
}
|
|
@@ -10111,7 +9989,7 @@ const isEs6Component = (node) => {
|
|
|
10111
9989
|
};
|
|
10112
9990
|
//#endregion
|
|
10113
9991
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
10114
|
-
const MESSAGE$
|
|
9992
|
+
const MESSAGE$54 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
10115
9993
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
10116
9994
|
"observer",
|
|
10117
9995
|
"lazy",
|
|
@@ -10304,7 +10182,7 @@ const displayName = defineRule({
|
|
|
10304
10182
|
const reportAt = (node) => {
|
|
10305
10183
|
context.report({
|
|
10306
10184
|
node,
|
|
10307
|
-
message: MESSAGE$
|
|
10185
|
+
message: MESSAGE$54
|
|
10308
10186
|
});
|
|
10309
10187
|
};
|
|
10310
10188
|
return {
|
|
@@ -11125,14 +11003,6 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
|
|
|
11125
11003
|
"catch",
|
|
11126
11004
|
"finally"
|
|
11127
11005
|
]);
|
|
11128
|
-
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11129
|
-
const getPromiseChainCallForCallback = (candidate) => {
|
|
11130
|
-
let callbackContainer = candidate.parent;
|
|
11131
|
-
while (callbackContainer && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callbackContainer.type)) callbackContainer = callbackContainer.parent;
|
|
11132
|
-
if (!isNodeOfType(callbackContainer, "CallExpression")) return null;
|
|
11133
|
-
if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
|
|
11134
|
-
return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
|
|
11135
|
-
};
|
|
11136
11006
|
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
11137
11007
|
const invokedFunctions = new Set([effectCallback]);
|
|
11138
11008
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
@@ -11144,6 +11014,7 @@ const collectEffectInvokedFunctions = (effectCallback) => {
|
|
|
11144
11014
|
invokedFunctions.add(strippedCandidate);
|
|
11145
11015
|
pendingFunctions.push(strippedCandidate);
|
|
11146
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");
|
|
11147
11018
|
while (pendingFunctions.length > 0) {
|
|
11148
11019
|
const currentFunction = pendingFunctions.pop();
|
|
11149
11020
|
if (!currentFunction) break;
|
|
@@ -11351,50 +11222,6 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
|
|
|
11351
11222
|
return true;
|
|
11352
11223
|
};
|
|
11353
11224
|
//#endregion
|
|
11354
|
-
//#region src/plugin/utils/is-node-reachable-within-function.ts
|
|
11355
|
-
const isInsideStaticallyUnreachableBranch = (node) => {
|
|
11356
|
-
let child = node;
|
|
11357
|
-
let parent = node.parent;
|
|
11358
|
-
while (parent) {
|
|
11359
|
-
if (isNodeOfType(parent, "IfStatement") && isNodeOfType(parent.test, "Literal")) {
|
|
11360
|
-
if (parent.test.value === false && parent.consequent === child) return true;
|
|
11361
|
-
if (parent.test.value === true && parent.alternate === child) return true;
|
|
11362
|
-
}
|
|
11363
|
-
if (isNodeOfType(parent, "ConditionalExpression") && isNodeOfType(parent.test, "Literal")) {
|
|
11364
|
-
if (parent.test.value === false && parent.consequent === child) return true;
|
|
11365
|
-
if (parent.test.value === true && parent.alternate === child) return true;
|
|
11366
|
-
}
|
|
11367
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) {
|
|
11368
|
-
if (isNodeOfType(parent.left, "Literal") && (parent.operator === "&&" && !parent.left.value || parent.operator === "||" && Boolean(parent.left.value))) return true;
|
|
11369
|
-
}
|
|
11370
|
-
child = parent;
|
|
11371
|
-
parent = parent.parent;
|
|
11372
|
-
}
|
|
11373
|
-
return false;
|
|
11374
|
-
};
|
|
11375
|
-
const isNodeReachableWithinFunction = (node, context) => {
|
|
11376
|
-
if (isInsideStaticallyUnreachableBranch(node)) return false;
|
|
11377
|
-
const owner = context.cfg.enclosingFunction(node);
|
|
11378
|
-
if (!owner) return true;
|
|
11379
|
-
const functionCfg = context.cfg.cfgFor(owner);
|
|
11380
|
-
if (!functionCfg) return true;
|
|
11381
|
-
const targetBlock = functionCfg.blockOf(node);
|
|
11382
|
-
if (!targetBlock) return true;
|
|
11383
|
-
const visitedBlocks = new Set([functionCfg.entry]);
|
|
11384
|
-
const pendingBlocks = [functionCfg.entry];
|
|
11385
|
-
while (pendingBlocks.length > 0) {
|
|
11386
|
-
const currentBlock = pendingBlocks.pop();
|
|
11387
|
-
if (!currentBlock) break;
|
|
11388
|
-
if (currentBlock === targetBlock) return true;
|
|
11389
|
-
for (const edge of currentBlock.successors) {
|
|
11390
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
11391
|
-
visitedBlocks.add(edge.to);
|
|
11392
|
-
pendingBlocks.push(edge.to);
|
|
11393
|
-
}
|
|
11394
|
-
}
|
|
11395
|
-
return false;
|
|
11396
|
-
};
|
|
11397
|
-
//#endregion
|
|
11398
11225
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
11399
11226
|
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
11400
11227
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
@@ -11523,6 +11350,27 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
11523
11350
|
});
|
|
11524
11351
|
return usages.filter((usage) => isNodeReachableWithinFunction(usage.node, context));
|
|
11525
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
|
+
};
|
|
11526
11374
|
const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
|
|
11527
11375
|
let pathAnchor = usageNode;
|
|
11528
11376
|
let pathOwner = findEnclosingFunction(pathAnchor);
|
|
@@ -11996,94 +11844,6 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
11996
11844
|
return didFindUnmountCleanup;
|
|
11997
11845
|
};
|
|
11998
11846
|
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
11999
|
-
const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
|
|
12000
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
12001
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
|
|
12002
|
-
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
12003
|
-
if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
|
|
12004
|
-
return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
|
|
12005
|
-
}
|
|
12006
|
-
if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
|
|
12007
|
-
"===",
|
|
12008
|
-
"==",
|
|
12009
|
-
"!==",
|
|
12010
|
-
"!="
|
|
12011
|
-
].includes(unwrappedExpression.operator)) {
|
|
12012
|
-
const leftValue = readStaticBoolean(unwrappedExpression.left);
|
|
12013
|
-
const rightValue = readStaticBoolean(unwrappedExpression.right);
|
|
12014
|
-
const booleanValue = leftValue ?? rightValue;
|
|
12015
|
-
const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
|
|
12016
|
-
if (booleanValue === null || comparedKey === null) return [];
|
|
12017
|
-
return [{
|
|
12018
|
-
key: comparedKey,
|
|
12019
|
-
value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
|
|
12020
|
-
}];
|
|
12021
|
-
}
|
|
12022
|
-
const expressionKey = resolveExpressionKey(unwrappedExpression, context);
|
|
12023
|
-
return expressionKey === null ? [] : [{
|
|
12024
|
-
key: expressionKey,
|
|
12025
|
-
value: blockedExpressionValue
|
|
12026
|
-
}];
|
|
12027
|
-
};
|
|
12028
|
-
const isDirectEarlyReturnConsequent = (ifStatement) => {
|
|
12029
|
-
if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
|
|
12030
|
-
if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
|
|
12031
|
-
return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
|
|
12032
|
-
};
|
|
12033
|
-
const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
|
|
12034
|
-
if (!isFunctionLike$2(callback) || callback.async) return [];
|
|
12035
|
-
const guardStates = [];
|
|
12036
|
-
walkAst(callback.body, (child) => {
|
|
12037
|
-
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
12038
|
-
if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
|
|
12039
|
-
});
|
|
12040
|
-
let descendant = usageNode;
|
|
12041
|
-
let ancestor = descendant.parent;
|
|
12042
|
-
while (ancestor && ancestor !== callback) {
|
|
12043
|
-
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
|
|
12044
|
-
descendant = ancestor;
|
|
12045
|
-
ancestor = ancestor.parent;
|
|
12046
|
-
}
|
|
12047
|
-
return guardStates;
|
|
12048
|
-
};
|
|
12049
|
-
const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
|
|
12050
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
12051
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
12052
|
-
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction) || cleanupFunction.async) return false;
|
|
12053
|
-
let didInvalidateGuard = false;
|
|
12054
|
-
walkAst(cleanupFunction.body, (child) => {
|
|
12055
|
-
if (didInvalidateGuard) return false;
|
|
12056
|
-
if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
|
|
12057
|
-
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveExpressionKey(child.left, context) === guardState.key && readStaticBoolean(child.right) === guardState.value && context.cfg.isUnconditionalFromEntry(child)) {
|
|
12058
|
-
didInvalidateGuard = true;
|
|
12059
|
-
return false;
|
|
12060
|
-
}
|
|
12061
|
-
});
|
|
12062
|
-
return didInvalidateGuard;
|
|
12063
|
-
};
|
|
12064
|
-
const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, context) => {
|
|
12065
|
-
const usageStart = getRangeStart(usageNode);
|
|
12066
|
-
if (!isFunctionLike$2(callback) || usageStart === null) return true;
|
|
12067
|
-
let didWriteGuard = false;
|
|
12068
|
-
walkAst(callback.body, (child) => {
|
|
12069
|
-
if (didWriteGuard) return false;
|
|
12070
|
-
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
12071
|
-
const childStart = getRangeStart(child);
|
|
12072
|
-
if (childStart === null || childStart >= usageStart) return;
|
|
12073
|
-
const writtenExpression = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : null;
|
|
12074
|
-
if (writtenExpression && resolveExpressionKey(writtenExpression, context) === guardState.key) {
|
|
12075
|
-
didWriteGuard = true;
|
|
12076
|
-
return false;
|
|
12077
|
-
}
|
|
12078
|
-
});
|
|
12079
|
-
return didWriteGuard;
|
|
12080
|
-
};
|
|
12081
|
-
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
12082
|
-
const usageFunction = findEnclosingFunction(usage.node);
|
|
12083
|
-
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
12084
|
-
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
12085
|
-
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
12086
|
-
};
|
|
12087
11847
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
12088
11848
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
12089
11849
|
if (callback.async) return false;
|
|
@@ -12123,7 +11883,6 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
12123
11883
|
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
|
|
12124
11884
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
12125
11885
|
});
|
|
12126
|
-
if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
|
|
12127
11886
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
12128
11887
|
};
|
|
12129
11888
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
@@ -14867,7 +14626,7 @@ const forbidElements = defineRule({
|
|
|
14867
14626
|
});
|
|
14868
14627
|
//#endregion
|
|
14869
14628
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
14870
|
-
const MESSAGE$
|
|
14629
|
+
const MESSAGE$53 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
14871
14630
|
const forwardRefUsesRef = defineRule({
|
|
14872
14631
|
id: "forward-ref-uses-ref",
|
|
14873
14632
|
title: "forwardRef without ref parameter",
|
|
@@ -14890,7 +14649,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
14890
14649
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
14891
14650
|
context.report({
|
|
14892
14651
|
node: inner,
|
|
14893
|
-
message: MESSAGE$
|
|
14652
|
+
message: MESSAGE$53
|
|
14894
14653
|
});
|
|
14895
14654
|
} })
|
|
14896
14655
|
});
|
|
@@ -14931,7 +14690,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
14931
14690
|
});
|
|
14932
14691
|
//#endregion
|
|
14933
14692
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
14934
|
-
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`.";
|
|
14935
14694
|
const DEFAULT_HEADING_TAGS = [
|
|
14936
14695
|
"h1",
|
|
14937
14696
|
"h2",
|
|
@@ -14965,7 +14724,7 @@ const headingHasContent = defineRule({
|
|
|
14965
14724
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
14966
14725
|
context.report({
|
|
14967
14726
|
node,
|
|
14968
|
-
message: MESSAGE$
|
|
14727
|
+
message: MESSAGE$52
|
|
14969
14728
|
});
|
|
14970
14729
|
} };
|
|
14971
14730
|
}
|
|
@@ -15129,7 +14888,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
15129
14888
|
});
|
|
15130
14889
|
//#endregion
|
|
15131
14890
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
15132
|
-
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`.";
|
|
15133
14892
|
const resolveSettings$39 = (settings) => {
|
|
15134
14893
|
const reactDoctor = settings?.["react-doctor"];
|
|
15135
14894
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -15176,13 +14935,13 @@ const htmlHasLang = defineRule({
|
|
|
15176
14935
|
if (!lang) {
|
|
15177
14936
|
context.report({
|
|
15178
14937
|
node: node.name,
|
|
15179
|
-
message: MESSAGE$
|
|
14938
|
+
message: MESSAGE$51
|
|
15180
14939
|
});
|
|
15181
14940
|
return;
|
|
15182
14941
|
}
|
|
15183
14942
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
15184
14943
|
node: lang,
|
|
15185
|
-
message: MESSAGE$
|
|
14944
|
+
message: MESSAGE$51
|
|
15186
14945
|
});
|
|
15187
14946
|
} };
|
|
15188
14947
|
}
|
|
@@ -15422,7 +15181,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
15422
15181
|
});
|
|
15423
15182
|
//#endregion
|
|
15424
15183
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
15425
|
-
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.";
|
|
15426
15185
|
const evaluateTitleValue = (value) => {
|
|
15427
15186
|
if (!value) return "missing";
|
|
15428
15187
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -15462,14 +15221,14 @@ const iframeHasTitle = defineRule({
|
|
|
15462
15221
|
if (!titleAttr) {
|
|
15463
15222
|
if (hasSpread || tag === "iframe") context.report({
|
|
15464
15223
|
node: node.name,
|
|
15465
|
-
message: MESSAGE$
|
|
15224
|
+
message: MESSAGE$50
|
|
15466
15225
|
});
|
|
15467
15226
|
return;
|
|
15468
15227
|
}
|
|
15469
15228
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
15470
15229
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
15471
15230
|
node: titleAttr,
|
|
15472
|
-
message: MESSAGE$
|
|
15231
|
+
message: MESSAGE$50
|
|
15473
15232
|
});
|
|
15474
15233
|
} })
|
|
15475
15234
|
});
|
|
@@ -15594,7 +15353,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
15594
15353
|
});
|
|
15595
15354
|
//#endregion
|
|
15596
15355
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
15597
|
-
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.";
|
|
15598
15357
|
const DEFAULT_COMPONENTS = ["img"];
|
|
15599
15358
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
15600
15359
|
"image",
|
|
@@ -15659,7 +15418,7 @@ const imgRedundantAlt = defineRule({
|
|
|
15659
15418
|
if (!altAttribute) return;
|
|
15660
15419
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
15661
15420
|
node: altAttribute,
|
|
15662
|
-
message: MESSAGE$
|
|
15421
|
+
message: MESSAGE$49
|
|
15663
15422
|
});
|
|
15664
15423
|
} };
|
|
15665
15424
|
}
|
|
@@ -17442,7 +17201,7 @@ const GLOBAL_BUILTIN_NAMES = new Set([
|
|
|
17442
17201
|
]);
|
|
17443
17202
|
const STRING_PROTOTYPE_PATH = "String.prototype";
|
|
17444
17203
|
const REGEXP_PROTOTYPE_PATH = "RegExp.prototype";
|
|
17445
|
-
const getStaticStringValue
|
|
17204
|
+
const getStaticStringValue = (argument) => {
|
|
17446
17205
|
if (!argument) return null;
|
|
17447
17206
|
const unwrappedArgument = stripParenExpression(argument);
|
|
17448
17207
|
if (isNodeOfType(unwrappedArgument, "Literal") && typeof unwrappedArgument.value === "string") return unwrappedArgument.value;
|
|
@@ -17450,7 +17209,7 @@ const getStaticStringValue$1 = (argument) => {
|
|
|
17450
17209
|
return null;
|
|
17451
17210
|
};
|
|
17452
17211
|
const getEffectiveRegExpFlags = (patternArgument, flagsArgument) => {
|
|
17453
|
-
if (flagsArgument) return getStaticStringValue
|
|
17212
|
+
if (flagsArgument) return getStaticStringValue(flagsArgument);
|
|
17454
17213
|
if (!patternArgument) return "";
|
|
17455
17214
|
const unwrappedPattern = stripParenExpression(patternArgument);
|
|
17456
17215
|
if (isNodeOfType(unwrappedPattern, "Literal") && unwrappedPattern.value instanceof RegExp) return unwrappedPattern.value.flags;
|
|
@@ -17600,7 +17359,7 @@ const getCallRegExpHazard = (node, context, symbolCache) => {
|
|
|
17600
17359
|
const mutationHazard = targetPath === "global" ? "globalRegExpReplaced" : "replaceAllIntegrityLost";
|
|
17601
17360
|
const guardedPropertyName = targetPath === "global" ? "RegExp" : "replaceAll";
|
|
17602
17361
|
if (isSinglePropertyMutation) {
|
|
17603
|
-
const propertyName = getStaticStringValue
|
|
17362
|
+
const propertyName = getStaticStringValue(node.arguments?.[1]);
|
|
17604
17363
|
return propertyName === null || propertyName === guardedPropertyName ? mutationHazard : "none";
|
|
17605
17364
|
}
|
|
17606
17365
|
if (!isPropertyCollectionMutation) return "none";
|
|
@@ -17629,7 +17388,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
17629
17388
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
17630
17389
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
17631
17390
|
};
|
|
17632
|
-
const MESSAGE$
|
|
17391
|
+
const MESSAGE$48 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
17633
17392
|
const jsHoistRegexp = defineRule({
|
|
17634
17393
|
id: "js-hoist-regexp",
|
|
17635
17394
|
title: "RegExp built inside a loop",
|
|
@@ -17646,7 +17405,7 @@ const jsHoistRegexp = defineRule({
|
|
|
17646
17405
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
17647
17406
|
context.report({
|
|
17648
17407
|
node,
|
|
17649
|
-
message: MESSAGE$
|
|
17408
|
+
message: MESSAGE$48
|
|
17650
17409
|
});
|
|
17651
17410
|
};
|
|
17652
17411
|
return createLoopAwareVisitors({
|
|
@@ -20266,7 +20025,7 @@ const jsxMaxDepth = defineRule({
|
|
|
20266
20025
|
});
|
|
20267
20026
|
//#endregion
|
|
20268
20027
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
20269
|
-
const MESSAGE$
|
|
20028
|
+
const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
20270
20029
|
const LITERAL_TEXT_TAGS = new Set([
|
|
20271
20030
|
"code",
|
|
20272
20031
|
"pre",
|
|
@@ -20336,7 +20095,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
20336
20095
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
20337
20096
|
context.report({
|
|
20338
20097
|
node,
|
|
20339
|
-
message: MESSAGE$
|
|
20098
|
+
message: MESSAGE$47
|
|
20340
20099
|
});
|
|
20341
20100
|
} })
|
|
20342
20101
|
});
|
|
@@ -20367,7 +20126,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
20367
20126
|
};
|
|
20368
20127
|
//#endregion
|
|
20369
20128
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
20370
|
-
const MESSAGE$
|
|
20129
|
+
const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
20371
20130
|
const CONTEXT_MODULES$1 = [
|
|
20372
20131
|
"react",
|
|
20373
20132
|
"use-context-selector",
|
|
@@ -20465,7 +20224,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
20465
20224
|
if (!isConstructedValue(innerExpression)) continue;
|
|
20466
20225
|
context.report({
|
|
20467
20226
|
node: attribute,
|
|
20468
|
-
message: MESSAGE$
|
|
20227
|
+
message: MESSAGE$46
|
|
20469
20228
|
});
|
|
20470
20229
|
}
|
|
20471
20230
|
}
|
|
@@ -21320,7 +21079,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
21320
21079
|
];
|
|
21321
21080
|
//#endregion
|
|
21322
21081
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
21323
|
-
const MESSAGE$
|
|
21082
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
21324
21083
|
const isDataArrayPropName = (propName) => {
|
|
21325
21084
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
21326
21085
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -21407,7 +21166,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
21407
21166
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
21408
21167
|
context.report({
|
|
21409
21168
|
node,
|
|
21410
|
-
message: MESSAGE$
|
|
21169
|
+
message: MESSAGE$45
|
|
21411
21170
|
});
|
|
21412
21171
|
}
|
|
21413
21172
|
};
|
|
@@ -21665,7 +21424,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
21665
21424
|
]);
|
|
21666
21425
|
//#endregion
|
|
21667
21426
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
21668
|
-
const MESSAGE$
|
|
21427
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
21669
21428
|
const isAccessorPredicateName = (propName) => {
|
|
21670
21429
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
21671
21430
|
if (propName.length <= prefix.length) continue;
|
|
@@ -21871,7 +21630,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
21871
21630
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
21872
21631
|
context.report({
|
|
21873
21632
|
node,
|
|
21874
|
-
message: MESSAGE$
|
|
21633
|
+
message: MESSAGE$44
|
|
21875
21634
|
});
|
|
21876
21635
|
}
|
|
21877
21636
|
};
|
|
@@ -22091,7 +21850,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
22091
21850
|
];
|
|
22092
21851
|
//#endregion
|
|
22093
21852
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
22094
|
-
const MESSAGE$
|
|
21853
|
+
const MESSAGE$43 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
22095
21854
|
const isConfigObjectPropName = (propName) => {
|
|
22096
21855
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
22097
21856
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -22180,7 +21939,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22180
21939
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
22181
21940
|
context.report({
|
|
22182
21941
|
node,
|
|
22183
|
-
message: MESSAGE$
|
|
21942
|
+
message: MESSAGE$43
|
|
22184
21943
|
});
|
|
22185
21944
|
}
|
|
22186
21945
|
};
|
|
@@ -22188,7 +21947,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22188
21947
|
});
|
|
22189
21948
|
//#endregion
|
|
22190
21949
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
22191
|
-
const MESSAGE$
|
|
21950
|
+
const MESSAGE$42 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
22192
21951
|
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;
|
|
22193
21952
|
const resolveSettings$29 = (settings) => {
|
|
22194
21953
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -22226,7 +21985,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
22226
21985
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
22227
21986
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
22228
21987
|
node: attribute,
|
|
22229
|
-
message: MESSAGE$
|
|
21988
|
+
message: MESSAGE$42
|
|
22230
21989
|
});
|
|
22231
21990
|
}
|
|
22232
21991
|
} };
|
|
@@ -22846,7 +22605,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
22846
22605
|
});
|
|
22847
22606
|
//#endregion
|
|
22848
22607
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
22849
|
-
const MESSAGE$
|
|
22608
|
+
const MESSAGE$41 = "You can't tell what props reach this element when you spread them.";
|
|
22850
22609
|
const resolveSettings$25 = (settings) => {
|
|
22851
22610
|
const reactDoctor = settings?.["react-doctor"];
|
|
22852
22611
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -22889,7 +22648,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
22889
22648
|
}
|
|
22890
22649
|
context.report({
|
|
22891
22650
|
node: attribute,
|
|
22892
|
-
message: MESSAGE$
|
|
22651
|
+
message: MESSAGE$41
|
|
22893
22652
|
});
|
|
22894
22653
|
didReportInFile = true;
|
|
22895
22654
|
return;
|
|
@@ -23165,7 +22924,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
23165
22924
|
});
|
|
23166
22925
|
//#endregion
|
|
23167
22926
|
//#region src/plugin/rules/a11y/lang.ts
|
|
23168
|
-
const MESSAGE$
|
|
22927
|
+
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`.";
|
|
23169
22928
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
23170
22929
|
"aa",
|
|
23171
22930
|
"ab",
|
|
@@ -23645,7 +23404,7 @@ const lang = defineRule({
|
|
|
23645
23404
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
23646
23405
|
context.report({
|
|
23647
23406
|
node: langAttr,
|
|
23648
|
-
message: MESSAGE$
|
|
23407
|
+
message: MESSAGE$40
|
|
23649
23408
|
});
|
|
23650
23409
|
return;
|
|
23651
23410
|
}
|
|
@@ -23654,7 +23413,7 @@ const lang = defineRule({
|
|
|
23654
23413
|
if (value === null) return;
|
|
23655
23414
|
if (!isValidLangTag(value)) context.report({
|
|
23656
23415
|
node: langAttr,
|
|
23657
|
-
message: MESSAGE$
|
|
23416
|
+
message: MESSAGE$40
|
|
23658
23417
|
});
|
|
23659
23418
|
} })
|
|
23660
23419
|
});
|
|
@@ -23705,7 +23464,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
23705
23464
|
});
|
|
23706
23465
|
//#endregion
|
|
23707
23466
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
23708
|
-
const MESSAGE$
|
|
23467
|
+
const MESSAGE$39 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
23709
23468
|
const DEFAULT_AUDIO = ["audio"];
|
|
23710
23469
|
const DEFAULT_VIDEO = ["video"];
|
|
23711
23470
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -23794,7 +23553,7 @@ const mediaHasCaption = defineRule({
|
|
|
23794
23553
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
23795
23554
|
context.report({
|
|
23796
23555
|
node: node.name,
|
|
23797
|
-
message: MESSAGE$
|
|
23556
|
+
message: MESSAGE$39
|
|
23798
23557
|
});
|
|
23799
23558
|
return;
|
|
23800
23559
|
}
|
|
@@ -23813,7 +23572,7 @@ const mediaHasCaption = defineRule({
|
|
|
23813
23572
|
return kindValue.value.toLowerCase() === "captions";
|
|
23814
23573
|
})) context.report({
|
|
23815
23574
|
node: node.name,
|
|
23816
|
-
message: MESSAGE$
|
|
23575
|
+
message: MESSAGE$39
|
|
23817
23576
|
});
|
|
23818
23577
|
} };
|
|
23819
23578
|
}
|
|
@@ -25356,7 +25115,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
25356
25115
|
});
|
|
25357
25116
|
//#endregion
|
|
25358
25117
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
25359
|
-
const MESSAGE$
|
|
25118
|
+
const MESSAGE$38 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
25360
25119
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
25361
25120
|
const noAccessKey = defineRule({
|
|
25362
25121
|
id: "no-access-key",
|
|
@@ -25375,7 +25134,7 @@ const noAccessKey = defineRule({
|
|
|
25375
25134
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
25376
25135
|
context.report({
|
|
25377
25136
|
node: accessKey,
|
|
25378
|
-
message: MESSAGE$
|
|
25137
|
+
message: MESSAGE$38
|
|
25379
25138
|
});
|
|
25380
25139
|
return;
|
|
25381
25140
|
}
|
|
@@ -25385,7 +25144,7 @@ const noAccessKey = defineRule({
|
|
|
25385
25144
|
if (isUndefinedIdentifier(expression)) return;
|
|
25386
25145
|
context.report({
|
|
25387
25146
|
node: accessKey,
|
|
25388
|
-
message: MESSAGE$
|
|
25147
|
+
message: MESSAGE$38
|
|
25389
25148
|
});
|
|
25390
25149
|
}
|
|
25391
25150
|
} };
|
|
@@ -27240,7 +26999,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
27240
26999
|
});
|
|
27241
27000
|
//#endregion
|
|
27242
27001
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
27243
|
-
const MESSAGE$
|
|
27002
|
+
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.";
|
|
27244
27003
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
27245
27004
|
"button",
|
|
27246
27005
|
"embed",
|
|
@@ -27352,7 +27111,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
27352
27111
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
27353
27112
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
27354
27113
|
node: ariaHidden,
|
|
27355
|
-
message: MESSAGE$
|
|
27114
|
+
message: MESSAGE$37
|
|
27356
27115
|
});
|
|
27357
27116
|
} })
|
|
27358
27117
|
});
|
|
@@ -28274,7 +28033,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
28274
28033
|
};
|
|
28275
28034
|
//#endregion
|
|
28276
28035
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
28277
|
-
const MESSAGE$
|
|
28036
|
+
const MESSAGE$36 = "Your users can see & submit the wrong data when this list reorders.";
|
|
28278
28037
|
const SECOND_INDEX_METHODS = new Set([
|
|
28279
28038
|
"every",
|
|
28280
28039
|
"filter",
|
|
@@ -28393,14 +28152,14 @@ const noArrayIndexKey = defineRule({
|
|
|
28393
28152
|
if (propName !== "key") continue;
|
|
28394
28153
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
28395
28154
|
node: property,
|
|
28396
|
-
message: MESSAGE$
|
|
28155
|
+
message: MESSAGE$36
|
|
28397
28156
|
});
|
|
28398
28157
|
}
|
|
28399
28158
|
} })
|
|
28400
28159
|
});
|
|
28401
28160
|
//#endregion
|
|
28402
28161
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
28403
|
-
const MESSAGE$
|
|
28162
|
+
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.";
|
|
28404
28163
|
const noAsyncEffectCallback = defineRule({
|
|
28405
28164
|
id: "no-async-effect-callback",
|
|
28406
28165
|
title: "Async effect callback",
|
|
@@ -28418,13 +28177,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
28418
28177
|
if (!callback.async) return;
|
|
28419
28178
|
context.report({
|
|
28420
28179
|
node: callback,
|
|
28421
|
-
message: MESSAGE$
|
|
28180
|
+
message: MESSAGE$35
|
|
28422
28181
|
});
|
|
28423
28182
|
} })
|
|
28424
28183
|
});
|
|
28425
28184
|
//#endregion
|
|
28426
28185
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
28427
|
-
const MESSAGE$
|
|
28186
|
+
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.";
|
|
28428
28187
|
const resolveSettings$21 = (settings) => {
|
|
28429
28188
|
const reactDoctor = settings?.["react-doctor"];
|
|
28430
28189
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -28521,7 +28280,7 @@ const noAutofocus = defineRule({
|
|
|
28521
28280
|
if (isConditionallyRendered(node)) return;
|
|
28522
28281
|
context.report({
|
|
28523
28282
|
node: autoFocusAttribute,
|
|
28524
|
-
message: MESSAGE$
|
|
28283
|
+
message: MESSAGE$34
|
|
28525
28284
|
});
|
|
28526
28285
|
} };
|
|
28527
28286
|
}
|
|
@@ -29494,7 +29253,7 @@ const noChainStateUpdates = defineRule({
|
|
|
29494
29253
|
});
|
|
29495
29254
|
//#endregion
|
|
29496
29255
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
29497
|
-
const MESSAGE$
|
|
29256
|
+
const MESSAGE$33 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
29498
29257
|
const noChildrenProp = defineRule({
|
|
29499
29258
|
id: "no-children-prop",
|
|
29500
29259
|
title: "Children passed as a prop",
|
|
@@ -29506,7 +29265,7 @@ const noChildrenProp = defineRule({
|
|
|
29506
29265
|
if (node.name.name !== "children") return;
|
|
29507
29266
|
context.report({
|
|
29508
29267
|
node: node.name,
|
|
29509
|
-
message: MESSAGE$
|
|
29268
|
+
message: MESSAGE$33
|
|
29510
29269
|
});
|
|
29511
29270
|
},
|
|
29512
29271
|
CallExpression(node) {
|
|
@@ -29519,7 +29278,7 @@ const noChildrenProp = defineRule({
|
|
|
29519
29278
|
const propertyKey = property.key;
|
|
29520
29279
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
29521
29280
|
node: propertyKey,
|
|
29522
|
-
message: MESSAGE$
|
|
29281
|
+
message: MESSAGE$33
|
|
29523
29282
|
});
|
|
29524
29283
|
}
|
|
29525
29284
|
}
|
|
@@ -29527,7 +29286,7 @@ const noChildrenProp = defineRule({
|
|
|
29527
29286
|
});
|
|
29528
29287
|
//#endregion
|
|
29529
29288
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
29530
|
-
const MESSAGE$
|
|
29289
|
+
const MESSAGE$32 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
29531
29290
|
const noCloneElement = defineRule({
|
|
29532
29291
|
id: "no-clone-element",
|
|
29533
29292
|
title: "cloneElement makes child props fragile",
|
|
@@ -29540,7 +29299,7 @@ const noCloneElement = defineRule({
|
|
|
29540
29299
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
29541
29300
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
29542
29301
|
node: callee,
|
|
29543
|
-
message: MESSAGE$
|
|
29302
|
+
message: MESSAGE$32
|
|
29544
29303
|
});
|
|
29545
29304
|
return;
|
|
29546
29305
|
}
|
|
@@ -29553,7 +29312,7 @@ const noCloneElement = defineRule({
|
|
|
29553
29312
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
29554
29313
|
context.report({
|
|
29555
29314
|
node: callee,
|
|
29556
|
-
message: MESSAGE$
|
|
29315
|
+
message: MESSAGE$32
|
|
29557
29316
|
});
|
|
29558
29317
|
}
|
|
29559
29318
|
} })
|
|
@@ -29573,7 +29332,7 @@ const getCallMethodName = (callee) => {
|
|
|
29573
29332
|
};
|
|
29574
29333
|
//#endregion
|
|
29575
29334
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
29576
|
-
const MESSAGE$
|
|
29335
|
+
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
29577
29336
|
const CONTEXT_MODULES = [
|
|
29578
29337
|
"react",
|
|
29579
29338
|
"use-context-selector",
|
|
@@ -29621,13 +29380,13 @@ const noCreateContextInRender = defineRule({
|
|
|
29621
29380
|
if (!componentOrHookName) return;
|
|
29622
29381
|
context.report({
|
|
29623
29382
|
node,
|
|
29624
|
-
message: `${MESSAGE$
|
|
29383
|
+
message: `${MESSAGE$31} (called inside "${componentOrHookName}")`
|
|
29625
29384
|
});
|
|
29626
29385
|
} })
|
|
29627
29386
|
});
|
|
29628
29387
|
//#endregion
|
|
29629
29388
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
29630
|
-
const MESSAGE$
|
|
29389
|
+
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.";
|
|
29631
29390
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
29632
29391
|
const parent = functionNode.parent;
|
|
29633
29392
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -29657,7 +29416,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
29657
29416
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
|
|
29658
29417
|
context.report({
|
|
29659
29418
|
node,
|
|
29660
|
-
message: MESSAGE$
|
|
29419
|
+
message: MESSAGE$30
|
|
29661
29420
|
});
|
|
29662
29421
|
} })
|
|
29663
29422
|
});
|
|
@@ -29797,7 +29556,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
29797
29556
|
});
|
|
29798
29557
|
//#endregion
|
|
29799
29558
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
29800
|
-
const MESSAGE$
|
|
29559
|
+
const MESSAGE$29 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
29801
29560
|
const noDanger = defineRule({
|
|
29802
29561
|
id: "no-danger",
|
|
29803
29562
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -29811,7 +29570,7 @@ const noDanger = defineRule({
|
|
|
29811
29570
|
if (!propAttribute) return;
|
|
29812
29571
|
context.report({
|
|
29813
29572
|
node: propAttribute.name,
|
|
29814
|
-
message: MESSAGE$
|
|
29573
|
+
message: MESSAGE$29
|
|
29815
29574
|
});
|
|
29816
29575
|
},
|
|
29817
29576
|
CallExpression(node) {
|
|
@@ -29823,7 +29582,7 @@ const noDanger = defineRule({
|
|
|
29823
29582
|
const propertyKey = property.key;
|
|
29824
29583
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
29825
29584
|
node: propertyKey,
|
|
29826
|
-
message: MESSAGE$
|
|
29585
|
+
message: MESSAGE$29
|
|
29827
29586
|
});
|
|
29828
29587
|
}
|
|
29829
29588
|
}
|
|
@@ -29846,7 +29605,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
29846
29605
|
};
|
|
29847
29606
|
//#endregion
|
|
29848
29607
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
29849
|
-
const MESSAGE$
|
|
29608
|
+
const MESSAGE$28 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
29850
29609
|
const mergePropsShape = (target, source) => {
|
|
29851
29610
|
target.hasDangerously ||= source.hasDangerously;
|
|
29852
29611
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -29921,7 +29680,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29921
29680
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
29922
29681
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
29923
29682
|
node: opening,
|
|
29924
|
-
message: MESSAGE$
|
|
29683
|
+
message: MESSAGE$28
|
|
29925
29684
|
});
|
|
29926
29685
|
},
|
|
29927
29686
|
CallExpression(node) {
|
|
@@ -29934,7 +29693,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29934
29693
|
const positionalChildren = node.arguments.slice(2);
|
|
29935
29694
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
29936
29695
|
node,
|
|
29937
|
-
message: MESSAGE$
|
|
29696
|
+
message: MESSAGE$28
|
|
29938
29697
|
});
|
|
29939
29698
|
}
|
|
29940
29699
|
})
|
|
@@ -30899,7 +30658,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
30899
30658
|
//#endregion
|
|
30900
30659
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
30901
30660
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
30902
|
-
const MESSAGE$
|
|
30661
|
+
const MESSAGE$27 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
30903
30662
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
30904
30663
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
30905
30664
|
let ancestor = setStateCall.parent;
|
|
@@ -31027,7 +30786,7 @@ const noDidMountSetState = defineRule({
|
|
|
31027
30786
|
}
|
|
31028
30787
|
context.report({
|
|
31029
30788
|
node: node.callee,
|
|
31030
|
-
message: MESSAGE$
|
|
30789
|
+
message: MESSAGE$27
|
|
31031
30790
|
});
|
|
31032
30791
|
} };
|
|
31033
30792
|
}
|
|
@@ -31035,7 +30794,7 @@ const noDidMountSetState = defineRule({
|
|
|
31035
30794
|
//#endregion
|
|
31036
30795
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
31037
30796
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
31038
|
-
const MESSAGE$
|
|
30797
|
+
const MESSAGE$26 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
31039
30798
|
const EQUALITY_OPERATORS = new Set([
|
|
31040
30799
|
"==",
|
|
31041
30800
|
"===",
|
|
@@ -31209,7 +30968,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
31209
30968
|
if (isInsideDiffGuard(node)) return;
|
|
31210
30969
|
context.report({
|
|
31211
30970
|
node: node.callee,
|
|
31212
|
-
message: MESSAGE$
|
|
30971
|
+
message: MESSAGE$26
|
|
31213
30972
|
});
|
|
31214
30973
|
} };
|
|
31215
30974
|
}
|
|
@@ -31232,7 +30991,7 @@ const isStateMemberExpression = (node) => {
|
|
|
31232
30991
|
};
|
|
31233
30992
|
//#endregion
|
|
31234
30993
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
31235
|
-
const MESSAGE$
|
|
30994
|
+
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.";
|
|
31236
30995
|
const shouldIgnoreMutation = (node) => {
|
|
31237
30996
|
let isConstructor = false;
|
|
31238
30997
|
let isInsideCallExpression = false;
|
|
@@ -31254,7 +31013,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
31254
31013
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
31255
31014
|
context.report({
|
|
31256
31015
|
node: reportNode,
|
|
31257
|
-
message: MESSAGE$
|
|
31016
|
+
message: MESSAGE$25
|
|
31258
31017
|
});
|
|
31259
31018
|
};
|
|
31260
31019
|
const noDirectMutationState = defineRule({
|
|
@@ -31605,7 +31364,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
31605
31364
|
});
|
|
31606
31365
|
//#endregion
|
|
31607
31366
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
31608
|
-
const MESSAGE$
|
|
31367
|
+
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.";
|
|
31609
31368
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
31610
31369
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
31611
31370
|
const noDocumentWrite = defineRule({
|
|
@@ -31622,7 +31381,7 @@ const noDocumentWrite = defineRule({
|
|
|
31622
31381
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
31623
31382
|
context.report({
|
|
31624
31383
|
node,
|
|
31625
|
-
message: MESSAGE$
|
|
31384
|
+
message: MESSAGE$24
|
|
31626
31385
|
});
|
|
31627
31386
|
} })
|
|
31628
31387
|
});
|
|
@@ -33840,7 +33599,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
33840
33599
|
"ReactDOM",
|
|
33841
33600
|
"ReactDom"
|
|
33842
33601
|
]);
|
|
33843
|
-
const MESSAGE$
|
|
33602
|
+
const MESSAGE$23 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
33844
33603
|
const noFindDomNode = defineRule({
|
|
33845
33604
|
id: "no-find-dom-node",
|
|
33846
33605
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -33851,7 +33610,7 @@ const noFindDomNode = defineRule({
|
|
|
33851
33610
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
33852
33611
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
33853
33612
|
node: callee,
|
|
33854
|
-
message: MESSAGE$
|
|
33613
|
+
message: MESSAGE$23
|
|
33855
33614
|
});
|
|
33856
33615
|
return;
|
|
33857
33616
|
}
|
|
@@ -33862,7 +33621,7 @@ const noFindDomNode = defineRule({
|
|
|
33862
33621
|
if (callee.property.name !== "findDOMNode") return;
|
|
33863
33622
|
context.report({
|
|
33864
33623
|
node: callee.property,
|
|
33865
|
-
message: MESSAGE$
|
|
33624
|
+
message: MESSAGE$23
|
|
33866
33625
|
});
|
|
33867
33626
|
}
|
|
33868
33627
|
} })
|
|
@@ -34071,6 +33830,13 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
34071
33830
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
34072
33831
|
};
|
|
34073
33832
|
//#endregion
|
|
33833
|
+
//#region src/plugin/utils/is-type-only-import.ts
|
|
33834
|
+
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
33835
|
+
if (!specifiers || specifiers.length === 0) return false;
|
|
33836
|
+
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
33837
|
+
};
|
|
33838
|
+
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
33839
|
+
//#endregion
|
|
34074
33840
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
34075
33841
|
const isLibraryDevPage = (filename) => {
|
|
34076
33842
|
if (!filename) return false;
|
|
@@ -34769,7 +34535,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34769
34535
|
});
|
|
34770
34536
|
//#endregion
|
|
34771
34537
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
34772
|
-
const MESSAGE$
|
|
34538
|
+
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.";
|
|
34773
34539
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
34774
34540
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
34775
34541
|
title: "Lazy image with high fetchPriority",
|
|
@@ -34783,7 +34549,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
34783
34549
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
34784
34550
|
context.report({
|
|
34785
34551
|
node: node.name,
|
|
34786
|
-
message: MESSAGE$
|
|
34552
|
+
message: MESSAGE$22
|
|
34787
34553
|
});
|
|
34788
34554
|
} })
|
|
34789
34555
|
});
|
|
@@ -34972,7 +34738,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
34972
34738
|
});
|
|
34973
34739
|
//#endregion
|
|
34974
34740
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
34975
|
-
const MESSAGE$
|
|
34741
|
+
const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
34976
34742
|
const REACT_USE_REF_OPTIONS = {
|
|
34977
34743
|
allowGlobalReactNamespace: false,
|
|
34978
34744
|
allowUnboundBareCalls: false
|
|
@@ -35073,7 +34839,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
35073
34839
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
35074
34840
|
context.report({
|
|
35075
34841
|
node: indeterminateAttribute,
|
|
35076
|
-
message: MESSAGE$
|
|
34842
|
+
message: MESSAGE$21
|
|
35077
34843
|
});
|
|
35078
34844
|
},
|
|
35079
34845
|
CallExpression(node) {
|
|
@@ -35082,7 +34848,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
35082
34848
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
35083
34849
|
context.report({
|
|
35084
34850
|
node,
|
|
35085
|
-
message: MESSAGE$
|
|
34851
|
+
message: MESSAGE$21
|
|
35086
34852
|
});
|
|
35087
34853
|
}
|
|
35088
34854
|
};
|
|
@@ -35370,7 +35136,7 @@ const noIsMounted = defineRule({
|
|
|
35370
35136
|
});
|
|
35371
35137
|
//#endregion
|
|
35372
35138
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
35373
|
-
const MESSAGE$
|
|
35139
|
+
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)`.";
|
|
35374
35140
|
const isJsonMethodCall = (node, method) => {
|
|
35375
35141
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
35376
35142
|
const callee = node.callee;
|
|
@@ -35434,13 +35200,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
35434
35200
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
35435
35201
|
context.report({
|
|
35436
35202
|
node,
|
|
35437
|
-
message: MESSAGE$
|
|
35203
|
+
message: MESSAGE$20
|
|
35438
35204
|
});
|
|
35439
35205
|
} })
|
|
35440
35206
|
});
|
|
35441
35207
|
//#endregion
|
|
35442
35208
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
35443
|
-
const MESSAGE$
|
|
35209
|
+
const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
35444
35210
|
const isJsxElementTypeReference = (node) => {
|
|
35445
35211
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
35446
35212
|
const typeName = node.typeName;
|
|
@@ -35494,7 +35260,7 @@ const noJsxElementType = defineRule({
|
|
|
35494
35260
|
if (isJsxImported) return;
|
|
35495
35261
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
35496
35262
|
node: typeAnnotation,
|
|
35497
|
-
message: MESSAGE$
|
|
35263
|
+
message: MESSAGE$19
|
|
35498
35264
|
});
|
|
35499
35265
|
}
|
|
35500
35266
|
};
|
|
@@ -35726,221 +35492,6 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
35726
35492
|
}
|
|
35727
35493
|
});
|
|
35728
35494
|
//#endregion
|
|
35729
|
-
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
35730
|
-
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
35731
|
-
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
35732
|
-
const expression = stripParenExpression(node);
|
|
35733
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
35734
|
-
const propertyName = getStaticPropertyName(expression);
|
|
35735
|
-
const receiver = stripParenExpression(expression.object);
|
|
35736
|
-
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
35737
|
-
}
|
|
35738
|
-
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
35739
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
35740
|
-
const symbol = scopes.symbolFor(expression);
|
|
35741
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
35742
|
-
visitedSymbolIds.add(symbol.id);
|
|
35743
|
-
if (isImportedFromReact(symbol)) {
|
|
35744
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
35745
|
-
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
35746
|
-
}
|
|
35747
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
35748
|
-
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
35749
|
-
};
|
|
35750
|
-
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
35751
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
35752
|
-
visitedClassNodes.add(classNode);
|
|
35753
|
-
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
35754
|
-
};
|
|
35755
|
-
//#endregion
|
|
35756
|
-
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
35757
|
-
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
35758
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
35759
|
-
let containsReactHookCall = false;
|
|
35760
|
-
walkAst(functionNode.body, (node) => {
|
|
35761
|
-
if (containsReactHookCall) return false;
|
|
35762
|
-
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
35763
|
-
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
35764
|
-
containsReactHookCall = true;
|
|
35765
|
-
return false;
|
|
35766
|
-
}
|
|
35767
|
-
});
|
|
35768
|
-
return containsReactHookCall;
|
|
35769
|
-
};
|
|
35770
|
-
//#endregion
|
|
35771
|
-
//#region src/plugin/utils/function-returns-props-children.ts
|
|
35772
|
-
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
35773
|
-
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
35774
|
-
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
35775
|
-
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
35776
|
-
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
35777
|
-
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
35778
|
-
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
35779
|
-
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
35780
|
-
const propertyValue = stripParenExpression(property.value);
|
|
35781
|
-
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
35782
|
-
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
35783
|
-
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
35784
|
-
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
35785
|
-
}
|
|
35786
|
-
}
|
|
35787
|
-
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
35788
|
-
const candidate = stripParenExpression(expression);
|
|
35789
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
35790
|
-
const symbol = scopes.symbolFor(candidate);
|
|
35791
|
-
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
35792
|
-
}
|
|
35793
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
35794
|
-
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
35795
|
-
const receiver = stripParenExpression(candidate.object);
|
|
35796
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
35797
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
35798
|
-
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
35799
|
-
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
35800
|
-
}, controlFlow);
|
|
35801
|
-
};
|
|
35802
|
-
//#endregion
|
|
35803
|
-
//#region src/plugin/utils/function-returns-only-null.ts
|
|
35804
|
-
const isNullExpression = (expression) => {
|
|
35805
|
-
const candidate = stripParenExpression(expression);
|
|
35806
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
35807
|
-
};
|
|
35808
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
35809
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
35810
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
35811
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
35812
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
35813
|
-
};
|
|
35814
|
-
//#endregion
|
|
35815
|
-
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
35816
|
-
const findFactoryRoot = (node) => {
|
|
35817
|
-
const candidate = stripParenExpression(node);
|
|
35818
|
-
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
35819
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
35820
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
35821
|
-
return null;
|
|
35822
|
-
};
|
|
35823
|
-
const findFactoryPropertyName = (node) => {
|
|
35824
|
-
const candidate = stripParenExpression(node);
|
|
35825
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
35826
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
35827
|
-
return null;
|
|
35828
|
-
};
|
|
35829
|
-
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
35830
|
-
const candidate = stripParenExpression(expression);
|
|
35831
|
-
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
35832
|
-
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
35833
|
-
if (!factoryRoot) return false;
|
|
35834
|
-
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
35835
|
-
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
35836
|
-
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
35837
|
-
if (!symbol || symbol.kind !== "import") return false;
|
|
35838
|
-
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
35839
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
35840
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
35841
|
-
};
|
|
35842
|
-
//#endregion
|
|
35843
|
-
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
35844
|
-
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
35845
|
-
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
35846
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
35847
|
-
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
35848
|
-
const candidate = stripParenExpression(expression);
|
|
35849
|
-
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
35850
|
-
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
35851
|
-
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
35852
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
35853
|
-
const symbol = scopes.symbolFor(candidate);
|
|
35854
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
35855
|
-
visitedSymbolIds.add(symbol.id);
|
|
35856
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
35857
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
35858
|
-
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
35859
|
-
}
|
|
35860
|
-
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
35861
|
-
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
35862
|
-
const factoryCallee = stripParenExpression(candidate.callee);
|
|
35863
|
-
if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
|
|
35864
|
-
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
35865
|
-
const wrappedComponent = candidate.arguments[0];
|
|
35866
|
-
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
35867
|
-
}
|
|
35868
|
-
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
35869
|
-
const factory = candidate.arguments[0];
|
|
35870
|
-
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
35871
|
-
const unwrappedFactory = stripParenExpression(factory);
|
|
35872
|
-
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
35873
|
-
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
35874
|
-
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
35875
|
-
const returnedExpression = returnStatements[0]?.argument;
|
|
35876
|
-
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
35877
|
-
};
|
|
35878
|
-
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
35879
|
-
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
35880
|
-
for (const candidateSymbol of candidateSymbols) {
|
|
35881
|
-
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
35882
|
-
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
35883
|
-
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
35884
|
-
continue;
|
|
35885
|
-
}
|
|
35886
|
-
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
35887
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
35888
|
-
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
35889
|
-
continue;
|
|
35890
|
-
}
|
|
35891
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
35892
|
-
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
35893
|
-
continue;
|
|
35894
|
-
}
|
|
35895
|
-
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
35896
|
-
}
|
|
35897
|
-
return false;
|
|
35898
|
-
};
|
|
35899
|
-
//#endregion
|
|
35900
|
-
//#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
|
|
35901
|
-
const REACT_COMPONENT_TYPE_NAMES = new Set([
|
|
35902
|
-
"ComponentClass",
|
|
35903
|
-
"ComponentType",
|
|
35904
|
-
"FC",
|
|
35905
|
-
"FunctionComponent"
|
|
35906
|
-
]);
|
|
35907
|
-
const findVisibleSymbol = (identifier, scopes) => {
|
|
35908
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
35909
|
-
let scope = scopes.scopeFor(identifier);
|
|
35910
|
-
while (true) {
|
|
35911
|
-
const symbol = scope.symbolsByName.get(identifier.name);
|
|
35912
|
-
if (symbol) return symbol;
|
|
35913
|
-
if (!scope.parent) return null;
|
|
35914
|
-
scope = scope.parent;
|
|
35915
|
-
}
|
|
35916
|
-
};
|
|
35917
|
-
const isReactNamespaceType = (identifier, scopes) => {
|
|
35918
|
-
const symbol = findVisibleSymbol(identifier, scopes);
|
|
35919
|
-
return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
35920
|
-
};
|
|
35921
|
-
const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
|
|
35922
|
-
if (!typeNode) return false;
|
|
35923
|
-
if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
35924
|
-
if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
|
|
35925
|
-
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
35926
|
-
const typeName = typeNode.typeName;
|
|
35927
|
-
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);
|
|
35928
|
-
if (!isNodeOfType(typeName, "Identifier")) return false;
|
|
35929
|
-
const typeSymbol = findVisibleSymbol(typeName, scopes);
|
|
35930
|
-
if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
|
|
35931
|
-
if (isImportedFromReact(typeSymbol)) {
|
|
35932
|
-
const importedName = getImportedName(typeSymbol.declarationNode);
|
|
35933
|
-
return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
|
|
35934
|
-
}
|
|
35935
|
-
if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
|
|
35936
|
-
visitedSymbolIds.add(typeSymbol.id);
|
|
35937
|
-
return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
35938
|
-
};
|
|
35939
|
-
const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
|
|
35940
|
-
const bindingIdentifier = symbol.bindingIdentifier;
|
|
35941
|
-
return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
|
|
35942
|
-
};
|
|
35943
|
-
//#endregion
|
|
35944
35495
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
35945
35496
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
35946
35497
|
"childContextTypes",
|
|
@@ -35951,6 +35502,15 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
35951
35502
|
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.`;
|
|
35952
35503
|
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.";
|
|
35953
35504
|
};
|
|
35505
|
+
const isInsideClassBody = (node) => {
|
|
35506
|
+
let current = node.parent;
|
|
35507
|
+
while (current) {
|
|
35508
|
+
if (isNodeOfType(current, "ClassBody")) return true;
|
|
35509
|
+
if (isFunctionLike$2(current)) return false;
|
|
35510
|
+
current = current.parent;
|
|
35511
|
+
}
|
|
35512
|
+
return false;
|
|
35513
|
+
};
|
|
35954
35514
|
const noLegacyContextApi = defineRule({
|
|
35955
35515
|
id: "no-legacy-context-api",
|
|
35956
35516
|
title: "Legacy context API",
|
|
@@ -35965,7 +35525,6 @@ const noLegacyContextApi = defineRule({
|
|
|
35965
35525
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
35966
35526
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
35967
35527
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
35968
|
-
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
35969
35528
|
context.report({
|
|
35970
35529
|
node: memberNode.key,
|
|
35971
35530
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -35973,8 +35532,6 @@ const noLegacyContextApi = defineRule({
|
|
|
35973
35532
|
};
|
|
35974
35533
|
return {
|
|
35975
35534
|
ClassBody(node) {
|
|
35976
|
-
const classNode = node.parent;
|
|
35977
|
-
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
35978
35535
|
for (const member of node.body ?? []) checkMember(member);
|
|
35979
35536
|
},
|
|
35980
35537
|
AssignmentExpression(node) {
|
|
@@ -35984,11 +35541,9 @@ const noLegacyContextApi = defineRule({
|
|
|
35984
35541
|
if (left.computed) return;
|
|
35985
35542
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
35986
35543
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
35987
|
-
if (left.
|
|
35988
|
-
|
|
35989
|
-
if (
|
|
35990
|
-
const symbol = context.scopes.symbolFor(component);
|
|
35991
|
-
if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
|
|
35544
|
+
if (!isNodeOfType(left.object, "Identifier")) return;
|
|
35545
|
+
if (!isUppercaseName(left.object.name)) return;
|
|
35546
|
+
if (isInsideClassBody(node)) return;
|
|
35992
35547
|
context.report({
|
|
35993
35548
|
node: left,
|
|
35994
35549
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -36810,7 +36365,7 @@ const noMoment = defineRule({
|
|
|
36810
36365
|
});
|
|
36811
36366
|
//#endregion
|
|
36812
36367
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
36813
|
-
const MESSAGE$
|
|
36368
|
+
const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
36814
36369
|
const resolveSettings$16 = (settings) => {
|
|
36815
36370
|
const reactDoctor = settings?.["react-doctor"];
|
|
36816
36371
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -37169,7 +36724,7 @@ const noMultiComp = defineRule({
|
|
|
37169
36724
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
37170
36725
|
for (const component of flagged.slice(1)) context.report({
|
|
37171
36726
|
node: component.reportNode,
|
|
37172
|
-
message: MESSAGE$
|
|
36727
|
+
message: MESSAGE$18
|
|
37173
36728
|
});
|
|
37174
36729
|
} };
|
|
37175
36730
|
}
|
|
@@ -37382,7 +36937,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
37382
36937
|
};
|
|
37383
36938
|
//#endregion
|
|
37384
36939
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
37385
|
-
const MESSAGE$
|
|
36940
|
+
const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
|
|
37386
36941
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
37387
36942
|
"copyWithin",
|
|
37388
36943
|
"fill",
|
|
@@ -37591,7 +37146,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37591
37146
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
37592
37147
|
context.report({
|
|
37593
37148
|
node: options.crossFileConsumerCallSite,
|
|
37594
|
-
message: `${MESSAGE$
|
|
37149
|
+
message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
37595
37150
|
});
|
|
37596
37151
|
return;
|
|
37597
37152
|
}
|
|
@@ -37600,7 +37155,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37600
37155
|
reportedNodes.add(mutation.node);
|
|
37601
37156
|
context.report({
|
|
37602
37157
|
node: mutation.node,
|
|
37603
|
-
message: MESSAGE$
|
|
37158
|
+
message: MESSAGE$17
|
|
37604
37159
|
});
|
|
37605
37160
|
}
|
|
37606
37161
|
};
|
|
@@ -38007,7 +37562,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
38007
37562
|
});
|
|
38008
37563
|
//#endregion
|
|
38009
37564
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
38010
|
-
const MESSAGE$
|
|
37565
|
+
const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
38011
37566
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
38012
37567
|
"onKeyDown",
|
|
38013
37568
|
"onKeyUp",
|
|
@@ -38126,7 +37681,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38126
37681
|
if (numeric === null) {
|
|
38127
37682
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
38128
37683
|
node: tabIndex,
|
|
38129
|
-
message: MESSAGE$
|
|
37684
|
+
message: MESSAGE$16
|
|
38130
37685
|
});
|
|
38131
37686
|
return;
|
|
38132
37687
|
}
|
|
@@ -38148,7 +37703,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38148
37703
|
if (!roleAttribute) {
|
|
38149
37704
|
context.report({
|
|
38150
37705
|
node: tabIndex,
|
|
38151
|
-
message: MESSAGE$
|
|
37706
|
+
message: MESSAGE$16
|
|
38152
37707
|
});
|
|
38153
37708
|
return;
|
|
38154
37709
|
}
|
|
@@ -38162,7 +37717,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38162
37717
|
}
|
|
38163
37718
|
context.report({
|
|
38164
37719
|
node: tabIndex,
|
|
38165
|
-
message: MESSAGE$
|
|
37720
|
+
message: MESSAGE$16
|
|
38166
37721
|
});
|
|
38167
37722
|
} };
|
|
38168
37723
|
}
|
|
@@ -39581,6 +39136,174 @@ const noPropCallbackInRender = defineRule({
|
|
|
39581
39136
|
} })
|
|
39582
39137
|
});
|
|
39583
39138
|
//#endregion
|
|
39139
|
+
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
39140
|
+
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
39141
|
+
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
39142
|
+
const expression = stripParenExpression(node);
|
|
39143
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39144
|
+
const propertyName = getStaticPropertyName(expression);
|
|
39145
|
+
const receiver = stripParenExpression(expression.object);
|
|
39146
|
+
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
39147
|
+
}
|
|
39148
|
+
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39149
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39150
|
+
const symbol = scopes.symbolFor(expression);
|
|
39151
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
39152
|
+
visitedSymbolIds.add(symbol.id);
|
|
39153
|
+
if (isImportedFromReact(symbol)) {
|
|
39154
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
39155
|
+
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
39156
|
+
}
|
|
39157
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39158
|
+
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
39159
|
+
};
|
|
39160
|
+
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39161
|
+
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
39162
|
+
visitedClassNodes.add(classNode);
|
|
39163
|
+
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39164
|
+
};
|
|
39165
|
+
//#endregion
|
|
39166
|
+
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
39167
|
+
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
39168
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
39169
|
+
let containsReactHookCall = false;
|
|
39170
|
+
walkAst(functionNode.body, (node) => {
|
|
39171
|
+
if (containsReactHookCall) return false;
|
|
39172
|
+
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
39173
|
+
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39174
|
+
containsReactHookCall = true;
|
|
39175
|
+
return false;
|
|
39176
|
+
}
|
|
39177
|
+
});
|
|
39178
|
+
return containsReactHookCall;
|
|
39179
|
+
};
|
|
39180
|
+
//#endregion
|
|
39181
|
+
//#region src/plugin/utils/function-returns-props-children.ts
|
|
39182
|
+
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
39183
|
+
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
39184
|
+
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
39185
|
+
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
39186
|
+
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
39187
|
+
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
39188
|
+
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
39189
|
+
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
39190
|
+
const propertyValue = stripParenExpression(property.value);
|
|
39191
|
+
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
39192
|
+
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
39193
|
+
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
39194
|
+
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
39195
|
+
}
|
|
39196
|
+
}
|
|
39197
|
+
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
39198
|
+
const candidate = stripParenExpression(expression);
|
|
39199
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
39200
|
+
const symbol = scopes.symbolFor(candidate);
|
|
39201
|
+
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
39202
|
+
}
|
|
39203
|
+
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
39204
|
+
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
39205
|
+
const receiver = stripParenExpression(candidate.object);
|
|
39206
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
39207
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
39208
|
+
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
39209
|
+
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
39210
|
+
}, controlFlow);
|
|
39211
|
+
};
|
|
39212
|
+
//#endregion
|
|
39213
|
+
//#region src/plugin/utils/function-returns-only-null.ts
|
|
39214
|
+
const isNullExpression = (expression) => {
|
|
39215
|
+
const candidate = stripParenExpression(expression);
|
|
39216
|
+
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39217
|
+
};
|
|
39218
|
+
const functionReturnsOnlyNull = (functionNode) => {
|
|
39219
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
39220
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39221
|
+
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39222
|
+
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39223
|
+
};
|
|
39224
|
+
//#endregion
|
|
39225
|
+
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
39226
|
+
const findFactoryRoot = (node) => {
|
|
39227
|
+
const candidate = stripParenExpression(node);
|
|
39228
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
39229
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
39230
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
39231
|
+
return null;
|
|
39232
|
+
};
|
|
39233
|
+
const findFactoryPropertyName = (node) => {
|
|
39234
|
+
const candidate = stripParenExpression(node);
|
|
39235
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
39236
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
39237
|
+
return null;
|
|
39238
|
+
};
|
|
39239
|
+
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
39240
|
+
const candidate = stripParenExpression(expression);
|
|
39241
|
+
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
39242
|
+
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
39243
|
+
if (!factoryRoot) return false;
|
|
39244
|
+
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
39245
|
+
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
39246
|
+
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
39247
|
+
if (!symbol || symbol.kind !== "import") return false;
|
|
39248
|
+
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
39249
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
39250
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
39251
|
+
};
|
|
39252
|
+
//#endregion
|
|
39253
|
+
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
39254
|
+
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
39255
|
+
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
39256
|
+
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39257
|
+
const candidate = stripParenExpression(expression);
|
|
39258
|
+
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
39259
|
+
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
39260
|
+
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
39261
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
39262
|
+
const symbol = scopes.symbolFor(candidate);
|
|
39263
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
39264
|
+
visitedSymbolIds.add(symbol.id);
|
|
39265
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
39266
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
39267
|
+
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
39268
|
+
}
|
|
39269
|
+
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
39270
|
+
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
39271
|
+
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39272
|
+
const wrappedComponent = candidate.arguments[0];
|
|
39273
|
+
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
39274
|
+
}
|
|
39275
|
+
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
39276
|
+
const factory = candidate.arguments[0];
|
|
39277
|
+
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
39278
|
+
const unwrappedFactory = stripParenExpression(factory);
|
|
39279
|
+
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
39280
|
+
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
39281
|
+
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
39282
|
+
const returnedExpression = returnStatements[0]?.argument;
|
|
39283
|
+
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
39284
|
+
};
|
|
39285
|
+
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
39286
|
+
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
39287
|
+
for (const candidateSymbol of candidateSymbols) {
|
|
39288
|
+
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
39289
|
+
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
39290
|
+
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
39291
|
+
continue;
|
|
39292
|
+
}
|
|
39293
|
+
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
39294
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
39295
|
+
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
39296
|
+
continue;
|
|
39297
|
+
}
|
|
39298
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
39299
|
+
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
39300
|
+
continue;
|
|
39301
|
+
}
|
|
39302
|
+
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
39303
|
+
}
|
|
39304
|
+
return false;
|
|
39305
|
+
};
|
|
39306
|
+
//#endregion
|
|
39584
39307
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
39585
39308
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
39586
39309
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -39788,7 +39511,7 @@ const noRandomKey = defineRule({
|
|
|
39788
39511
|
});
|
|
39789
39512
|
//#endregion
|
|
39790
39513
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
39791
|
-
const MESSAGE$
|
|
39514
|
+
const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
39792
39515
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
39793
39516
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
39794
39517
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -39814,13 +39537,13 @@ const noReactChildren = defineRule({
|
|
|
39814
39537
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
39815
39538
|
context.report({
|
|
39816
39539
|
node: calleeOuter,
|
|
39817
|
-
message: MESSAGE$
|
|
39540
|
+
message: MESSAGE$15
|
|
39818
39541
|
});
|
|
39819
39542
|
return;
|
|
39820
39543
|
}
|
|
39821
39544
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
39822
39545
|
node: calleeOuter,
|
|
39823
|
-
message: MESSAGE$
|
|
39546
|
+
message: MESSAGE$15
|
|
39824
39547
|
});
|
|
39825
39548
|
} })
|
|
39826
39549
|
});
|
|
@@ -40501,7 +40224,7 @@ const noRenderPropChildren = defineRule({
|
|
|
40501
40224
|
});
|
|
40502
40225
|
//#endregion
|
|
40503
40226
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
40504
|
-
const MESSAGE$
|
|
40227
|
+
const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
40505
40228
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
40506
40229
|
const isUsedAsReturnValue = (parent) => {
|
|
40507
40230
|
if (!parent) return false;
|
|
@@ -40519,7 +40242,7 @@ const noRenderReturnValue = defineRule({
|
|
|
40519
40242
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
40520
40243
|
context.report({
|
|
40521
40244
|
node: node.callee,
|
|
40522
|
-
message: MESSAGE$
|
|
40245
|
+
message: MESSAGE$14
|
|
40523
40246
|
});
|
|
40524
40247
|
} })
|
|
40525
40248
|
});
|
|
@@ -41328,7 +41051,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
41328
41051
|
});
|
|
41329
41052
|
//#endregion
|
|
41330
41053
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
41331
|
-
const MESSAGE$
|
|
41054
|
+
const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
41332
41055
|
const noSetState = defineRule({
|
|
41333
41056
|
id: "no-set-state",
|
|
41334
41057
|
title: "Local class state forbidden",
|
|
@@ -41343,7 +41066,7 @@ const noSetState = defineRule({
|
|
|
41343
41066
|
if (!getParentComponent(node)) return;
|
|
41344
41067
|
context.report({
|
|
41345
41068
|
node: node.callee,
|
|
41346
|
-
message: MESSAGE$
|
|
41069
|
+
message: MESSAGE$13
|
|
41347
41070
|
});
|
|
41348
41071
|
} })
|
|
41349
41072
|
});
|
|
@@ -41713,7 +41436,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
41713
41436
|
};
|
|
41714
41437
|
//#endregion
|
|
41715
41438
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
41716
|
-
const MESSAGE$
|
|
41439
|
+
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.";
|
|
41717
41440
|
const DEFAULT_HANDLERS = [
|
|
41718
41441
|
"onClick",
|
|
41719
41442
|
"onMouseDown",
|
|
@@ -41828,7 +41551,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41828
41551
|
if (!roleAttribute || !roleAttribute.value) {
|
|
41829
41552
|
context.report({
|
|
41830
41553
|
node: node.name,
|
|
41831
|
-
message: MESSAGE$
|
|
41554
|
+
message: MESSAGE$12
|
|
41832
41555
|
});
|
|
41833
41556
|
return;
|
|
41834
41557
|
}
|
|
@@ -41838,7 +41561,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41838
41561
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
41839
41562
|
context.report({
|
|
41840
41563
|
node: node.name,
|
|
41841
|
-
message: MESSAGE$
|
|
41564
|
+
message: MESSAGE$12
|
|
41842
41565
|
});
|
|
41843
41566
|
return;
|
|
41844
41567
|
}
|
|
@@ -41846,7 +41569,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41846
41569
|
if (!settings.allowExpressionValues) {
|
|
41847
41570
|
context.report({
|
|
41848
41571
|
node: node.name,
|
|
41849
|
-
message: MESSAGE$
|
|
41572
|
+
message: MESSAGE$12
|
|
41850
41573
|
});
|
|
41851
41574
|
return;
|
|
41852
41575
|
}
|
|
@@ -41854,7 +41577,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41854
41577
|
if (isStaticNullishExpression(expression)) {
|
|
41855
41578
|
context.report({
|
|
41856
41579
|
node: node.name,
|
|
41857
|
-
message: MESSAGE$
|
|
41580
|
+
message: MESSAGE$12
|
|
41858
41581
|
});
|
|
41859
41582
|
return;
|
|
41860
41583
|
}
|
|
@@ -41864,7 +41587,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41864
41587
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
41865
41588
|
context.report({
|
|
41866
41589
|
node: node.name,
|
|
41867
|
-
message: MESSAGE$
|
|
41590
|
+
message: MESSAGE$12
|
|
41868
41591
|
});
|
|
41869
41592
|
return;
|
|
41870
41593
|
}
|
|
@@ -41872,7 +41595,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41872
41595
|
}
|
|
41873
41596
|
context.report({
|
|
41874
41597
|
node: node.name,
|
|
41875
|
-
message: MESSAGE$
|
|
41598
|
+
message: MESSAGE$12
|
|
41876
41599
|
});
|
|
41877
41600
|
} };
|
|
41878
41601
|
}
|
|
@@ -41978,7 +41701,7 @@ const noStringRefs = defineRule({
|
|
|
41978
41701
|
});
|
|
41979
41702
|
//#endregion
|
|
41980
41703
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
41981
|
-
const MESSAGE$
|
|
41704
|
+
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)`).";
|
|
41982
41705
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
41983
41706
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
41984
41707
|
const noSyncXhr = defineRule({
|
|
@@ -41999,7 +41722,7 @@ const noSyncXhr = defineRule({
|
|
|
41999
41722
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
42000
41723
|
context.report({
|
|
42001
41724
|
node,
|
|
42002
|
-
message: MESSAGE$
|
|
41725
|
+
message: MESSAGE$11
|
|
42003
41726
|
});
|
|
42004
41727
|
};
|
|
42005
41728
|
return {
|
|
@@ -42024,7 +41747,7 @@ const noSyncXhr = defineRule({
|
|
|
42024
41747
|
});
|
|
42025
41748
|
//#endregion
|
|
42026
41749
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
42027
|
-
const MESSAGE$
|
|
41750
|
+
const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
|
|
42028
41751
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
42029
41752
|
let ancestor = node.parent;
|
|
42030
41753
|
while (ancestor) {
|
|
@@ -42112,7 +41835,7 @@ const noThisInSfc = defineRule({
|
|
|
42112
41835
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
42113
41836
|
context.report({
|
|
42114
41837
|
node,
|
|
42115
|
-
message: MESSAGE$
|
|
41838
|
+
message: MESSAGE$10
|
|
42116
41839
|
});
|
|
42117
41840
|
} };
|
|
42118
41841
|
}
|
|
@@ -43757,11 +43480,7 @@ const resolveSettings$8 = (settings) => {
|
|
|
43757
43480
|
propNamePattern: ruleSettings.propNamePattern ?? "render*"
|
|
43758
43481
|
};
|
|
43759
43482
|
};
|
|
43760
|
-
const
|
|
43761
|
-
allowGlobalReactNamespace: true,
|
|
43762
|
-
resolveNamedAliases: true
|
|
43763
|
-
});
|
|
43764
|
-
const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
43483
|
+
const expressionContainsJsxOrCreateElement = (root) => {
|
|
43765
43484
|
let found = false;
|
|
43766
43485
|
walkAst(root, (node) => {
|
|
43767
43486
|
if (found) return false;
|
|
@@ -43770,17 +43489,17 @@ const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
|
43770
43489
|
found = true;
|
|
43771
43490
|
return false;
|
|
43772
43491
|
}
|
|
43773
|
-
if (isNodeOfType(node, "CallExpression") &&
|
|
43492
|
+
if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
|
|
43774
43493
|
found = true;
|
|
43775
43494
|
return false;
|
|
43776
43495
|
}
|
|
43777
43496
|
});
|
|
43778
43497
|
return found;
|
|
43779
43498
|
};
|
|
43780
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode
|
|
43781
|
-
const isReactClassComponent = (classNode
|
|
43499
|
+
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
|
|
43500
|
+
const isReactClassComponent = (classNode) => {
|
|
43782
43501
|
if (isEs6Component(classNode)) return true;
|
|
43783
|
-
return expressionContainsJsxOrCreateElement(classNode
|
|
43502
|
+
return expressionContainsJsxOrCreateElement(classNode);
|
|
43784
43503
|
};
|
|
43785
43504
|
const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
43786
43505
|
let walker = node.parent;
|
|
@@ -43797,7 +43516,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
|
43797
43516
|
};
|
|
43798
43517
|
}
|
|
43799
43518
|
if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
|
|
43800
|
-
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker
|
|
43519
|
+
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
|
|
43801
43520
|
component: walker,
|
|
43802
43521
|
name: walker.id.name
|
|
43803
43522
|
};
|
|
@@ -43886,12 +43605,12 @@ const isObjectCallbackCandidate = (node) => {
|
|
|
43886
43605
|
}
|
|
43887
43606
|
return false;
|
|
43888
43607
|
};
|
|
43889
|
-
const hocCallContainsComponent = (call
|
|
43608
|
+
const hocCallContainsComponent = (call) => {
|
|
43890
43609
|
const firstArgument = call.arguments[0];
|
|
43891
43610
|
if (!firstArgument) return false;
|
|
43892
|
-
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument
|
|
43893
|
-
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument
|
|
43894
|
-
return expressionContainsJsxOrCreateElement(firstArgument
|
|
43611
|
+
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument);
|
|
43612
|
+
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument);
|
|
43613
|
+
return expressionContainsJsxOrCreateElement(firstArgument);
|
|
43895
43614
|
};
|
|
43896
43615
|
const isFirstArgumentOfHocCall = (node) => {
|
|
43897
43616
|
const parent = node.parent;
|
|
@@ -43924,35 +43643,19 @@ const isReturnOfMapCallback = (node) => {
|
|
|
43924
43643
|
}
|
|
43925
43644
|
return false;
|
|
43926
43645
|
};
|
|
43646
|
+
const isCloneElementCall = (node) => {
|
|
43647
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
43648
|
+
const callee = node.callee;
|
|
43649
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name === "cloneElement";
|
|
43650
|
+
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "cloneElement";
|
|
43651
|
+
};
|
|
43927
43652
|
const TS_VALUE_PASSTHROUGH_TYPES = new Set([
|
|
43928
43653
|
"TSAsExpression",
|
|
43929
43654
|
"TSNonNullExpression",
|
|
43930
43655
|
"TSSatisfiesExpression",
|
|
43931
43656
|
"TSTypeAssertion"
|
|
43932
43657
|
]);
|
|
43933
|
-
const
|
|
43934
|
-
"as",
|
|
43935
|
-
"body",
|
|
43936
|
-
"calendarcontainer",
|
|
43937
|
-
"component",
|
|
43938
|
-
"fallback",
|
|
43939
|
-
"tooltip"
|
|
43940
|
-
]);
|
|
43941
|
-
const isElementTypeJsxAttribute = (node) => {
|
|
43942
|
-
if (!isNodeOfType(node, "JSXAttribute")) return false;
|
|
43943
|
-
if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
|
|
43944
|
-
const attributeName = node.name.name;
|
|
43945
|
-
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
43946
|
-
};
|
|
43947
|
-
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
43948
|
-
allowGlobalReactNamespace: true,
|
|
43949
|
-
resolveNamedAliases: true
|
|
43950
|
-
});
|
|
43951
|
-
const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
|
|
43952
|
-
allowGlobalReactNamespace: true,
|
|
43953
|
-
resolveNamedAliases: true
|
|
43954
|
-
});
|
|
43955
|
-
const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
43658
|
+
const isRenderFlowingReadReference = (identifier) => {
|
|
43956
43659
|
let valueNode = identifier;
|
|
43957
43660
|
let parent = valueNode.parent;
|
|
43958
43661
|
while (parent) {
|
|
@@ -43962,26 +43665,20 @@ const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @_
|
|
|
43962
43665
|
continue;
|
|
43963
43666
|
}
|
|
43964
43667
|
switch (parent.type) {
|
|
43965
|
-
case "
|
|
43966
|
-
case "
|
|
43967
|
-
case "
|
|
43968
|
-
case "ArrowFunctionExpression": return false;
|
|
43668
|
+
case "JSXExpressionContainer":
|
|
43669
|
+
case "ReturnStatement": return true;
|
|
43670
|
+
case "ArrowFunctionExpression": return parent.body === valueNode;
|
|
43969
43671
|
case "CallExpression":
|
|
43970
43672
|
if (parent.callee === valueNode) return false;
|
|
43971
|
-
if (
|
|
43972
|
-
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
43673
|
+
if (isCreateElementCall(parent) || isCloneElementCall(parent)) return true;
|
|
43973
43674
|
valueNode = parent;
|
|
43974
43675
|
parent = parent.parent;
|
|
43975
43676
|
continue;
|
|
43976
|
-
case "VariableDeclarator":
|
|
43977
|
-
|
|
43978
|
-
const
|
|
43979
|
-
|
|
43980
|
-
|
|
43981
|
-
nextVisitedSymbols.add(aliasSymbol.id);
|
|
43982
|
-
return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
|
|
43983
|
-
}
|
|
43984
|
-
case "AssignmentExpression": return false;
|
|
43677
|
+
case "VariableDeclarator": return parent.init === valueNode && isNodeOfType(parent.id, "Identifier") && isReactComponentName(parent.id.name);
|
|
43678
|
+
case "AssignmentExpression": {
|
|
43679
|
+
const assignmentTarget = parent.left;
|
|
43680
|
+
return parent.right === valueNode && isNodeOfType(assignmentTarget, "Identifier") && isReactComponentName(assignmentTarget.name);
|
|
43681
|
+
}
|
|
43985
43682
|
case "Property":
|
|
43986
43683
|
if (parent.value !== valueNode) return false;
|
|
43987
43684
|
valueNode = parent;
|
|
@@ -44019,7 +43716,6 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44019
43716
|
severity: "warn",
|
|
44020
43717
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
44021
43718
|
category: "Performance",
|
|
44022
|
-
tags: ["react-jsx-only"],
|
|
44023
43719
|
create: (context) => {
|
|
44024
43720
|
const settings = resolveSettings$8(context.settings);
|
|
44025
43721
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -44082,7 +43778,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44082
43778
|
},
|
|
44083
43779
|
Identifier(node) {
|
|
44084
43780
|
if (!isReactComponentName(node.name)) return;
|
|
44085
|
-
if (!isRenderFlowingReadReference(node
|
|
43781
|
+
if (!isRenderFlowingReadReference(node)) return;
|
|
44086
43782
|
recordInstantiation(node, node.name);
|
|
44087
43783
|
},
|
|
44088
43784
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -44091,33 +43787,28 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44091
43787
|
ClassDeclaration(node) {
|
|
44092
43788
|
if (!node.id) return;
|
|
44093
43789
|
if (!isReactComponentName(node.id.name)) return;
|
|
44094
|
-
if (!isReactClassComponent(node
|
|
43790
|
+
if (!isReactClassComponent(node)) return;
|
|
44095
43791
|
enqueueCandidate(node, null);
|
|
44096
43792
|
},
|
|
44097
43793
|
ClassExpression(node) {
|
|
44098
43794
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
44099
43795
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
44100
|
-
if (!isReactClassComponent(node
|
|
43796
|
+
if (!isReactClassComponent(node)) return;
|
|
44101
43797
|
enqueueCandidate(node, null);
|
|
44102
43798
|
},
|
|
44103
43799
|
CallExpression(node) {
|
|
44104
|
-
if (
|
|
43800
|
+
if (isCreateElementCall(node)) {
|
|
44105
43801
|
const firstArgument = node.arguments[0];
|
|
44106
43802
|
if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
|
|
44107
43803
|
else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
|
|
44108
43804
|
}
|
|
44109
|
-
|
|
44110
|
-
if (!
|
|
44111
|
-
|
|
44112
|
-
const inferredName = inferFunctionLikeName(node);
|
|
44113
|
-
const propInfo = isComponentDeclaredInProp(node);
|
|
44114
|
-
if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
|
|
44115
|
-
enqueueCandidate(node, propInfo === null ? inferredName : null);
|
|
43805
|
+
if (!isHocCallee$1(node)) return;
|
|
43806
|
+
if (!hocCallContainsComponent(node)) return;
|
|
43807
|
+
enqueueCandidate(node, null);
|
|
44116
43808
|
},
|
|
44117
43809
|
"Program:exit"() {
|
|
44118
43810
|
for (const report of queuedReports) {
|
|
44119
43811
|
if (report.requiredInstantiationName !== null) {
|
|
44120
|
-
if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
|
|
44121
43812
|
if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
|
|
44122
43813
|
}
|
|
44123
43814
|
context.report({
|
|
@@ -44291,7 +43982,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
44291
43982
|
//#endregion
|
|
44292
43983
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
44293
43984
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
44294
|
-
const MESSAGE$
|
|
43985
|
+
const MESSAGE$9 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
44295
43986
|
const resolveSettings$7 = (settings) => {
|
|
44296
43987
|
const reactDoctor = settings?.["react-doctor"];
|
|
44297
43988
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -44325,7 +44016,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
44325
44016
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
44326
44017
|
context.report({
|
|
44327
44018
|
node: node.callee,
|
|
44328
|
-
message: MESSAGE$
|
|
44019
|
+
message: MESSAGE$9
|
|
44329
44020
|
});
|
|
44330
44021
|
} };
|
|
44331
44022
|
}
|
|
@@ -45321,7 +45012,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
45321
45012
|
});
|
|
45322
45013
|
//#endregion
|
|
45323
45014
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
45324
|
-
const MESSAGE$
|
|
45015
|
+
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.";
|
|
45325
45016
|
const preactPreferOndblclick = defineRule({
|
|
45326
45017
|
id: "preact-prefer-ondblclick",
|
|
45327
45018
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -45336,7 +45027,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
45336
45027
|
if (!onDoubleClickAttribute) return;
|
|
45337
45028
|
context.report({
|
|
45338
45029
|
node: onDoubleClickAttribute,
|
|
45339
|
-
message: MESSAGE$
|
|
45030
|
+
message: MESSAGE$8
|
|
45340
45031
|
});
|
|
45341
45032
|
} })
|
|
45342
45033
|
});
|
|
@@ -45587,7 +45278,7 @@ const preferExplicitVariants = defineRule({
|
|
|
45587
45278
|
});
|
|
45588
45279
|
//#endregion
|
|
45589
45280
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
45590
|
-
const MESSAGE$
|
|
45281
|
+
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.";
|
|
45591
45282
|
const resolveSettings$4 = (settings) => {
|
|
45592
45283
|
const reactDoctor = settings?.["react-doctor"];
|
|
45593
45284
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -45626,7 +45317,7 @@ const preferFunctionComponent = defineRule({
|
|
|
45626
45317
|
const reportNode = node.id ?? node;
|
|
45627
45318
|
context.report({
|
|
45628
45319
|
node: reportNode,
|
|
45629
|
-
message: MESSAGE$
|
|
45320
|
+
message: MESSAGE$7
|
|
45630
45321
|
});
|
|
45631
45322
|
};
|
|
45632
45323
|
return {
|
|
@@ -47204,258 +46895,7 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
47204
46895
|
}
|
|
47205
46896
|
});
|
|
47206
46897
|
//#endregion
|
|
47207
|
-
//#region src/plugin/utils/is-node-conditionally-executed.ts
|
|
47208
|
-
const isNodeConditionallyExecuted = (node, boundary) => {
|
|
47209
|
-
let child = node;
|
|
47210
|
-
let parent = child.parent ?? null;
|
|
47211
|
-
while (parent && parent !== boundary) {
|
|
47212
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) return true;
|
|
47213
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) return true;
|
|
47214
|
-
if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) return true;
|
|
47215
|
-
child = parent;
|
|
47216
|
-
parent = child.parent ?? null;
|
|
47217
|
-
}
|
|
47218
|
-
return false;
|
|
47219
|
-
};
|
|
47220
|
-
//#endregion
|
|
47221
|
-
//#region src/plugin/rules/tanstack-query/utils/resolve-tanstack-query-hook-name.ts
|
|
47222
|
-
const resolveTanstackNamespaceHookName = (memberExpression, contextNode, scopes) => {
|
|
47223
|
-
const hookName = getStaticPropertyKeyName(memberExpression, { allowComputedString: true });
|
|
47224
|
-
const namespaceObject = stripParenExpression(memberExpression.object);
|
|
47225
|
-
if (!hookName || !TANSTACK_QUERY_HOOKS.has(hookName)) return null;
|
|
47226
|
-
if (!isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
47227
|
-
const resolvedNamespaceSymbol = resolveConstIdentifierAlias(namespaceObject, scopes);
|
|
47228
|
-
if (resolvedNamespaceSymbol?.kind !== "import") return null;
|
|
47229
|
-
const namespaceBinding = getImportBindingForName(contextNode, resolvedNamespaceSymbol.name);
|
|
47230
|
-
return namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source) ? hookName : null;
|
|
47231
|
-
};
|
|
47232
|
-
const resolveTanstackQueryHookName = (callExpression, scopes) => {
|
|
47233
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
47234
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
47235
|
-
const resolvedSymbol = resolveConstIdentifierAlias(callee, scopes);
|
|
47236
|
-
if (!resolvedSymbol) return null;
|
|
47237
|
-
if (resolvedSymbol.kind === "const" && resolvedSymbol.initializer) {
|
|
47238
|
-
const initializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47239
|
-
return isNodeOfType(initializer, "MemberExpression") ? resolveTanstackNamespaceHookName(initializer, callExpression, scopes) : null;
|
|
47240
|
-
}
|
|
47241
|
-
if (resolvedSymbol.kind !== "import") return null;
|
|
47242
|
-
const importBinding = getImportBindingForName(callExpression, resolvedSymbol.name);
|
|
47243
|
-
if (importBinding === null) return null;
|
|
47244
|
-
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
47245
|
-
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
47246
|
-
}
|
|
47247
|
-
if (isNodeOfType(callee, "MemberExpression")) return resolveTanstackNamespaceHookName(callee, callExpression, scopes);
|
|
47248
|
-
return null;
|
|
47249
|
-
};
|
|
47250
|
-
const resolveTanstackQueryHookNameFromInitializer = (initializer, scopes) => {
|
|
47251
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
47252
|
-
if (isNodeOfType(unwrappedInitializer, "CallExpression")) return resolveTanstackQueryHookName(unwrappedInitializer, scopes);
|
|
47253
|
-
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return null;
|
|
47254
|
-
const resolvedSymbol = resolveConstIdentifierAlias(unwrappedInitializer, scopes);
|
|
47255
|
-
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
47256
|
-
const resolvedInitializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47257
|
-
if (!isNodeOfType(resolvedInitializer, "CallExpression")) return null;
|
|
47258
|
-
return resolveTanstackQueryHookName(resolvedInitializer, scopes);
|
|
47259
|
-
};
|
|
47260
|
-
//#endregion
|
|
47261
46898
|
//#region src/plugin/rules/tanstack-query/query-no-query-in-effect.ts
|
|
47262
|
-
const isTanstackQueryResult = (expression, context) => Boolean(resolveTanstackQueryHookNameFromInitializer(expression, context.scopes));
|
|
47263
|
-
const isStaticRefetchMember = (memberExpression) => getStaticPropertyKeyName(memberExpression, { allowComputedString: true }) === "refetch";
|
|
47264
|
-
const resolveCalledFunction = (callee, context) => {
|
|
47265
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
47266
|
-
if (isFunctionLike$2(unwrappedCallee)) return unwrappedCallee;
|
|
47267
|
-
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
47268
|
-
const symbol = resolveConstIdentifierAlias(unwrappedCallee, context.scopes);
|
|
47269
|
-
if (!symbol) return null;
|
|
47270
|
-
const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
|
|
47271
|
-
return candidate && isFunctionLike$2(candidate) ? candidate : null;
|
|
47272
|
-
};
|
|
47273
|
-
const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
47274
|
-
if (!isFunctionLike$2(functionNode)) return true;
|
|
47275
|
-
if (functionNode.generator) return true;
|
|
47276
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47277
|
-
if (boundaryStart === null) return true;
|
|
47278
|
-
let hasSuspension = false;
|
|
47279
|
-
walkAst(functionNode, (node) => {
|
|
47280
|
-
if (node !== functionNode && isFunctionLike$2(node)) return false;
|
|
47281
|
-
if (!isNodeOfType(node, "AwaitExpression") || !isNodeReachableWithinFunction(node, context)) return;
|
|
47282
|
-
const suspensionStart = getRangeStart(node);
|
|
47283
|
-
if (suspensionStart !== null && suspensionStart < boundaryStart) {
|
|
47284
|
-
hasSuspension = true;
|
|
47285
|
-
return false;
|
|
47286
|
-
}
|
|
47287
|
-
});
|
|
47288
|
-
return hasSuspension;
|
|
47289
|
-
};
|
|
47290
|
-
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47291
|
-
let enclosingFunction = findEnclosingFunction(functionNode);
|
|
47292
|
-
while (enclosingFunction) {
|
|
47293
|
-
if (enclosingFunction === ancestor) return true;
|
|
47294
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
47295
|
-
}
|
|
47296
|
-
return false;
|
|
47297
|
-
};
|
|
47298
|
-
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 === "??="));
|
|
47299
|
-
const functionInvokesTarget = (callerFunction, targetFunction, context, visitedFunctions, canCrossSuspension = false) => {
|
|
47300
|
-
if (visitedFunctions.has(callerFunction)) return false;
|
|
47301
|
-
visitedFunctions.add(callerFunction);
|
|
47302
|
-
let invokesTarget = false;
|
|
47303
|
-
walkAst(callerFunction, (node) => {
|
|
47304
|
-
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47305
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47306
|
-
if (!isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context) || !canCrossSuspension && hasSuspensionBefore(callerFunction, node, context)) return;
|
|
47307
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47308
|
-
if (calledFunction === targetFunction || calledFunction && functionInvokesTarget(calledFunction, targetFunction, context, visitedFunctions, canCrossSuspension)) {
|
|
47309
|
-
invokesTarget = true;
|
|
47310
|
-
return false;
|
|
47311
|
-
}
|
|
47312
|
-
});
|
|
47313
|
-
return invokesTarget;
|
|
47314
|
-
};
|
|
47315
|
-
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47316
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47317
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47318
|
-
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47319
|
-
let isInvokedBefore = false;
|
|
47320
|
-
walkAst(boundaryFunction, (node) => {
|
|
47321
|
-
if (node !== boundaryFunction && isFunctionLike$2(node)) return false;
|
|
47322
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47323
|
-
const callStart = getRangeStart(node);
|
|
47324
|
-
if (callStart === null || callStart >= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, boundaryFunction, context) || hasSuspensionBefore(boundaryFunction, node, context)) return;
|
|
47325
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47326
|
-
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set())) {
|
|
47327
|
-
isInvokedBefore = true;
|
|
47328
|
-
return false;
|
|
47329
|
-
}
|
|
47330
|
-
});
|
|
47331
|
-
return isInvokedBefore;
|
|
47332
|
-
};
|
|
47333
|
-
const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, context) => {
|
|
47334
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47335
|
-
if (boundaryStart === null) return false;
|
|
47336
|
-
let isInvokedAfter = false;
|
|
47337
|
-
walkAst(callerFunction, (node) => {
|
|
47338
|
-
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47339
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47340
|
-
const callStart = getRangeStart(node);
|
|
47341
|
-
if (callStart === null || callStart <= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context)) return;
|
|
47342
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47343
|
-
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set(), true)) {
|
|
47344
|
-
isInvokedAfter = true;
|
|
47345
|
-
return false;
|
|
47346
|
-
}
|
|
47347
|
-
});
|
|
47348
|
-
return isInvokedAfter;
|
|
47349
|
-
};
|
|
47350
|
-
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47351
|
-
const writeStart = getRangeStart(writeNode);
|
|
47352
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47353
|
-
const writeFunction = findEnclosingFunction(writeNode);
|
|
47354
|
-
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47355
|
-
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47356
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47357
|
-
const renderFunction = deferredExecutionFunction ? findEnclosingFunction(deferredExecutionFunction) : null;
|
|
47358
|
-
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47359
|
-
if (!writeFunction) return writeStart < boundaryStart;
|
|
47360
|
-
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
47361
|
-
if (Boolean(renderFunction && (writeFunction === renderFunction || isFunctionAncestor(writeFunction, renderFunction)))) return true;
|
|
47362
|
-
if (deferredExecutionFunction && boundaryFunction !== deferredExecutionFunction) {
|
|
47363
|
-
if (hasSuspensionBefore(boundaryFunction, boundary, context) && isFunctionInvokedBefore(boundaryFunction, writeNode, context)) return true;
|
|
47364
|
-
return isFunctionInvokedAfter(boundaryFunction, writeNode, writeFunction, context);
|
|
47365
|
-
}
|
|
47366
|
-
return writeStart < boundaryStart;
|
|
47367
|
-
}
|
|
47368
|
-
if (renderFunction && isFunctionAncestor(renderFunction, writeFunction) && !hasSuspensionBefore(writeFunction, writeNode, context) && functionInvokesTarget(renderFunction, writeFunction, context, /* @__PURE__ */ new Set())) return true;
|
|
47369
|
-
return !hasSuspensionBefore(writeFunction, writeNode, context) && isFunctionInvokedBefore(writeFunction, boundary, context);
|
|
47370
|
-
};
|
|
47371
|
-
const getStaticStringValue = (node) => {
|
|
47372
|
-
const unwrappedNode = stripParenExpression(node);
|
|
47373
|
-
if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "string") return unwrappedNode.value;
|
|
47374
|
-
if (isNodeOfType(unwrappedNode, "TemplateLiteral") && unwrappedNode.expressions.length === 0) return getStaticTemplateLiteralValue(unwrappedNode);
|
|
47375
|
-
return null;
|
|
47376
|
-
};
|
|
47377
|
-
const isSameRefetchMember = (target, candidate, context) => {
|
|
47378
|
-
const unwrappedTarget = stripParenExpression(target);
|
|
47379
|
-
const unwrappedCandidate = stripParenExpression(candidate);
|
|
47380
|
-
if (!isNodeOfType(unwrappedTarget, "Identifier") || !isNodeOfType(unwrappedCandidate, "MemberExpression") || !isStaticRefetchMember(unwrappedCandidate)) return false;
|
|
47381
|
-
const candidateTarget = stripParenExpression(unwrappedCandidate.object);
|
|
47382
|
-
if (!isNodeOfType(candidateTarget, "Identifier")) return false;
|
|
47383
|
-
const targetSymbol = resolveConstIdentifierAlias(unwrappedTarget, context.scopes);
|
|
47384
|
-
const candidateSymbol = resolveConstIdentifierAlias(candidateTarget, context.scopes);
|
|
47385
|
-
return Boolean(targetSymbol && candidateSymbol?.id === targetSymbol.id);
|
|
47386
|
-
};
|
|
47387
|
-
const getRefetchMutationTarget = (node, context) => {
|
|
47388
|
-
if (isNodeOfType(node, "MemberExpression") && isStaticRefetchMember(node)) {
|
|
47389
|
-
const parent = node.parent;
|
|
47390
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.left === node && isSameRefetchMember(node.object, parent.right, context)) return null;
|
|
47391
|
-
return isNodeOfType(parent, "AssignmentExpression") && parent.left === node || isNodeOfType(parent, "UpdateExpression") && parent.argument === node || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" ? node.object : null;
|
|
47392
|
-
}
|
|
47393
|
-
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
47394
|
-
const callee = stripParenExpression(node.callee);
|
|
47395
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47396
|
-
const receiver = stripParenExpression(callee.object);
|
|
47397
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || context.scopes.symbolFor(receiver)) return null;
|
|
47398
|
-
const methodName = getStaticPropertyKeyName(callee, { allowComputedString: true });
|
|
47399
|
-
const target = node.arguments[0];
|
|
47400
|
-
if (!target || isNodeOfType(target, "SpreadElement")) return null;
|
|
47401
|
-
if (methodName === "defineProperty") {
|
|
47402
|
-
const propertyKey = node.arguments[1];
|
|
47403
|
-
if (!propertyKey || getStaticStringValue(propertyKey) !== "refetch") return null;
|
|
47404
|
-
const descriptor = node.arguments[2];
|
|
47405
|
-
if (isNodeOfType(descriptor, "ObjectExpression")) {
|
|
47406
|
-
const valueProperty = descriptor.properties.find((property) => isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "value");
|
|
47407
|
-
if (isNodeOfType(valueProperty, "Property") && isSameRefetchMember(target, valueProperty.value, context)) return null;
|
|
47408
|
-
}
|
|
47409
|
-
return target;
|
|
47410
|
-
}
|
|
47411
|
-
if (methodName !== "assign") return null;
|
|
47412
|
-
let finalRefetchValue = null;
|
|
47413
|
-
for (const source of node.arguments.slice(1)) {
|
|
47414
|
-
if (!isNodeOfType(source, "ObjectExpression")) continue;
|
|
47415
|
-
for (const property of source.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "refetch") finalRefetchValue = property.value;
|
|
47416
|
-
}
|
|
47417
|
-
if (!finalRefetchValue || isSameRefetchMember(target, finalRefetchValue, context)) return null;
|
|
47418
|
-
return target;
|
|
47419
|
-
};
|
|
47420
|
-
const hasRefetchMemberWriteBefore = (expression, boundary, context, deferredExecutionFunction) => {
|
|
47421
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
47422
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47423
|
-
const resultSymbol = resolveConstIdentifierAlias(unwrappedExpression, context.scopes);
|
|
47424
|
-
if (!resultSymbol) return false;
|
|
47425
|
-
const program = findProgramRoot(expression);
|
|
47426
|
-
if (!program) return true;
|
|
47427
|
-
if (getRangeStart(boundary) === null) return true;
|
|
47428
|
-
let hasWrite = false;
|
|
47429
|
-
walkAst(program, (node) => {
|
|
47430
|
-
const mutationTarget = getRefetchMutationTarget(node, context);
|
|
47431
|
-
if (!mutationTarget) return;
|
|
47432
|
-
if (!isWriteExecutedBefore(node, boundary, context, deferredExecutionFunction)) return;
|
|
47433
|
-
const object = stripParenExpression(mutationTarget);
|
|
47434
|
-
if (!isNodeOfType(object, "Identifier")) return;
|
|
47435
|
-
if (resolveConstIdentifierAlias(object, context.scopes)?.id === resultSymbol.id) {
|
|
47436
|
-
hasWrite = true;
|
|
47437
|
-
return false;
|
|
47438
|
-
}
|
|
47439
|
-
});
|
|
47440
|
-
return hasWrite;
|
|
47441
|
-
};
|
|
47442
|
-
const isTanstackRefetchExpression = (expression, context, deferredExecutionFunction, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47443
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
47444
|
-
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return isStaticRefetchMember(unwrappedExpression) && isTanstackQueryResult(unwrappedExpression.object, context) && !hasRefetchMemberWriteBefore(unwrappedExpression.object, unwrappedExpression, context, deferredExecutionFunction);
|
|
47445
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47446
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
47447
|
-
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read") || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
47448
|
-
visitedSymbolIds.add(symbol.id);
|
|
47449
|
-
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
47450
|
-
if (isNodeOfType(bindingProperty, "Property") && getStaticPropertyKeyName(bindingProperty, { allowComputedString: true }) === "refetch") {
|
|
47451
|
-
const initializer = symbol.declarationNode.init;
|
|
47452
|
-
return Boolean(initializer && isTanstackQueryResult(initializer, context) && !hasRefetchMemberWriteBefore(initializer, symbol.declarationNode, context, null));
|
|
47453
|
-
}
|
|
47454
|
-
return Boolean(symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer && isTanstackRefetchExpression(symbol.initializer, context, null, visitedSymbolIds));
|
|
47455
|
-
};
|
|
47456
|
-
const isTanstackRefetchCall = (callExpression, context, effectCallback) => {
|
|
47457
|
-
return isTanstackRefetchExpression(callExpression.callee, context, effectCallback);
|
|
47458
|
-
};
|
|
47459
46899
|
const queryNoQueryInEffect = defineRule({
|
|
47460
46900
|
id: "query-no-query-in-effect",
|
|
47461
46901
|
title: "Query refetch inside useEffect",
|
|
@@ -47471,7 +46911,8 @@ const queryNoQueryInEffect = defineRule({
|
|
|
47471
46911
|
walkAst(callback, (child) => {
|
|
47472
46912
|
if (child !== callback && isFunctionLike$2(child) && !effectInvokedFunctions.has(child)) return false;
|
|
47473
46913
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
47474
|
-
|
|
46914
|
+
const callee = child.callee;
|
|
46915
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "refetch" || isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "refetch") context.report({
|
|
47475
46916
|
node: child,
|
|
47476
46917
|
message: "refetch() inside useEffect duplicates work React Query already does, causing extra fetches."
|
|
47477
46918
|
});
|
|
@@ -47480,6 +46921,28 @@ const queryNoQueryInEffect = defineRule({
|
|
|
47480
46921
|
});
|
|
47481
46922
|
//#endregion
|
|
47482
46923
|
//#region src/plugin/rules/tanstack-query/query-no-rest-destructuring.ts
|
|
46924
|
+
const resolveTanstackQueryHookName = (callExpression) => {
|
|
46925
|
+
const callee = callExpression.callee;
|
|
46926
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
46927
|
+
const importBinding = getImportBindingForName(callExpression, callee.name);
|
|
46928
|
+
if (importBinding === null) return TANSTACK_QUERY_HOOKS.has(callee.name) ? callee.name : null;
|
|
46929
|
+
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
46930
|
+
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
46931
|
+
}
|
|
46932
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && TANSTACK_QUERY_HOOKS.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) {
|
|
46933
|
+
const namespaceBinding = getImportBindingForName(callExpression, callee.object.name);
|
|
46934
|
+
if (namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source)) return callee.property.name;
|
|
46935
|
+
}
|
|
46936
|
+
return null;
|
|
46937
|
+
};
|
|
46938
|
+
const resolveHookNameFromInitializer = (initializer, scopes) => {
|
|
46939
|
+
if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
|
|
46940
|
+
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
46941
|
+
const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
|
|
46942
|
+
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
46943
|
+
if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
|
|
46944
|
+
return resolveTanstackQueryHookName(resolvedSymbol.initializer);
|
|
46945
|
+
};
|
|
47483
46946
|
const queryNoRestDestructuring = defineRule({
|
|
47484
46947
|
id: "query-no-rest-destructuring",
|
|
47485
46948
|
title: "Rest destructuring on query result",
|
|
@@ -47491,7 +46954,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
47491
46954
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
47492
46955
|
if (!node.init) return;
|
|
47493
46956
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
47494
|
-
const hookName =
|
|
46957
|
+
const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
|
|
47495
46958
|
if (!hookName) return;
|
|
47496
46959
|
context.report({
|
|
47497
46960
|
node: node.id,
|
|
@@ -47735,7 +47198,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
47735
47198
|
});
|
|
47736
47199
|
//#endregion
|
|
47737
47200
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
47738
|
-
const MESSAGE$
|
|
47201
|
+
const MESSAGE$6 = "This JSX crashes because `React` isn't in scope.";
|
|
47739
47202
|
const reactInJsxScope = defineRule({
|
|
47740
47203
|
id: "react-in-jsx-scope",
|
|
47741
47204
|
title: "React not in scope for JSX",
|
|
@@ -47747,190 +47210,19 @@ const reactInJsxScope = defineRule({
|
|
|
47747
47210
|
if (findVariableInitializer(node, "React")) return;
|
|
47748
47211
|
context.report({
|
|
47749
47212
|
node: node.name,
|
|
47750
|
-
message: MESSAGE$
|
|
47213
|
+
message: MESSAGE$6
|
|
47751
47214
|
});
|
|
47752
47215
|
},
|
|
47753
47216
|
JSXFragment(node) {
|
|
47754
47217
|
if (findVariableInitializer(node, "React")) return;
|
|
47755
47218
|
context.report({
|
|
47756
47219
|
node: node.openingFragment,
|
|
47757
|
-
message: MESSAGE$
|
|
47220
|
+
message: MESSAGE$6
|
|
47758
47221
|
});
|
|
47759
47222
|
}
|
|
47760
47223
|
})
|
|
47761
47224
|
});
|
|
47762
47225
|
//#endregion
|
|
47763
|
-
//#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
|
|
47764
|
-
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.";
|
|
47765
|
-
const REACT_MARKDOWN_MODULE = "react-markdown";
|
|
47766
|
-
const REHYPE_RAW_MODULE = "rehype-raw";
|
|
47767
|
-
const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
|
|
47768
|
-
const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
|
|
47769
|
-
const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
|
|
47770
|
-
const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
|
|
47771
|
-
const DEFAULT_EXPORT_NAMES = new Set(["default"]);
|
|
47772
|
-
const getImportDeclaration = (symbol) => {
|
|
47773
|
-
if (symbol.kind !== "import") return null;
|
|
47774
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
47775
|
-
return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
|
|
47776
|
-
};
|
|
47777
|
-
const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
|
|
47778
|
-
const isDefaultImportSymbol = (symbol, moduleName) => {
|
|
47779
|
-
if (!isImportFromModule(symbol, moduleName)) return false;
|
|
47780
|
-
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
47781
|
-
};
|
|
47782
|
-
const resolveImportedIdentifier = (node, scopes) => {
|
|
47783
|
-
if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
|
|
47784
|
-
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
47785
|
-
return symbol?.kind === "import" ? symbol : null;
|
|
47786
|
-
};
|
|
47787
|
-
const getStaticMemberParts = (node) => {
|
|
47788
|
-
if (isNodeOfType(node, "MemberExpression")) {
|
|
47789
|
-
if (node.computed || !isNodeOfType(node.property, "Identifier")) return null;
|
|
47790
|
-
return {
|
|
47791
|
-
object: node.object,
|
|
47792
|
-
propertyName: node.property.name
|
|
47793
|
-
};
|
|
47794
|
-
}
|
|
47795
|
-
if (isNodeOfType(node, "JSXMemberExpression")) {
|
|
47796
|
-
if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
|
|
47797
|
-
return {
|
|
47798
|
-
object: node.object,
|
|
47799
|
-
propertyName: node.property.name
|
|
47800
|
-
};
|
|
47801
|
-
}
|
|
47802
|
-
return null;
|
|
47803
|
-
};
|
|
47804
|
-
const isNamespaceMemberFromModule = (node, moduleName, memberNames, scopes) => {
|
|
47805
|
-
const memberParts = getStaticMemberParts(node);
|
|
47806
|
-
if (!memberParts || !memberNames.has(memberParts.propertyName)) return false;
|
|
47807
|
-
const namespaceSymbol = resolveImportedIdentifier(memberParts.object, scopes);
|
|
47808
|
-
return Boolean(namespaceSymbol && isImportFromModule(namespaceSymbol, moduleName) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
|
|
47809
|
-
};
|
|
47810
|
-
const isReactMarkdownComponent = (node, scopes) => {
|
|
47811
|
-
if (isNodeOfType(node, "JSXIdentifier")) {
|
|
47812
|
-
const symbol = resolveImportedIdentifier(node, scopes);
|
|
47813
|
-
if (!symbol || !isImportFromModule(symbol, REACT_MARKDOWN_MODULE)) return false;
|
|
47814
|
-
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return true;
|
|
47815
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
47816
|
-
return importedName === "default" || Boolean(importedName && REACT_MARKDOWN_NAMED_EXPORTS.has(importedName));
|
|
47817
|
-
}
|
|
47818
|
-
return isNamespaceMemberFromModule(node, REACT_MARKDOWN_MODULE, REACT_MARKDOWN_NAMESPACE_EXPORTS, scopes);
|
|
47819
|
-
};
|
|
47820
|
-
const isPluginFromModule = (rawNode, moduleName, scopes, visitedSymbolIds) => {
|
|
47821
|
-
const node = stripParenExpression(rawNode);
|
|
47822
|
-
if (isNodeOfType(node, "Identifier")) {
|
|
47823
|
-
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
47824
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
47825
|
-
if (isDefaultImportSymbol(symbol, moduleName)) return true;
|
|
47826
|
-
if (symbol.kind !== "const" || !symbol.initializer) return false;
|
|
47827
|
-
visitedSymbolIds.add(symbol.id);
|
|
47828
|
-
return isPluginFromModule(symbol.initializer, moduleName, scopes, visitedSymbolIds);
|
|
47829
|
-
}
|
|
47830
|
-
if (isNamespaceMemberFromModule(node, moduleName, DEFAULT_EXPORT_NAMES, scopes)) return true;
|
|
47831
|
-
if (!isNodeOfType(node, "ArrayExpression")) return false;
|
|
47832
|
-
for (const element of node.elements) {
|
|
47833
|
-
if (!element || isNodeOfType(element, "SpreadElement")) continue;
|
|
47834
|
-
return isPluginFromModule(element, moduleName, scopes, visitedSymbolIds);
|
|
47835
|
-
}
|
|
47836
|
-
return false;
|
|
47837
|
-
};
|
|
47838
|
-
const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
|
|
47839
|
-
const node = stripParenExpression(rawNode);
|
|
47840
|
-
if (isNodeOfType(node, "Identifier")) {
|
|
47841
|
-
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
47842
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
47843
|
-
visitedSymbolIds.add(symbol.id);
|
|
47844
|
-
return collectPluginEntries(symbol.initializer, scopes, visitedSymbolIds);
|
|
47845
|
-
}
|
|
47846
|
-
if (!isNodeOfType(node, "ArrayExpression")) return null;
|
|
47847
|
-
const entries = [];
|
|
47848
|
-
for (const element of node.elements) {
|
|
47849
|
-
if (!element) continue;
|
|
47850
|
-
if (isNodeOfType(element, "SpreadElement")) {
|
|
47851
|
-
const spreadEntries = collectPluginEntries(element.argument, scopes, new Set(visitedSymbolIds));
|
|
47852
|
-
if (spreadEntries === null) return null;
|
|
47853
|
-
entries.push(...spreadEntries);
|
|
47854
|
-
continue;
|
|
47855
|
-
}
|
|
47856
|
-
entries.push(element);
|
|
47857
|
-
}
|
|
47858
|
-
return entries;
|
|
47859
|
-
};
|
|
47860
|
-
const findEffectiveExplicitAttribute = (attributes, attributeName) => {
|
|
47861
|
-
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
47862
|
-
const attribute = attributes[attributeIndex];
|
|
47863
|
-
if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
|
|
47864
|
-
if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
|
|
47865
|
-
}
|
|
47866
|
-
return null;
|
|
47867
|
-
};
|
|
47868
|
-
const getAttributeExpression = (attribute) => {
|
|
47869
|
-
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
47870
|
-
return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
|
|
47871
|
-
};
|
|
47872
|
-
const isDomPurifyNamespace = (node, scopes) => {
|
|
47873
|
-
const symbol = resolveImportedIdentifier(node, scopes);
|
|
47874
|
-
if (!symbol) return false;
|
|
47875
|
-
const importDeclaration = getImportDeclaration(symbol);
|
|
47876
|
-
if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
|
|
47877
|
-
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
47878
|
-
};
|
|
47879
|
-
const isDomPurifySanitizeCall = (node, scopes) => {
|
|
47880
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
47881
|
-
const memberParts = getStaticMemberParts(stripParenExpression(node.callee));
|
|
47882
|
-
return Boolean(memberParts && memberParts.propertyName === "sanitize" && isDomPurifyNamespace(memberParts.object, scopes));
|
|
47883
|
-
};
|
|
47884
|
-
const isStaticOrSanitizedMarkdownExpression = (rawNode, scopes, visitedSymbolIds) => {
|
|
47885
|
-
const node = stripParenExpression(rawNode);
|
|
47886
|
-
if (isNodeOfType(node, "Literal")) return true;
|
|
47887
|
-
if (isNodeOfType(node, "TemplateLiteral")) return node.expressions.every((expression) => isStaticOrSanitizedMarkdownExpression(expression, scopes, new Set(visitedSymbolIds)));
|
|
47888
|
-
if (isDomPurifySanitizeCall(node, scopes)) return true;
|
|
47889
|
-
if (isNodeOfType(node, "ConditionalExpression")) return isStaticOrSanitizedMarkdownExpression(node.consequent, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.alternate, scopes, new Set(visitedSymbolIds));
|
|
47890
|
-
if (isNodeOfType(node, "BinaryExpression") && node.operator === "+") return isStaticOrSanitizedMarkdownExpression(node.left, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.right, scopes, new Set(visitedSymbolIds));
|
|
47891
|
-
if (!isNodeOfType(node, "Identifier")) return false;
|
|
47892
|
-
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
47893
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
47894
|
-
visitedSymbolIds.add(symbol.id);
|
|
47895
|
-
return isStaticOrSanitizedMarkdownExpression(symbol.initializer, scopes, visitedSymbolIds);
|
|
47896
|
-
};
|
|
47897
|
-
const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
|
|
47898
|
-
const jsxElement = openingElement.parent;
|
|
47899
|
-
if (!isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
47900
|
-
const meaningfulChildren = jsxElement.children.filter((child) => !isNodeOfType(child, "JSXText") || child.value.trim().length > 0);
|
|
47901
|
-
if (meaningfulChildren.length > 0) return meaningfulChildren.some((child) => {
|
|
47902
|
-
if (isNodeOfType(child, "JSXText")) return false;
|
|
47903
|
-
if (!isNodeOfType(child, "JSXExpressionContainer")) return true;
|
|
47904
|
-
if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
|
|
47905
|
-
return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
|
|
47906
|
-
});
|
|
47907
|
-
const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
|
|
47908
|
-
if (!childrenAttribute) return false;
|
|
47909
|
-
const childrenExpression = getAttributeExpression(childrenAttribute);
|
|
47910
|
-
return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
|
|
47911
|
-
};
|
|
47912
|
-
const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
47913
|
-
id: "react-markdown-unsanitized-raw-html",
|
|
47914
|
-
title: "Unsanitized raw HTML in React Markdown",
|
|
47915
|
-
severity: "warn",
|
|
47916
|
-
recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
|
|
47917
|
-
create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
|
|
47918
|
-
if (!isReactMarkdownComponent(node.name, context.scopes)) return;
|
|
47919
|
-
const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
|
|
47920
|
-
if (!pluginsAttribute) return;
|
|
47921
|
-
const pluginsExpression = getAttributeExpression(pluginsAttribute);
|
|
47922
|
-
if (!pluginsExpression) return;
|
|
47923
|
-
const pluginEntries = collectPluginEntries(pluginsExpression, context.scopes, /* @__PURE__ */ new Set());
|
|
47924
|
-
if (pluginEntries === null) return;
|
|
47925
|
-
if (!pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_RAW_MODULE, context.scopes, /* @__PURE__ */ new Set()))) return;
|
|
47926
|
-
if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
|
|
47927
|
-
context.report({
|
|
47928
|
-
node,
|
|
47929
|
-
message: MESSAGE$6
|
|
47930
|
-
});
|
|
47931
|
-
} }))
|
|
47932
|
-
});
|
|
47933
|
-
//#endregion
|
|
47934
47226
|
//#region src/plugin/utils/collect-react-redux-selector-aliases.ts
|
|
47935
47227
|
const REACT_REDUX_MODULE = "react-redux";
|
|
47936
47228
|
const collectReactReduxSelectorAliases = (programRoot) => {
|
|
@@ -56358,6 +55650,22 @@ const isInsideClassComponent = (node) => {
|
|
|
56358
55650
|
}
|
|
56359
55651
|
return false;
|
|
56360
55652
|
};
|
|
55653
|
+
const hasShortCircuitAncestor = (descendant, ancestor) => {
|
|
55654
|
+
let current = descendant.parent;
|
|
55655
|
+
while (current && current !== ancestor) {
|
|
55656
|
+
if (isNodeOfType(current, "ConditionalExpression") && (isWithinRange(descendant, current.consequent) || isWithinRange(descendant, current.alternate))) return true;
|
|
55657
|
+
if (isNodeOfType(current, "LogicalExpression") && (current.operator === "&&" || current.operator === "||" || current.operator === "??") && isWithinRange(descendant, current.right)) return true;
|
|
55658
|
+
if (isNodeOfType(current, "AssignmentPattern") && isWithinRange(descendant, current.right)) return true;
|
|
55659
|
+
current = current.parent ?? null;
|
|
55660
|
+
}
|
|
55661
|
+
return false;
|
|
55662
|
+
};
|
|
55663
|
+
const isWithinRange = (descendant, sibling) => {
|
|
55664
|
+
const descendantSpan = descendant;
|
|
55665
|
+
const siblingSpan = sibling;
|
|
55666
|
+
if (typeof descendantSpan.start !== "number" || typeof siblingSpan.start !== "number" || typeof siblingSpan.end !== "number") return false;
|
|
55667
|
+
return descendantSpan.start >= siblingSpan.start && (descendantSpan.end ?? siblingSpan.end) <= siblingSpan.end;
|
|
55668
|
+
};
|
|
56361
55669
|
const isInsideTry = (descendant, ancestor) => {
|
|
56362
55670
|
let current = descendant.parent;
|
|
56363
55671
|
while (current && current !== ancestor) {
|
|
@@ -56549,7 +55857,7 @@ const rulesOfHooks = defineRule({
|
|
|
56549
55857
|
});
|
|
56550
55858
|
return;
|
|
56551
55859
|
}
|
|
56552
|
-
if (
|
|
55860
|
+
if (hasShortCircuitAncestor(node, enclosing.node)) {
|
|
56553
55861
|
context.report({
|
|
56554
55862
|
node: node.callee,
|
|
56555
55863
|
message: buildConditionalMessage(hookName)
|
|
@@ -63164,17 +62472,6 @@ const reactDoctorRules = [
|
|
|
63164
62472
|
requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
|
|
63165
62473
|
}
|
|
63166
62474
|
},
|
|
63167
|
-
{
|
|
63168
|
-
key: "react-doctor/react-markdown-unsanitized-raw-html",
|
|
63169
|
-
id: "react-markdown-unsanitized-raw-html",
|
|
63170
|
-
source: "react-doctor",
|
|
63171
|
-
originallyExternal: false,
|
|
63172
|
-
rule: {
|
|
63173
|
-
...reactMarkdownUnsanitizedRawHtml,
|
|
63174
|
-
framework: "global",
|
|
63175
|
-
category: "Security"
|
|
63176
|
-
}
|
|
63177
|
-
},
|
|
63178
62475
|
{
|
|
63179
62476
|
key: "react-doctor/redux-useselector-inline-derivation",
|
|
63180
62477
|
id: "redux-useselector-inline-derivation",
|