oxlint-plugin-react-doctor 0.7.7-dev.c79c897 → 0.7.7-dev.ce4179b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +46 -0
- package/dist/index.js +1731 -718
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,13 @@ import { analyze } from "eslint-scope";
|
|
|
7
7
|
//#region src/plugin/utils/is-node-of-type.ts
|
|
8
8
|
const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
|
|
9
9
|
//#endregion
|
|
10
|
+
//#region src/plugin/utils/is-type-only-import.ts
|
|
11
|
+
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
12
|
+
if (!specifiers || specifiers.length === 0) return false;
|
|
13
|
+
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
14
|
+
};
|
|
15
|
+
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
16
|
+
//#endregion
|
|
10
17
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
11
18
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
12
19
|
"solid-js",
|
|
@@ -21,17 +28,33 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
|
21
28
|
"vidode"
|
|
22
29
|
]);
|
|
23
30
|
const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
|
|
31
|
+
const REACT_JSX_DIALECT_PACKAGE_PREFIXES = [
|
|
32
|
+
"react",
|
|
33
|
+
"react-dom",
|
|
34
|
+
"preact"
|
|
35
|
+
];
|
|
24
36
|
const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
|
|
25
|
-
const
|
|
37
|
+
const collectJsxRuntimeImports = (program) => {
|
|
38
|
+
let hasNonReactRuntime = false;
|
|
39
|
+
let hasReactRuntime = false;
|
|
26
40
|
for (const statement of program.body) {
|
|
27
41
|
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
28
|
-
const
|
|
42
|
+
const importDeclaration = statement;
|
|
43
|
+
if (isTypeOnlyImport(importDeclaration)) continue;
|
|
44
|
+
const source = importDeclaration.source;
|
|
29
45
|
const value = source && typeof source.value === "string" ? source.value : null;
|
|
30
46
|
if (!value) continue;
|
|
31
|
-
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value))
|
|
32
|
-
if (startsWithAny(value,
|
|
47
|
+
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value) || startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasNonReactRuntime = true;
|
|
48
|
+
if (startsWithAny(value, REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasReactRuntime = true;
|
|
33
49
|
}
|
|
34
|
-
return
|
|
50
|
+
return {
|
|
51
|
+
hasNonReactRuntime,
|
|
52
|
+
hasReactRuntime
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const fileImportsNonReactJsxDialect = (program) => {
|
|
56
|
+
const runtimeImports = collectJsxRuntimeImports(program);
|
|
57
|
+
return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
35
58
|
};
|
|
36
59
|
const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
|
|
37
60
|
for (const attribute of openingNode.attributes) {
|
|
@@ -244,6 +267,7 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
|
|
|
244
267
|
const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
245
268
|
const innerVisitors = create(context);
|
|
246
269
|
let fileIsNonReactJsx = false;
|
|
270
|
+
let fileImportsReactRuntime = false;
|
|
247
271
|
const wrappedVisitors = {};
|
|
248
272
|
for (const [key, visitor] of Object.entries(innerVisitors)) {
|
|
249
273
|
if (typeof visitor !== "function") {
|
|
@@ -256,14 +280,16 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
256
280
|
}
|
|
257
281
|
if (key === "Program") {
|
|
258
282
|
wrappedVisitors.Program = (node) => {
|
|
259
|
-
|
|
283
|
+
const runtimeImports = collectJsxRuntimeImports(node);
|
|
284
|
+
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
285
|
+
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
260
286
|
visitor(node);
|
|
261
287
|
};
|
|
262
288
|
continue;
|
|
263
289
|
}
|
|
264
290
|
if (key === "JSXOpeningElement") {
|
|
265
291
|
wrappedVisitors.JSXOpeningElement = (node) => {
|
|
266
|
-
if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
292
|
+
if (!fileImportsReactRuntime && !fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
267
293
|
if (fileIsNonReactJsx) return;
|
|
268
294
|
visitor(node);
|
|
269
295
|
};
|
|
@@ -275,7 +301,9 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
275
301
|
};
|
|
276
302
|
}
|
|
277
303
|
if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
|
|
278
|
-
|
|
304
|
+
const runtimeImports = collectJsxRuntimeImports(node);
|
|
305
|
+
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
306
|
+
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
279
307
|
};
|
|
280
308
|
return wrappedVisitors;
|
|
281
309
|
});
|
|
@@ -2312,7 +2340,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2312
2340
|
});
|
|
2313
2341
|
//#endregion
|
|
2314
2342
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
2315
|
-
const MESSAGE$
|
|
2343
|
+
const MESSAGE$62 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
2316
2344
|
const isTransComponentsTemplate = (node) => {
|
|
2317
2345
|
let current = node.parent;
|
|
2318
2346
|
while (current) {
|
|
@@ -2348,7 +2376,7 @@ const anchorHasContent = defineRule({
|
|
|
2348
2376
|
if (isTransComponentsTemplate(node)) return;
|
|
2349
2377
|
context.report({
|
|
2350
2378
|
node: opening.name,
|
|
2351
|
-
message: MESSAGE$
|
|
2379
|
+
message: MESSAGE$62
|
|
2352
2380
|
});
|
|
2353
2381
|
} };
|
|
2354
2382
|
}
|
|
@@ -2780,7 +2808,7 @@ const parseJsxValue = (value) => {
|
|
|
2780
2808
|
};
|
|
2781
2809
|
//#endregion
|
|
2782
2810
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2783
|
-
const MESSAGE$
|
|
2811
|
+
const MESSAGE$61 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2784
2812
|
const mayBeContentEditable = (node) => {
|
|
2785
2813
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
|
|
2786
2814
|
if (!attribute) return false;
|
|
@@ -2811,7 +2839,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2811
2839
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2812
2840
|
context.report({
|
|
2813
2841
|
node: node.name,
|
|
2814
|
-
message: MESSAGE$
|
|
2842
|
+
message: MESSAGE$61
|
|
2815
2843
|
});
|
|
2816
2844
|
return;
|
|
2817
2845
|
}
|
|
@@ -2819,7 +2847,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2819
2847
|
if (mayBeContentEditable(node)) return;
|
|
2820
2848
|
context.report({
|
|
2821
2849
|
node: node.name,
|
|
2822
|
-
message: MESSAGE$
|
|
2850
|
+
message: MESSAGE$61
|
|
2823
2851
|
});
|
|
2824
2852
|
} })
|
|
2825
2853
|
});
|
|
@@ -4314,6 +4342,397 @@ const findTransparentExpressionRoot = (node) => {
|
|
|
4314
4342
|
return current;
|
|
4315
4343
|
};
|
|
4316
4344
|
//#endregion
|
|
4345
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
4346
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
4347
|
+
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
|
|
4348
|
+
const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
|
|
4349
|
+
if (node.computed) {
|
|
4350
|
+
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
4351
|
+
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
4352
|
+
return null;
|
|
4353
|
+
}
|
|
4354
|
+
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
4355
|
+
if (isNodeOfType(key, "Literal")) {
|
|
4356
|
+
if (typeof key.value === "string") return key.value;
|
|
4357
|
+
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
4358
|
+
}
|
|
4359
|
+
return null;
|
|
4360
|
+
};
|
|
4361
|
+
//#endregion
|
|
4362
|
+
//#region src/plugin/utils/get-destructured-binding-property-name.ts
|
|
4363
|
+
const getDestructuredBindingPropertyName = (bindingIdentifier) => {
|
|
4364
|
+
let bindingNode = bindingIdentifier;
|
|
4365
|
+
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
4366
|
+
const property = bindingNode.parent;
|
|
4367
|
+
if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
4368
|
+
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
4369
|
+
};
|
|
4370
|
+
//#endregion
|
|
4371
|
+
//#region src/plugin/utils/get-static-property-name.ts
|
|
4372
|
+
const getStaticPropertyName = (memberExpression) => {
|
|
4373
|
+
const property = memberExpression.property;
|
|
4374
|
+
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
4375
|
+
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
4376
|
+
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
4377
|
+
return null;
|
|
4378
|
+
};
|
|
4379
|
+
//#endregion
|
|
4380
|
+
//#region src/plugin/utils/has-static-property-write-before.ts
|
|
4381
|
+
const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
4382
|
+
const potentiallyAliasedSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
4383
|
+
const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
|
|
4384
|
+
"CatchClause",
|
|
4385
|
+
"ConditionalExpression",
|
|
4386
|
+
"DoWhileStatement",
|
|
4387
|
+
"ForInStatement",
|
|
4388
|
+
"ForOfStatement",
|
|
4389
|
+
"ForStatement",
|
|
4390
|
+
"IfStatement",
|
|
4391
|
+
"LogicalExpression",
|
|
4392
|
+
"SwitchCase",
|
|
4393
|
+
"SwitchStatement",
|
|
4394
|
+
"TryStatement",
|
|
4395
|
+
"WhileStatement"
|
|
4396
|
+
]);
|
|
4397
|
+
const getResolvedStaticPropertyName = (memberExpression, scopes) => {
|
|
4398
|
+
if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
|
|
4399
|
+
const directPropertyName = getStaticPropertyName(memberExpression);
|
|
4400
|
+
if (directPropertyName || !memberExpression.computed) return directPropertyName;
|
|
4401
|
+
const property = stripParenExpression(memberExpression.property);
|
|
4402
|
+
if (!isNodeOfType(property, "Identifier")) return null;
|
|
4403
|
+
const propertySymbol = resolveConstIdentifierAlias(property, scopes);
|
|
4404
|
+
const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
|
|
4405
|
+
return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
|
|
4406
|
+
};
|
|
4407
|
+
const collectScopeSymbols = (scope, symbols) => {
|
|
4408
|
+
symbols.push(...scope.symbols);
|
|
4409
|
+
for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
|
|
4410
|
+
};
|
|
4411
|
+
const getEquivalentSymbols = (identifier, scopes) => {
|
|
4412
|
+
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
4413
|
+
if (!rootSymbol) return [];
|
|
4414
|
+
let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
|
|
4415
|
+
if (!symbolsByRootId) {
|
|
4416
|
+
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
4417
|
+
equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
4418
|
+
}
|
|
4419
|
+
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
4420
|
+
if (cachedSymbols) return cachedSymbols;
|
|
4421
|
+
const allSymbols = [];
|
|
4422
|
+
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
4423
|
+
const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
|
|
4424
|
+
symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
|
|
4425
|
+
return equivalentSymbols;
|
|
4426
|
+
};
|
|
4427
|
+
const getDirectAliasSourceSymbol = (expression, scopes) => {
|
|
4428
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
4429
|
+
return isNodeOfType(unwrappedExpression, "Identifier") ? scopes.symbolFor(unwrappedExpression) : null;
|
|
4430
|
+
};
|
|
4431
|
+
const getDirectAssignmentSourceSymbol = (identifier, scopes) => {
|
|
4432
|
+
const assignmentTarget = findTransparentExpressionRoot(identifier);
|
|
4433
|
+
const parent = assignmentTarget.parent;
|
|
4434
|
+
if (!parent || !isNodeOfType(parent, "AssignmentExpression") || parent.operator !== "=" || parent.left !== assignmentTarget) return null;
|
|
4435
|
+
return getDirectAliasSourceSymbol(parent.right, scopes);
|
|
4436
|
+
};
|
|
4437
|
+
const isDirectAliasSourceReference = (identifier) => {
|
|
4438
|
+
const aliasSource = findTransparentExpressionRoot(identifier);
|
|
4439
|
+
const parent = aliasSource.parent;
|
|
4440
|
+
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === aliasSource && (isNodeOfType(parent.id, "Identifier") || isNodeOfType(parent.id, "ObjectPattern"))) return true;
|
|
4441
|
+
return Boolean(parent && isNodeOfType(parent, "AssignmentExpression") && parent.operator === "=" && parent.right === aliasSource && isNodeOfType(stripParenExpression(parent.left), "Identifier"));
|
|
4442
|
+
};
|
|
4443
|
+
const isDirectAliasOfKnownSymbol = (symbol, knownSymbolIds, scopes) => {
|
|
4444
|
+
if (symbol.initializer && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && symbol.declarationNode.id === symbol.bindingIdentifier) {
|
|
4445
|
+
const initializerSymbol = getDirectAliasSourceSymbol(symbol.initializer, scopes);
|
|
4446
|
+
if (initializerSymbol && knownSymbolIds.has(initializerSymbol.id)) return true;
|
|
4447
|
+
}
|
|
4448
|
+
return symbol.references.some((reference) => {
|
|
4449
|
+
const assignmentSourceSymbol = getDirectAssignmentSourceSymbol(reference.identifier, scopes);
|
|
4450
|
+
return Boolean(assignmentSourceSymbol && knownSymbolIds.has(assignmentSourceSymbol.id));
|
|
4451
|
+
});
|
|
4452
|
+
};
|
|
4453
|
+
const getPotentiallyAliasedSymbols = (identifier, scopes) => {
|
|
4454
|
+
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
4455
|
+
if (!rootSymbol) return [];
|
|
4456
|
+
let symbolsByRootId = potentiallyAliasedSymbolsByAnalysis.get(scopes);
|
|
4457
|
+
if (!symbolsByRootId) {
|
|
4458
|
+
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
4459
|
+
potentiallyAliasedSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
4460
|
+
}
|
|
4461
|
+
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
4462
|
+
if (cachedSymbols) return cachedSymbols;
|
|
4463
|
+
const allSymbols = [];
|
|
4464
|
+
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
4465
|
+
const aliasedSymbolIds = new Set([rootSymbol.id]);
|
|
4466
|
+
let didAddAlias = true;
|
|
4467
|
+
while (didAddAlias) {
|
|
4468
|
+
didAddAlias = false;
|
|
4469
|
+
for (const symbol of allSymbols) {
|
|
4470
|
+
if (aliasedSymbolIds.has(symbol.id)) continue;
|
|
4471
|
+
if (!isDirectAliasOfKnownSymbol(symbol, aliasedSymbolIds, scopes)) continue;
|
|
4472
|
+
aliasedSymbolIds.add(symbol.id);
|
|
4473
|
+
didAddAlias = true;
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
const aliasedSymbols = allSymbols.filter((symbol) => aliasedSymbolIds.has(symbol.id));
|
|
4477
|
+
symbolsByRootId.set(rootSymbol.id, aliasedSymbols);
|
|
4478
|
+
return aliasedSymbols;
|
|
4479
|
+
};
|
|
4480
|
+
const findExecutionBoundary = (node) => {
|
|
4481
|
+
let current = node;
|
|
4482
|
+
while (current) {
|
|
4483
|
+
if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
|
|
4484
|
+
current = current.parent ?? null;
|
|
4485
|
+
}
|
|
4486
|
+
return null;
|
|
4487
|
+
};
|
|
4488
|
+
const isOnUnconditionalPath = (node, boundary) => {
|
|
4489
|
+
let current = node.parent ?? null;
|
|
4490
|
+
while (current && current !== boundary) {
|
|
4491
|
+
if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
|
|
4492
|
+
current = current.parent ?? null;
|
|
4493
|
+
}
|
|
4494
|
+
return current === boundary;
|
|
4495
|
+
};
|
|
4496
|
+
const findFunctionBindingIdentifier = (functionNode) => {
|
|
4497
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
|
|
4498
|
+
const parent = functionNode.parent;
|
|
4499
|
+
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
4500
|
+
return null;
|
|
4501
|
+
};
|
|
4502
|
+
const findDirectCall = (identifier) => {
|
|
4503
|
+
let callee = identifier;
|
|
4504
|
+
let parent = callee.parent;
|
|
4505
|
+
while (parent && stripParenExpression(parent) === identifier) {
|
|
4506
|
+
callee = parent;
|
|
4507
|
+
parent = callee.parent;
|
|
4508
|
+
}
|
|
4509
|
+
return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
4510
|
+
};
|
|
4511
|
+
const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
4512
|
+
if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
|
|
4513
|
+
visitedFunctionNodes.add(functionNode);
|
|
4514
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
4515
|
+
if (!referenceBoundary) return false;
|
|
4516
|
+
const invocationCalls = [];
|
|
4517
|
+
const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
|
|
4518
|
+
if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
|
|
4519
|
+
const call = findDirectCall(reference.identifier);
|
|
4520
|
+
if (call) invocationCalls.push(call);
|
|
4521
|
+
}
|
|
4522
|
+
else {
|
|
4523
|
+
const call = findDirectCall(functionNode);
|
|
4524
|
+
if (call) invocationCalls.push(call);
|
|
4525
|
+
}
|
|
4526
|
+
return invocationCalls.some((call) => {
|
|
4527
|
+
if (call.range[0] >= referenceNode.range[0]) return false;
|
|
4528
|
+
const callBoundary = findExecutionBoundary(call);
|
|
4529
|
+
if (!callBoundary) return false;
|
|
4530
|
+
if (callBoundary === referenceBoundary) return true;
|
|
4531
|
+
if (!isFunctionLike$2(callBoundary)) return false;
|
|
4532
|
+
return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
|
|
4533
|
+
});
|
|
4534
|
+
};
|
|
4535
|
+
const isMemberWriteTarget = (memberExpression) => {
|
|
4536
|
+
const parent = memberExpression.parent;
|
|
4537
|
+
if (!parent) return false;
|
|
4538
|
+
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
|
|
4539
|
+
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
|
|
4540
|
+
return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
|
|
4541
|
+
};
|
|
4542
|
+
const getMemberWriteTarget = (identifier) => {
|
|
4543
|
+
let parent = identifier.parent;
|
|
4544
|
+
while (parent && stripParenExpression(parent) === identifier) parent = parent.parent;
|
|
4545
|
+
if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== identifier || !isMemberWriteTarget(parent)) return null;
|
|
4546
|
+
return parent;
|
|
4547
|
+
};
|
|
4548
|
+
const getStaticPropertyWriteTarget = (identifier, propertyName, scopes) => {
|
|
4549
|
+
const writeTarget = getMemberWriteTarget(identifier);
|
|
4550
|
+
return writeTarget && getResolvedStaticPropertyName(writeTarget, scopes) === propertyName ? writeTarget : null;
|
|
4551
|
+
};
|
|
4552
|
+
const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
4553
|
+
const writeTarget = getStaticPropertyWriteTarget(reference.identifier, propertyName, scopes);
|
|
4554
|
+
if (!writeTarget) return false;
|
|
4555
|
+
const writeBoundary = findExecutionBoundary(writeTarget);
|
|
4556
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
4557
|
+
if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(writeTarget, writeBoundary)) return false;
|
|
4558
|
+
if (writeBoundary === referenceBoundary) return writeTarget.range[0] < referenceNode.range[0];
|
|
4559
|
+
return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
|
|
4560
|
+
});
|
|
4561
|
+
const isStableStaticPropertyReference = (identifier) => {
|
|
4562
|
+
if (isDirectAliasSourceReference(identifier)) return true;
|
|
4563
|
+
const identifierRoot = findTransparentExpressionRoot(identifier);
|
|
4564
|
+
const memberExpression = identifierRoot.parent;
|
|
4565
|
+
return Boolean(memberExpression && isNodeOfType(memberExpression, "MemberExpression") && stripParenExpression(memberExpression.object) === identifierRoot);
|
|
4566
|
+
};
|
|
4567
|
+
const hasPossibleStaticPropertyWrite = (identifier, propertyName, scopes) => {
|
|
4568
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4569
|
+
return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => {
|
|
4570
|
+
const writeTarget = getMemberWriteTarget(reference.identifier);
|
|
4571
|
+
if (!writeTarget) return false;
|
|
4572
|
+
const writtenPropertyName = getResolvedStaticPropertyName(writeTarget, scopes);
|
|
4573
|
+
return writtenPropertyName === null || writtenPropertyName === propertyName;
|
|
4574
|
+
}));
|
|
4575
|
+
};
|
|
4576
|
+
const hasPossibleStaticPropertyMutationOrEscape = (identifier, propertyName, scopes) => {
|
|
4577
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4578
|
+
if (hasPossibleStaticPropertyWrite(identifier, propertyName, scopes)) return true;
|
|
4579
|
+
return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => !isStableStaticPropertyReference(reference.identifier)));
|
|
4580
|
+
};
|
|
4581
|
+
const hasPossibleStaticMemberCallWrite = (callExpression, scopes) => {
|
|
4582
|
+
const unwrappedCallExpression = stripParenExpression(callExpression);
|
|
4583
|
+
if (!isNodeOfType(unwrappedCallExpression, "CallExpression")) return false;
|
|
4584
|
+
const callee = stripParenExpression(unwrappedCallExpression.callee);
|
|
4585
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
4586
|
+
const propertyName = getStaticPropertyName(callee);
|
|
4587
|
+
if (propertyName === null) return false;
|
|
4588
|
+
const receiver = stripParenExpression(callee.object);
|
|
4589
|
+
return isNodeOfType(receiver, "Identifier") && hasPossibleStaticPropertyWrite(receiver, propertyName, scopes);
|
|
4590
|
+
};
|
|
4591
|
+
const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
4592
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4593
|
+
return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
|
|
4594
|
+
};
|
|
4595
|
+
//#endregion
|
|
4596
|
+
//#region src/plugin/utils/find-enclosing-function.ts
|
|
4597
|
+
const findEnclosingFunction$1 = (node) => {
|
|
4598
|
+
let cursor = node.parent;
|
|
4599
|
+
while (cursor) {
|
|
4600
|
+
if (isFunctionLike$2(cursor)) return cursor;
|
|
4601
|
+
cursor = cursor.parent ?? null;
|
|
4602
|
+
}
|
|
4603
|
+
return null;
|
|
4604
|
+
};
|
|
4605
|
+
//#endregion
|
|
4606
|
+
//#region src/plugin/utils/has-symbol-write-before.ts
|
|
4607
|
+
const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
4608
|
+
if (reference.flag === "read") return false;
|
|
4609
|
+
const writeFunction = findEnclosingFunction$1(reference.identifier);
|
|
4610
|
+
if (writeFunction === findEnclosingFunction$1(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
|
|
4611
|
+
if (!writeFunction) return true;
|
|
4612
|
+
return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
|
|
4613
|
+
});
|
|
4614
|
+
//#endregion
|
|
4615
|
+
//#region src/plugin/utils/get-order-independent-local-function.ts
|
|
4616
|
+
const COMMUTATIVE_COMPOUND_ASSIGNMENT_OPERATORS = new Set([
|
|
4617
|
+
"+=",
|
|
4618
|
+
"-=",
|
|
4619
|
+
"*=",
|
|
4620
|
+
"/=",
|
|
4621
|
+
"%=",
|
|
4622
|
+
"&=",
|
|
4623
|
+
"^=",
|
|
4624
|
+
"|="
|
|
4625
|
+
]);
|
|
4626
|
+
const isPureParameterExpression = (expression, parameterNames) => {
|
|
4627
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
4628
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) return true;
|
|
4629
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) return parameterNames.has(unwrappedExpression.name);
|
|
4630
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression") || isNodeOfType(unwrappedExpression, "LogicalExpression")) return isPureParameterExpression(unwrappedExpression.left, parameterNames) && isPureParameterExpression(unwrappedExpression.right, parameterNames);
|
|
4631
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression")) return unwrappedExpression.operator !== "delete" && isPureParameterExpression(unwrappedExpression.argument, parameterNames);
|
|
4632
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isPureParameterExpression(unwrappedExpression.test, parameterNames) && isPureParameterExpression(unwrappedExpression.consequent, parameterNames) && isPureParameterExpression(unwrappedExpression.alternate, parameterNames);
|
|
4633
|
+
if (isNodeOfType(unwrappedExpression, "TemplateLiteral")) return unwrappedExpression.expressions.every((nestedExpression) => isPureParameterExpression(nestedExpression, parameterNames));
|
|
4634
|
+
return false;
|
|
4635
|
+
};
|
|
4636
|
+
const isOrderIndependentPromiseResolveCall = (expression, parameterNames, scopes) => {
|
|
4637
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
4638
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
|
|
4639
|
+
if (!unwrappedExpression.arguments.every((argument) => !isNodeOfType(argument, "SpreadElement") && isPureParameterExpression(argument, parameterNames))) return false;
|
|
4640
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
4641
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
4642
|
+
if (getStaticPropertyName(callee) !== "resolve") return false;
|
|
4643
|
+
const receiver = stripParenExpression(callee.object);
|
|
4644
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && scopes.isGlobalReference(receiver);
|
|
4645
|
+
};
|
|
4646
|
+
const isHarmlessPromiseResolveAwait = (statement, parameterNames, scopes) => {
|
|
4647
|
+
if (!isNodeOfType(statement, "ExpressionStatement") || !isNodeOfType(statement.expression, "AwaitExpression")) return false;
|
|
4648
|
+
return isOrderIndependentPromiseResolveCall(statement.expression.argument, parameterNames, scopes);
|
|
4649
|
+
};
|
|
4650
|
+
const isCommutativeParameterMutation = (statement, parameterNames) => {
|
|
4651
|
+
if (!isNodeOfType(statement, "ExpressionStatement") || !isNodeOfType(statement.expression, "AssignmentExpression") || !COMMUTATIVE_COMPOUND_ASSIGNMENT_OPERATORS.has(statement.expression.operator)) return false;
|
|
4652
|
+
const mutationTarget = stripParenExpression(statement.expression.left);
|
|
4653
|
+
if (!isNodeOfType(mutationTarget, "MemberExpression")) return false;
|
|
4654
|
+
if (getStaticPropertyName(mutationTarget) === null) return false;
|
|
4655
|
+
const receiver = stripParenExpression(mutationTarget.object);
|
|
4656
|
+
return isNodeOfType(receiver, "Identifier") && parameterNames.has(receiver.name) && isNodeOfType(stripParenExpression(statement.expression.right), "Literal");
|
|
4657
|
+
};
|
|
4658
|
+
const isOrderIndependentFunction = (functionNode, scopes) => {
|
|
4659
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
4660
|
+
const parameterNames = /* @__PURE__ */ new Set();
|
|
4661
|
+
for (const parameter of functionNode.params) {
|
|
4662
|
+
if (!isNodeOfType(parameter, "Identifier")) return false;
|
|
4663
|
+
parameterNames.add(parameter.name);
|
|
4664
|
+
}
|
|
4665
|
+
if (!functionNode.async) {
|
|
4666
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isOrderIndependentPromiseResolveCall(functionNode.body, parameterNames, scopes);
|
|
4667
|
+
if (functionNode.body.body.length !== 1) return false;
|
|
4668
|
+
const [returnStatement] = functionNode.body.body;
|
|
4669
|
+
return Boolean(isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument && isOrderIndependentPromiseResolveCall(returnStatement.argument, parameterNames, scopes));
|
|
4670
|
+
}
|
|
4671
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isPureParameterExpression(functionNode.body, parameterNames);
|
|
4672
|
+
const statements = functionNode.body.body;
|
|
4673
|
+
for (let statementIndex = 0; statementIndex < statements.length; statementIndex++) {
|
|
4674
|
+
const statement = statements[statementIndex];
|
|
4675
|
+
const isTerminalStatement = statementIndex === statements.length - 1;
|
|
4676
|
+
if (isHarmlessPromiseResolveAwait(statement, parameterNames, scopes)) continue;
|
|
4677
|
+
if (isNodeOfType(statement, "ExpressionStatement") && isPureParameterExpression(statement.expression, parameterNames)) continue;
|
|
4678
|
+
if (isCommutativeParameterMutation(statement, parameterNames)) return isTerminalStatement;
|
|
4679
|
+
if (!isNodeOfType(statement, "ReturnStatement") || !isTerminalStatement) return false;
|
|
4680
|
+
return !statement.argument || isPureParameterExpression(statement.argument, parameterNames) || isOrderIndependentPromiseResolveCall(statement.argument, parameterNames, scopes);
|
|
4681
|
+
}
|
|
4682
|
+
return true;
|
|
4683
|
+
};
|
|
4684
|
+
const getObjectPropertyName = (property) => {
|
|
4685
|
+
if (!isNodeOfType(property, "Property")) return null;
|
|
4686
|
+
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
4687
|
+
};
|
|
4688
|
+
const resolveOrderIndependentObjectPropertyFunction = (objectExpression, propertyName, callExpression, scopes, visitedSymbolIds) => {
|
|
4689
|
+
const unwrappedObject = stripParenExpression(objectExpression);
|
|
4690
|
+
if (isNodeOfType(unwrappedObject, "Identifier")) {
|
|
4691
|
+
const symbol = scopes.symbolFor(unwrappedObject);
|
|
4692
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, callExpression, scopes) || hasPossibleStaticPropertyMutationOrEscape(unwrappedObject, propertyName, scopes)) return null;
|
|
4693
|
+
visitedSymbolIds.add(symbol.id);
|
|
4694
|
+
return resolveOrderIndependentObjectPropertyFunction(symbol.initializer, propertyName, callExpression, scopes, visitedSymbolIds);
|
|
4695
|
+
}
|
|
4696
|
+
if (!isNodeOfType(unwrappedObject, "ObjectExpression")) return null;
|
|
4697
|
+
let matchingProperty = null;
|
|
4698
|
+
for (const property of unwrappedObject.properties) {
|
|
4699
|
+
if (!isNodeOfType(property, "Property")) return null;
|
|
4700
|
+
if (property.kind !== "init") return null;
|
|
4701
|
+
const candidatePropertyName = getObjectPropertyName(property);
|
|
4702
|
+
if (candidatePropertyName === null) return null;
|
|
4703
|
+
if (candidatePropertyName === propertyName) matchingProperty = property;
|
|
4704
|
+
}
|
|
4705
|
+
if (!matchingProperty || !isNodeOfType(matchingProperty, "Property")) return null;
|
|
4706
|
+
const propertyValue = stripParenExpression(matchingProperty.value);
|
|
4707
|
+
if (isFunctionLike$2(propertyValue)) return isOrderIndependentFunction(propertyValue, scopes) ? propertyValue : null;
|
|
4708
|
+
return resolveOrderIndependentLocalFunction(propertyValue, callExpression, scopes, visitedSymbolIds);
|
|
4709
|
+
};
|
|
4710
|
+
const resolveOrderIndependentLocalFunction = (callee, callExpression, scopes, visitedSymbolIds) => {
|
|
4711
|
+
const unwrappedCallee = stripParenExpression(callee);
|
|
4712
|
+
if (isNodeOfType(unwrappedCallee, "MemberExpression")) {
|
|
4713
|
+
const propertyName = getStaticPropertyName(unwrappedCallee);
|
|
4714
|
+
if (propertyName === null) return null;
|
|
4715
|
+
return resolveOrderIndependentObjectPropertyFunction(stripParenExpression(unwrappedCallee.object), propertyName, callExpression, scopes, visitedSymbolIds);
|
|
4716
|
+
}
|
|
4717
|
+
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
4718
|
+
const symbol = scopes.symbolFor(unwrappedCallee);
|
|
4719
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, callExpression, scopes)) return null;
|
|
4720
|
+
visitedSymbolIds.add(symbol.id);
|
|
4721
|
+
if (!symbol.initializer) return null;
|
|
4722
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
4723
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
4724
|
+
if (destructuredPropertyName !== null) return resolveOrderIndependentObjectPropertyFunction(initializer, destructuredPropertyName, callExpression, scopes, visitedSymbolIds);
|
|
4725
|
+
if (isFunctionLike$2(initializer)) return isOrderIndependentFunction(initializer, scopes) ? initializer : null;
|
|
4726
|
+
if (symbol.kind !== "const") return null;
|
|
4727
|
+
return resolveOrderIndependentLocalFunction(initializer, callExpression, scopes, visitedSymbolIds);
|
|
4728
|
+
};
|
|
4729
|
+
const getOrderIndependentLocalFunction = (callExpression, scopes) => {
|
|
4730
|
+
const unwrappedCall = stripParenExpression(callExpression);
|
|
4731
|
+
if (!isNodeOfType(unwrappedCall, "CallExpression")) return null;
|
|
4732
|
+
if (hasPossibleStaticMemberCallWrite(unwrappedCall, scopes)) return null;
|
|
4733
|
+
return resolveOrderIndependentLocalFunction(unwrappedCall.callee, unwrappedCall, scopes, /* @__PURE__ */ new Set());
|
|
4734
|
+
};
|
|
4735
|
+
//#endregion
|
|
4317
4736
|
//#region src/plugin/utils/is-inline-function-expression.ts
|
|
4318
4737
|
/**
|
|
4319
4738
|
* Type-guard for the two "inline function expression" ESTree forms:
|
|
@@ -4347,13 +4766,15 @@ const isIntentionalSequencingCallee = (callee) => {
|
|
|
4347
4766
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(callee.property.name);
|
|
4348
4767
|
return false;
|
|
4349
4768
|
};
|
|
4350
|
-
const isAwaitingSleepLikeCall = (awaitNode) => {
|
|
4769
|
+
const isAwaitingSleepLikeCall = (awaitNode, context) => {
|
|
4351
4770
|
if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
|
|
4352
4771
|
const argument = awaitNode.argument;
|
|
4353
4772
|
if (!argument) return false;
|
|
4354
4773
|
if (!isNodeOfType(argument, "CallExpression")) return false;
|
|
4774
|
+
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) return false;
|
|
4355
4775
|
return isIntentionalSequencingCallee(argument.callee);
|
|
4356
4776
|
};
|
|
4777
|
+
const isAwaitingPossiblyMutatedMemberCall = (awaitNode, context) => isNodeOfType(awaitNode, "AwaitExpression") && Boolean(awaitNode.argument) && hasPossibleStaticMemberCallWrite(awaitNode.argument, context.scopes);
|
|
4357
4778
|
const PROMISE_CONCURRENCY_METHODS = new Set([
|
|
4358
4779
|
"all",
|
|
4359
4780
|
"allSettled",
|
|
@@ -4396,7 +4817,7 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
|
|
|
4396
4817
|
});
|
|
4397
4818
|
return isWaitLike;
|
|
4398
4819
|
};
|
|
4399
|
-
const isIntentionallySequentialAwait = (awaitNode) => isAwaitingSleepLikeCall(awaitNode) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
|
|
4820
|
+
const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
|
|
4400
4821
|
const collectPatternIdentifiers = (pattern, target) => {
|
|
4401
4822
|
if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
|
|
4402
4823
|
else if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
@@ -4587,12 +5008,12 @@ const getLoopLabelName = (loopNode) => {
|
|
|
4587
5008
|
if (isNodeOfType(parent, "LabeledStatement") && isNodeOfType(parent.label, "Identifier")) return parent.label.name;
|
|
4588
5009
|
return null;
|
|
4589
5010
|
};
|
|
4590
|
-
const loopBodyHasIntentionallySequentialAwait = (block) => {
|
|
5011
|
+
const loopBodyHasIntentionallySequentialAwait = (block, context) => {
|
|
4591
5012
|
let foundIntentional = false;
|
|
4592
5013
|
walkAst(block, (child) => {
|
|
4593
5014
|
if (foundIntentional) return false;
|
|
4594
5015
|
if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
|
|
4595
|
-
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child)) {
|
|
5016
|
+
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child, context)) {
|
|
4596
5017
|
foundIntentional = true;
|
|
4597
5018
|
return false;
|
|
4598
5019
|
}
|
|
@@ -4697,7 +5118,7 @@ const asyncAwaitInLoop = defineRule({
|
|
|
4697
5118
|
const inspectLoop = (loopNode, label) => {
|
|
4698
5119
|
const loopBody = loopNode.body;
|
|
4699
5120
|
if (!loopBody) return;
|
|
4700
|
-
if (loopBodyHasIntentionallySequentialAwait(loopBody)) return;
|
|
5121
|
+
if (loopBodyHasIntentionallySequentialAwait(loopBody, context)) return;
|
|
4701
5122
|
if ((isNodeOfType(loopNode, "WhileStatement") || isNodeOfType(loopNode, "DoWhileStatement")) && isLoopTestDependentOnBodyState(loopNode.test, loopBody)) return;
|
|
4702
5123
|
if (hasLoopCarriedDependency(loopBody)) return;
|
|
4703
5124
|
if (loopBodyHasAwaitDependentEarlyExit(loopBody, getLoopLabelName(loopNode))) return;
|
|
@@ -4957,7 +5378,7 @@ const guardConsequentPerformsSideEffects = (consequent) => {
|
|
|
4957
5378
|
});
|
|
4958
5379
|
return performsSideEffects;
|
|
4959
5380
|
};
|
|
4960
|
-
const findEnclosingFunction
|
|
5381
|
+
const findEnclosingFunction = (node) => {
|
|
4961
5382
|
let ancestor = node.parent;
|
|
4962
5383
|
while (ancestor) {
|
|
4963
5384
|
if (isFunctionLike$2(ancestor)) return ancestor;
|
|
@@ -4970,7 +5391,7 @@ const guardTestReadsReassignedLocal = (test, guardStatement) => {
|
|
|
4970
5391
|
const testIdentifierNames = /* @__PURE__ */ new Set();
|
|
4971
5392
|
collectReferenceIdentifierNames(test, testIdentifierNames);
|
|
4972
5393
|
if (testIdentifierNames.size === 0) return false;
|
|
4973
|
-
const enclosingFunction = findEnclosingFunction
|
|
5394
|
+
const enclosingFunction = findEnclosingFunction(guardStatement);
|
|
4974
5395
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction) || !enclosingFunction.body) return false;
|
|
4975
5396
|
let readsReassignedLocal = false;
|
|
4976
5397
|
walkAst(enclosingFunction.body, (child) => {
|
|
@@ -5141,19 +5562,36 @@ const isIntentionalSequencingAwait = (awaitedCall) => {
|
|
|
5141
5562
|
return getCalleeIdentifierTrail(awaitedCall).some((name) => INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(name));
|
|
5142
5563
|
};
|
|
5143
5564
|
const isBareExpressionAwait = (statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "AwaitExpression");
|
|
5565
|
+
const hasOrderIndependentBareAwaitArguments = (callExpression) => {
|
|
5566
|
+
const unwrappedCallExpression = stripParenExpression(callExpression);
|
|
5567
|
+
if (!isNodeOfType(unwrappedCallExpression, "CallExpression")) return false;
|
|
5568
|
+
return unwrappedCallExpression.arguments.every((argument) => {
|
|
5569
|
+
if (isNodeOfType(argument, "SpreadElement")) return false;
|
|
5570
|
+
const unwrappedArgument = stripParenExpression(argument);
|
|
5571
|
+
return isNodeOfType(unwrappedArgument, "Identifier") || isNodeOfType(unwrappedArgument, "Literal");
|
|
5572
|
+
});
|
|
5573
|
+
};
|
|
5144
5574
|
const isNonCallAwait = (statement) => {
|
|
5145
5575
|
const awaitedExpression = getAwaitedCall(statement);
|
|
5146
5576
|
if (!awaitedExpression) return false;
|
|
5147
5577
|
const stripped = stripParenExpression(awaitedExpression);
|
|
5148
5578
|
return !isNodeOfType(stripped, "CallExpression") && !isNodeOfType(stripped, "NewExpression") && !isNodeOfType(stripped, "ImportExpression");
|
|
5149
5579
|
};
|
|
5150
|
-
const sequenceContainsSerializationSignal = (statements) => {
|
|
5580
|
+
const sequenceContainsSerializationSignal = (statements, context) => {
|
|
5581
|
+
let bareAwaitFunction = null;
|
|
5151
5582
|
for (const statement of statements) {
|
|
5152
|
-
if (isBareExpressionAwait(statement)) return true;
|
|
5153
5583
|
if (isNonCallAwait(statement)) return true;
|
|
5154
5584
|
const awaitedCall = getAwaitedCall(statement);
|
|
5585
|
+
if (awaitedCall && hasPossibleStaticMemberCallWrite(awaitedCall, context.scopes)) return true;
|
|
5586
|
+
const orderIndependentFunction = awaitedCall ? getOrderIndependentLocalFunction(awaitedCall, context.scopes) : null;
|
|
5587
|
+
if (isBareExpressionAwait(statement)) {
|
|
5588
|
+
if (orderIndependentFunction === null) return true;
|
|
5589
|
+
if (!awaitedCall || !hasOrderIndependentBareAwaitArguments(awaitedCall)) return true;
|
|
5590
|
+
if (bareAwaitFunction !== null && bareAwaitFunction !== orderIndependentFunction) return true;
|
|
5591
|
+
bareAwaitFunction = orderIndependentFunction;
|
|
5592
|
+
}
|
|
5155
5593
|
if (isOrderedUiFlowAwait(awaitedCall)) return true;
|
|
5156
|
-
if (isIntentionalSequencingAwait(awaitedCall)) return true;
|
|
5594
|
+
if (isIntentionalSequencingAwait(awaitedCall) && orderIndependentFunction === null) return true;
|
|
5157
5595
|
}
|
|
5158
5596
|
return false;
|
|
5159
5597
|
};
|
|
@@ -5211,7 +5649,7 @@ const asyncParallel = defineRule({
|
|
|
5211
5649
|
const consecutiveAwaitStatements = [];
|
|
5212
5650
|
const flushConsecutiveAwaits = () => {
|
|
5213
5651
|
if (consecutiveAwaitStatements.length >= 3) {
|
|
5214
|
-
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements)) reportIfIndependent(consecutiveAwaitStatements, context);
|
|
5652
|
+
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements, context)) reportIfIndependent(consecutiveAwaitStatements, context);
|
|
5215
5653
|
}
|
|
5216
5654
|
consecutiveAwaitStatements.length = 0;
|
|
5217
5655
|
};
|
|
@@ -5224,7 +5662,7 @@ const asyncParallel = defineRule({
|
|
|
5224
5662
|
});
|
|
5225
5663
|
//#endregion
|
|
5226
5664
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
5227
|
-
const MESSAGE$
|
|
5665
|
+
const MESSAGE$60 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
|
|
5228
5666
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
5229
5667
|
const STORAGE_GLOBALS = new Set([
|
|
5230
5668
|
"window",
|
|
@@ -5289,7 +5727,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5289
5727
|
if (keyString === null || !isAuthCredentialKey(keyString)) return;
|
|
5290
5728
|
context.report({
|
|
5291
5729
|
node,
|
|
5292
|
-
message: MESSAGE$
|
|
5730
|
+
message: MESSAGE$60
|
|
5293
5731
|
});
|
|
5294
5732
|
},
|
|
5295
5733
|
AssignmentExpression(node) {
|
|
@@ -5300,7 +5738,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5300
5738
|
if (!propertyName || !isAuthCredentialKey(propertyName)) return;
|
|
5301
5739
|
context.report({
|
|
5302
5740
|
node: target,
|
|
5303
|
-
message: MESSAGE$
|
|
5741
|
+
message: MESSAGE$60
|
|
5304
5742
|
});
|
|
5305
5743
|
}
|
|
5306
5744
|
}))
|
|
@@ -5439,7 +5877,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
|
|
|
5439
5877
|
const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
|
|
5440
5878
|
const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
|
|
5441
5879
|
const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
|
|
5442
|
-
const MESSAGE$
|
|
5880
|
+
const MESSAGE$59 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
|
|
5443
5881
|
const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
|
|
5444
5882
|
const scanWorkflowContent = (content) => {
|
|
5445
5883
|
const lines = content.split("\n");
|
|
@@ -5484,7 +5922,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5484
5922
|
const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
|
|
5485
5923
|
const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
|
|
5486
5924
|
return [{
|
|
5487
|
-
message: MESSAGE$
|
|
5925
|
+
message: MESSAGE$59,
|
|
5488
5926
|
line: step.startLineIndex + installLineOffset + 1,
|
|
5489
5927
|
column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
|
|
5490
5928
|
}];
|
|
@@ -5494,7 +5932,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5494
5932
|
const scanNonWorkflowConfig = scanByPattern({
|
|
5495
5933
|
shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
|
|
5496
5934
|
pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
|
|
5497
|
-
message: MESSAGE$
|
|
5935
|
+
message: MESSAGE$59
|
|
5498
5936
|
});
|
|
5499
5937
|
const scan = (file) => {
|
|
5500
5938
|
if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
|
|
@@ -5989,21 +6427,6 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5989
6427
|
}
|
|
5990
6428
|
});
|
|
5991
6429
|
//#endregion
|
|
5992
|
-
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5993
|
-
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5994
|
-
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition")) return null;
|
|
5995
|
-
if (node.computed) {
|
|
5996
|
-
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
5997
|
-
return null;
|
|
5998
|
-
}
|
|
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);
|
|
6003
|
-
}
|
|
6004
|
-
return null;
|
|
6005
|
-
};
|
|
6006
|
-
//#endregion
|
|
6007
6430
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
6008
6431
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
6009
6432
|
if (!a || !b) return a === b;
|
|
@@ -6261,7 +6684,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
6261
6684
|
};
|
|
6262
6685
|
//#endregion
|
|
6263
6686
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
6264
|
-
const MESSAGE$
|
|
6687
|
+
const MESSAGE$58 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
6265
6688
|
const KEY_HANDLERS = [
|
|
6266
6689
|
"onKeyUp",
|
|
6267
6690
|
"onKeyDown",
|
|
@@ -6472,7 +6895,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6472
6895
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
|
|
6473
6896
|
context.report({
|
|
6474
6897
|
node: node.name,
|
|
6475
|
-
message: MESSAGE$
|
|
6898
|
+
message: MESSAGE$58
|
|
6476
6899
|
});
|
|
6477
6900
|
} };
|
|
6478
6901
|
}
|
|
@@ -6687,15 +7110,6 @@ const getDirectConstInitializer = (symbol) => {
|
|
|
6687
7110
|
return symbol.initializer;
|
|
6688
7111
|
};
|
|
6689
7112
|
//#endregion
|
|
6690
|
-
//#region src/plugin/utils/get-static-property-name.ts
|
|
6691
|
-
const getStaticPropertyName = (memberExpression) => {
|
|
6692
|
-
const property = memberExpression.property;
|
|
6693
|
-
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
6694
|
-
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
6695
|
-
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
6696
|
-
return null;
|
|
6697
|
-
};
|
|
6698
|
-
//#endregion
|
|
6699
7113
|
//#region src/plugin/utils/get-range-start.ts
|
|
6700
7114
|
const getRangeStart = (node) => {
|
|
6701
7115
|
const rangeStart = node.range?.[0];
|
|
@@ -7091,16 +7505,6 @@ const collectFunctionReturnStatements = (functionNode) => {
|
|
|
7091
7505
|
return returnStatements;
|
|
7092
7506
|
};
|
|
7093
7507
|
//#endregion
|
|
7094
|
-
//#region src/plugin/utils/find-enclosing-function.ts
|
|
7095
|
-
const findEnclosingFunction = (node) => {
|
|
7096
|
-
let cursor = node.parent;
|
|
7097
|
-
while (cursor) {
|
|
7098
|
-
if (isFunctionLike$2(cursor)) return cursor;
|
|
7099
|
-
cursor = cursor.parent ?? null;
|
|
7100
|
-
}
|
|
7101
|
-
return null;
|
|
7102
|
-
};
|
|
7103
|
-
//#endregion
|
|
7104
7508
|
//#region src/plugin/utils/statement-always-exits.ts
|
|
7105
7509
|
const statementAlwaysExits = (statement) => {
|
|
7106
7510
|
if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
|
|
@@ -7146,8 +7550,8 @@ const applyDefinitions = (incomingDefinitions, definitions) => {
|
|
|
7146
7550
|
const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow) => {
|
|
7147
7551
|
if (!REASSIGNABLE_BINDING_KINDS.has(symbol.kind)) return symbol.initializer ? [symbol.initializer] : [];
|
|
7148
7552
|
if (!controlFlow) return [];
|
|
7149
|
-
const referenceFunction = findEnclosingFunction(referenceNode);
|
|
7150
|
-
if (findEnclosingFunction(symbol.bindingIdentifier) !== referenceFunction) return [];
|
|
7553
|
+
const referenceFunction = findEnclosingFunction$1(referenceNode);
|
|
7554
|
+
if (findEnclosingFunction$1(symbol.bindingIdentifier) !== referenceFunction) return [];
|
|
7151
7555
|
if (!referenceFunction) return [];
|
|
7152
7556
|
const functionControlFlow = controlFlow.cfgFor(referenceFunction);
|
|
7153
7557
|
if (!functionControlFlow) return [];
|
|
@@ -7171,7 +7575,7 @@ const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow)
|
|
|
7171
7575
|
if (symbol.initializer) addDefinition(symbol.initializer, symbol.bindingIdentifier, bindingPosition);
|
|
7172
7576
|
for (const reference of symbol.references) {
|
|
7173
7577
|
const writePosition = getRangeStart(reference.identifier);
|
|
7174
|
-
if (reference.flag === "read" || findEnclosingFunction(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
|
|
7578
|
+
if (reference.flag === "read" || findEnclosingFunction$1(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
|
|
7175
7579
|
const assignedExpression = getAssignedExpressionForWrite(reference.identifier);
|
|
7176
7580
|
if (!assignedExpression) continue;
|
|
7177
7581
|
addDefinition(assignedExpression, reference.identifier, writePosition);
|
|
@@ -8725,7 +9129,7 @@ const getClassNameLiteral = (classAttribute) => {
|
|
|
8725
9129
|
};
|
|
8726
9130
|
//#endregion
|
|
8727
9131
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
8728
|
-
const MESSAGE$
|
|
9132
|
+
const MESSAGE$57 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
8729
9133
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
8730
9134
|
"td",
|
|
8731
9135
|
"th",
|
|
@@ -8843,6 +9247,7 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
|
|
|
8843
9247
|
const LABEL_ELEMENT = "label";
|
|
8844
9248
|
const LABEL_COMPONENT_NAME = "Label";
|
|
8845
9249
|
const POLYMORPHIC_COMPONENT_PROP = "component";
|
|
9250
|
+
const TITLE_ATTRIBUTE = "title";
|
|
8846
9251
|
const WRAPPER_LABEL_PROP = "label";
|
|
8847
9252
|
const SELECT_ELEMENT = "select";
|
|
8848
9253
|
const DEFAULT_DEPTH = 5;
|
|
@@ -8888,6 +9293,96 @@ const hasNonEmptyPropValue = (attribute) => {
|
|
|
8888
9293
|
}
|
|
8889
9294
|
return true;
|
|
8890
9295
|
};
|
|
9296
|
+
const getLastJsxPropIgnoreCase = (attributes, targetProp) => {
|
|
9297
|
+
const targetPropLower = targetProp.toLowerCase();
|
|
9298
|
+
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
9299
|
+
const attribute = attributes[attributeIndex];
|
|
9300
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
9301
|
+
if (getJsxAttributeName(attribute.name)?.toLowerCase() === targetPropLower) return attribute;
|
|
9302
|
+
}
|
|
9303
|
+
};
|
|
9304
|
+
const getStaticNativeTitleArrayValue = (expression, scopes) => {
|
|
9305
|
+
const elementValues = [];
|
|
9306
|
+
for (const rawElement of expression.elements) {
|
|
9307
|
+
if (rawElement === null) {
|
|
9308
|
+
elementValues.push("");
|
|
9309
|
+
continue;
|
|
9310
|
+
}
|
|
9311
|
+
if (isNodeOfType(rawElement, "SpreadElement")) return null;
|
|
9312
|
+
const element = stripParenExpression(rawElement);
|
|
9313
|
+
if (isNodeOfType(element, "Literal")) {
|
|
9314
|
+
elementValues.push(element.value === null ? "" : String(element.value));
|
|
9315
|
+
continue;
|
|
9316
|
+
}
|
|
9317
|
+
if (isNodeOfType(element, "TemplateLiteral")) {
|
|
9318
|
+
const staticValue = getStaticTemplateLiteralValue(element);
|
|
9319
|
+
if (staticValue === null) return null;
|
|
9320
|
+
elementValues.push(staticValue);
|
|
9321
|
+
continue;
|
|
9322
|
+
}
|
|
9323
|
+
if (isNodeOfType(element, "Identifier") && element.name === "undefined" && scopes.isGlobalReference(element) || isNodeOfType(element, "UnaryExpression") && element.operator === "void") {
|
|
9324
|
+
elementValues.push("");
|
|
9325
|
+
continue;
|
|
9326
|
+
}
|
|
9327
|
+
if (isNodeOfType(element, "ArrayExpression")) {
|
|
9328
|
+
const nestedValue = getStaticNativeTitleArrayValue(element, scopes);
|
|
9329
|
+
if (nestedValue === null) return null;
|
|
9330
|
+
elementValues.push(nestedValue);
|
|
9331
|
+
continue;
|
|
9332
|
+
}
|
|
9333
|
+
return null;
|
|
9334
|
+
}
|
|
9335
|
+
return elementValues.join(",");
|
|
9336
|
+
};
|
|
9337
|
+
const isGlobalSymbolExpression = (expression, scopes) => {
|
|
9338
|
+
if (isNodeOfType(expression, "Identifier")) return expression.name === "Symbol" && scopes.isGlobalReference(expression);
|
|
9339
|
+
if (!isNodeOfType(expression, "MemberExpression")) return false;
|
|
9340
|
+
const object = stripParenExpression(expression.object);
|
|
9341
|
+
return isNodeOfType(object, "Identifier") && object.name === "Symbol" && scopes.isGlobalReference(object);
|
|
9342
|
+
};
|
|
9343
|
+
const hasNonEmptyNativeTitleExpression = (rawExpression, scopes) => {
|
|
9344
|
+
const expression = stripParenExpression(rawExpression);
|
|
9345
|
+
if (isNodeOfType(expression, "Literal")) {
|
|
9346
|
+
if (typeof expression.value === "string") return expression.value.trim().length > 0;
|
|
9347
|
+
return expression.value !== null && typeof expression.value !== "boolean";
|
|
9348
|
+
}
|
|
9349
|
+
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
9350
|
+
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
9351
|
+
return staticValue === null || staticValue.trim().length > 0;
|
|
9352
|
+
}
|
|
9353
|
+
if (isNodeOfType(expression, "Identifier")) return expression.name !== "undefined" || !scopes.isGlobalReference(expression);
|
|
9354
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return false;
|
|
9355
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") return false;
|
|
9356
|
+
if (isNodeOfType(expression, "ArrowFunctionExpression")) return false;
|
|
9357
|
+
if (isNodeOfType(expression, "FunctionExpression")) return false;
|
|
9358
|
+
if (isNodeOfType(expression, "ClassExpression")) return false;
|
|
9359
|
+
if (isNodeOfType(expression, "ArrayExpression")) {
|
|
9360
|
+
const staticValue = getStaticNativeTitleArrayValue(expression, scopes);
|
|
9361
|
+
return staticValue === null || staticValue.trim().length > 0;
|
|
9362
|
+
}
|
|
9363
|
+
if (isGlobalSymbolExpression(expression, scopes)) return false;
|
|
9364
|
+
if (isNodeOfType(expression, "CallExpression") && isGlobalSymbolExpression(expression.callee, scopes)) return false;
|
|
9365
|
+
if (isNodeOfType(expression, "ConditionalExpression")) return hasNonEmptyNativeTitleExpression(expression.consequent, scopes) && hasNonEmptyNativeTitleExpression(expression.alternate, scopes);
|
|
9366
|
+
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
9367
|
+
const finalExpression = expression.expressions.at(-1);
|
|
9368
|
+
return finalExpression ? hasNonEmptyNativeTitleExpression(finalExpression, scopes) : false;
|
|
9369
|
+
}
|
|
9370
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
9371
|
+
const leftExpression = stripParenExpression(expression.left);
|
|
9372
|
+
if (!isNodeOfType(leftExpression, "Literal")) return expression.operator !== "&&";
|
|
9373
|
+
if (expression.operator === "??") return hasNonEmptyNativeTitleExpression(leftExpression.value === null ? expression.right : leftExpression, scopes);
|
|
9374
|
+
const leftValueIsTruthy = Boolean(leftExpression.value);
|
|
9375
|
+
if (expression.operator === "&&") return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? expression.right : leftExpression, scopes);
|
|
9376
|
+
return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? leftExpression : expression.right, scopes);
|
|
9377
|
+
}
|
|
9378
|
+
return true;
|
|
9379
|
+
};
|
|
9380
|
+
const hasNonEmptyNativeTitle = (attribute, scopes) => {
|
|
9381
|
+
if (!attribute?.value) return false;
|
|
9382
|
+
if (isNodeOfType(attribute.value, "Literal")) return typeof attribute.value.value === "string" && attribute.value.value.trim().length > 0;
|
|
9383
|
+
if (isNodeOfType(attribute.value, "JSXExpressionContainer")) return hasNonEmptyNativeTitleExpression(attribute.value.expression, scopes);
|
|
9384
|
+
return true;
|
|
9385
|
+
};
|
|
8891
9386
|
const toAttributeMatchKey = (kind, value) => {
|
|
8892
9387
|
const trimmedValue = value.trim();
|
|
8893
9388
|
return trimmedValue.length > 0 ? `${kind}:${trimmedValue}` : null;
|
|
@@ -9144,6 +9639,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9144
9639
|
if (typeValue === "submit" || typeValue === "reset") return;
|
|
9145
9640
|
if (typeValue === "button" && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "value"))) return;
|
|
9146
9641
|
}
|
|
9642
|
+
if (isDomElement && hasNonEmptyNativeTitle(getLastJsxPropIgnoreCase(opening.attributes, TITLE_ATTRIBUTE), context.scopes)) return;
|
|
9147
9643
|
if (supportsPlaceholderNameFallback(tagName, opening) && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "placeholder"))) return;
|
|
9148
9644
|
if (hasLabellingProp(opening.attributes, settings.labelAttributes)) return;
|
|
9149
9645
|
if (isInsideJsxAttribute(node)) return;
|
|
@@ -9163,7 +9659,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9163
9659
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
9164
9660
|
context.report({
|
|
9165
9661
|
node: candidate.opening,
|
|
9166
|
-
message: MESSAGE$
|
|
9662
|
+
message: MESSAGE$57
|
|
9167
9663
|
});
|
|
9168
9664
|
}
|
|
9169
9665
|
}
|
|
@@ -9924,7 +10420,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
9924
10420
|
});
|
|
9925
10421
|
//#endregion
|
|
9926
10422
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
9927
|
-
const MESSAGE$
|
|
10423
|
+
const MESSAGE$56 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
9928
10424
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
9929
10425
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
9930
10426
|
"aria-label",
|
|
@@ -9949,7 +10445,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
9949
10445
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
9950
10446
|
context.report({
|
|
9951
10447
|
node: node.name,
|
|
9952
|
-
message: MESSAGE$
|
|
10448
|
+
message: MESSAGE$56
|
|
9953
10449
|
});
|
|
9954
10450
|
} };
|
|
9955
10451
|
}
|
|
@@ -9989,7 +10485,7 @@ const isEs6Component = (node) => {
|
|
|
9989
10485
|
};
|
|
9990
10486
|
//#endregion
|
|
9991
10487
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
9992
|
-
const MESSAGE$
|
|
10488
|
+
const MESSAGE$55 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
9993
10489
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
9994
10490
|
"observer",
|
|
9995
10491
|
"lazy",
|
|
@@ -10182,7 +10678,7 @@ const displayName = defineRule({
|
|
|
10182
10678
|
const reportAt = (node) => {
|
|
10183
10679
|
context.report({
|
|
10184
10680
|
node,
|
|
10185
|
-
message: MESSAGE$
|
|
10681
|
+
message: MESSAGE$55
|
|
10186
10682
|
});
|
|
10187
10683
|
};
|
|
10188
10684
|
return {
|
|
@@ -11093,7 +11589,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
|
|
|
11093
11589
|
//#endregion
|
|
11094
11590
|
//#region src/plugin/utils/enclosing-component-or-hook-name.ts
|
|
11095
11591
|
const enclosingComponentOrHookName = (node) => {
|
|
11096
|
-
const functionNode = findEnclosingFunction(node);
|
|
11592
|
+
const functionNode = findEnclosingFunction$1(node);
|
|
11097
11593
|
return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
|
|
11098
11594
|
};
|
|
11099
11595
|
//#endregion
|
|
@@ -11150,11 +11646,11 @@ const executesDuringRender = (functionNode, scopes) => {
|
|
|
11150
11646
|
//#endregion
|
|
11151
11647
|
//#region src/plugin/utils/find-render-phase-component-or-hook.ts
|
|
11152
11648
|
const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
11153
|
-
let functionNode = findEnclosingFunction(node);
|
|
11649
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
11154
11650
|
while (functionNode) {
|
|
11155
11651
|
if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
|
|
11156
11652
|
if (!executesDuringRender(functionNode, scopes)) return null;
|
|
11157
|
-
functionNode = findEnclosingFunction(functionNode);
|
|
11653
|
+
functionNode = findEnclosingFunction$1(functionNode);
|
|
11158
11654
|
}
|
|
11159
11655
|
return null;
|
|
11160
11656
|
};
|
|
@@ -11229,6 +11725,50 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
|
|
|
11229
11725
|
return true;
|
|
11230
11726
|
};
|
|
11231
11727
|
//#endregion
|
|
11728
|
+
//#region src/plugin/utils/is-node-reachable-within-function.ts
|
|
11729
|
+
const isInsideStaticallyUnreachableBranch = (node) => {
|
|
11730
|
+
let child = node;
|
|
11731
|
+
let parent = node.parent;
|
|
11732
|
+
while (parent) {
|
|
11733
|
+
if (isNodeOfType(parent, "IfStatement") && isNodeOfType(parent.test, "Literal")) {
|
|
11734
|
+
if (parent.test.value === false && parent.consequent === child) return true;
|
|
11735
|
+
if (parent.test.value === true && parent.alternate === child) return true;
|
|
11736
|
+
}
|
|
11737
|
+
if (isNodeOfType(parent, "ConditionalExpression") && isNodeOfType(parent.test, "Literal")) {
|
|
11738
|
+
if (parent.test.value === false && parent.consequent === child) return true;
|
|
11739
|
+
if (parent.test.value === true && parent.alternate === child) return true;
|
|
11740
|
+
}
|
|
11741
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) {
|
|
11742
|
+
if (isNodeOfType(parent.left, "Literal") && (parent.operator === "&&" && !parent.left.value || parent.operator === "||" && Boolean(parent.left.value))) return true;
|
|
11743
|
+
}
|
|
11744
|
+
child = parent;
|
|
11745
|
+
parent = parent.parent;
|
|
11746
|
+
}
|
|
11747
|
+
return false;
|
|
11748
|
+
};
|
|
11749
|
+
const isNodeReachableWithinFunction = (node, context) => {
|
|
11750
|
+
if (isInsideStaticallyUnreachableBranch(node)) return false;
|
|
11751
|
+
const owner = context.cfg.enclosingFunction(node);
|
|
11752
|
+
if (!owner) return true;
|
|
11753
|
+
const functionCfg = context.cfg.cfgFor(owner);
|
|
11754
|
+
if (!functionCfg) return true;
|
|
11755
|
+
const targetBlock = functionCfg.blockOf(node);
|
|
11756
|
+
if (!targetBlock) return true;
|
|
11757
|
+
const visitedBlocks = new Set([functionCfg.entry]);
|
|
11758
|
+
const pendingBlocks = [functionCfg.entry];
|
|
11759
|
+
while (pendingBlocks.length > 0) {
|
|
11760
|
+
const currentBlock = pendingBlocks.pop();
|
|
11761
|
+
if (!currentBlock) break;
|
|
11762
|
+
if (currentBlock === targetBlock) return true;
|
|
11763
|
+
for (const edge of currentBlock.successors) {
|
|
11764
|
+
if (visitedBlocks.has(edge.to)) continue;
|
|
11765
|
+
visitedBlocks.add(edge.to);
|
|
11766
|
+
pendingBlocks.push(edge.to);
|
|
11767
|
+
}
|
|
11768
|
+
}
|
|
11769
|
+
return false;
|
|
11770
|
+
};
|
|
11771
|
+
//#endregion
|
|
11232
11772
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
11233
11773
|
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
11234
11774
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
@@ -11357,36 +11897,15 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
11357
11897
|
});
|
|
11358
11898
|
return usages.filter((usage) => isNodeReachableWithinFunction(usage.node, context));
|
|
11359
11899
|
};
|
|
11360
|
-
const isNodeReachableWithinFunction = (node, context) => {
|
|
11361
|
-
const owner = context.cfg.enclosingFunction(node);
|
|
11362
|
-
if (!owner) return true;
|
|
11363
|
-
const functionCfg = context.cfg.cfgFor(owner);
|
|
11364
|
-
if (!functionCfg) return true;
|
|
11365
|
-
const targetBlock = functionCfg.blockOf(node);
|
|
11366
|
-
if (!targetBlock) return true;
|
|
11367
|
-
const visitedBlocks = new Set([functionCfg.entry]);
|
|
11368
|
-
const pendingBlocks = [functionCfg.entry];
|
|
11369
|
-
while (pendingBlocks.length > 0) {
|
|
11370
|
-
const currentBlock = pendingBlocks.pop();
|
|
11371
|
-
if (!currentBlock) break;
|
|
11372
|
-
if (currentBlock === targetBlock) return true;
|
|
11373
|
-
for (const edge of currentBlock.successors) {
|
|
11374
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
11375
|
-
visitedBlocks.add(edge.to);
|
|
11376
|
-
pendingBlocks.push(edge.to);
|
|
11377
|
-
}
|
|
11378
|
-
}
|
|
11379
|
-
return false;
|
|
11380
|
-
};
|
|
11381
11900
|
const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
|
|
11382
11901
|
let pathAnchor = usageNode;
|
|
11383
|
-
let pathOwner = findEnclosingFunction(pathAnchor);
|
|
11902
|
+
let pathOwner = findEnclosingFunction$1(pathAnchor);
|
|
11384
11903
|
while (pathOwner && isSynchronousIteratorCallback(pathOwner)) {
|
|
11385
11904
|
if (matchingNodes.length > 0 && matchingNodes.every((matchingNode) => context.cfg.enclosingFunction(matchingNode) === pathOwner)) break;
|
|
11386
11905
|
const iteratorCall = pathOwner.parent;
|
|
11387
11906
|
if (!isNodeOfType(iteratorCall, "CallExpression")) break;
|
|
11388
11907
|
pathAnchor = iteratorCall;
|
|
11389
|
-
pathOwner = findEnclosingFunction(pathAnchor);
|
|
11908
|
+
pathOwner = findEnclosingFunction$1(pathAnchor);
|
|
11390
11909
|
}
|
|
11391
11910
|
const owner = context.cfg.enclosingFunction(pathAnchor);
|
|
11392
11911
|
if (!owner) return false;
|
|
@@ -11615,7 +12134,7 @@ const findCollectionMappingCall = (callbackNode) => {
|
|
|
11615
12134
|
return callee.property.name === "map" && callNode.arguments?.[0] === callbackNode ? callNode : null;
|
|
11616
12135
|
};
|
|
11617
12136
|
const findMappedResourceCollectionKey = (resourceNode, context) => {
|
|
11618
|
-
const callbackNode = findEnclosingFunction(resourceNode);
|
|
12137
|
+
const callbackNode = findEnclosingFunction$1(resourceNode);
|
|
11619
12138
|
if (!callbackNode || !isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression")) return null;
|
|
11620
12139
|
const mappingCall = findCollectionMappingCall(callbackNode);
|
|
11621
12140
|
if (!mappingCall) return null;
|
|
@@ -11726,10 +12245,10 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
11726
12245
|
});
|
|
11727
12246
|
if (invocationCalls.length !== 1) return null;
|
|
11728
12247
|
const invocationCall = invocationCalls[0];
|
|
11729
|
-
return findEnclosingFunction(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
12248
|
+
return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
11730
12249
|
};
|
|
11731
12250
|
const resolveCleanupPathAnchor = (usageNode, effectCallback, context) => {
|
|
11732
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
12251
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
11733
12252
|
if (!usageFunction || usageFunction === effectCallback) return usageNode;
|
|
11734
12253
|
return findSingleDirectInvocation(usageFunction, effectCallback, context) ?? usageNode;
|
|
11735
12254
|
};
|
|
@@ -11744,7 +12263,7 @@ const resolveSingleAssignedCleanupFunction = (expression, usage, context) => {
|
|
|
11744
12263
|
const assignmentReference = assignmentReferences[0];
|
|
11745
12264
|
const assignmentTarget = findTransparentExpressionRoot(assignmentReference.identifier);
|
|
11746
12265
|
const assignmentNode = assignmentTarget.parent;
|
|
11747
|
-
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction(assignmentNode) !== findEnclosingFunction(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
|
|
12266
|
+
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction$1(assignmentNode) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
|
|
11748
12267
|
const assignedValue = stripParenExpression(assignmentNode.right);
|
|
11749
12268
|
return isFunctionLike$2(assignedValue) ? assignedValue : null;
|
|
11750
12269
|
};
|
|
@@ -11833,12 +12352,12 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
11833
12352
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
11834
12353
|
};
|
|
11835
12354
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
11836
|
-
const componentFunction = findEnclosingFunction(callback);
|
|
12355
|
+
const componentFunction = findEnclosingFunction$1(callback);
|
|
11837
12356
|
if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
|
|
11838
12357
|
let didFindUnmountCleanup = false;
|
|
11839
12358
|
walkAst(componentFunction.body, (child) => {
|
|
11840
12359
|
if (didFindUnmountCleanup) return false;
|
|
11841
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
|
|
12360
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
|
|
11842
12361
|
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
11843
12362
|
const dependencyList = child.arguments?.[1];
|
|
11844
12363
|
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
@@ -11934,7 +12453,7 @@ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, co
|
|
|
11934
12453
|
return didWriteGuard;
|
|
11935
12454
|
};
|
|
11936
12455
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
11937
|
-
const usageFunction = findEnclosingFunction(usage.node);
|
|
12456
|
+
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
11938
12457
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
11939
12458
|
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
11940
12459
|
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
@@ -11942,7 +12461,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
|
|
|
11942
12461
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
11943
12462
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
11944
12463
|
if (callback.async) return false;
|
|
11945
|
-
if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
12464
|
+
if (usage.kind === "subscribe" && findEnclosingFunction$1(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
11946
12465
|
if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
|
|
11947
12466
|
const matchingCleanupReturns = [];
|
|
11948
12467
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
@@ -12111,7 +12630,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
|
12111
12630
|
parentNode = currentNode.parent;
|
|
12112
12631
|
}
|
|
12113
12632
|
if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
|
|
12114
|
-
const effectCallback = findEnclosingFunction(parentNode);
|
|
12633
|
+
const effectCallback = findEnclosingFunction$1(parentNode);
|
|
12115
12634
|
const effectCall = effectCallback?.parent;
|
|
12116
12635
|
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
12117
12636
|
};
|
|
@@ -12121,13 +12640,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
12121
12640
|
if (!bindingIdentifier) return false;
|
|
12122
12641
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
12123
12642
|
if (!symbol) return false;
|
|
12124
|
-
return symbol.references.some((reference) => findEnclosingFunction(reference.identifier) !== functionNode);
|
|
12643
|
+
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
12125
12644
|
};
|
|
12126
12645
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
12127
12646
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
12128
|
-
const releaseFunction = findEnclosingFunction(releaseNode);
|
|
12647
|
+
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
12129
12648
|
if (!releaseFunction) return true;
|
|
12130
|
-
if (releaseFunction === findEnclosingFunction(usage.node)) return true;
|
|
12649
|
+
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
12131
12650
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
12132
12651
|
};
|
|
12133
12652
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -12310,10 +12829,10 @@ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction
|
|
|
12310
12829
|
}
|
|
12311
12830
|
if (assignedKey !== storage.refCurrentKey) return;
|
|
12312
12831
|
const assignedValue = stripParenExpression(child.right);
|
|
12313
|
-
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction(child) === cleanupFunction) return;
|
|
12832
|
+
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction$1(child) === cleanupFunction) return;
|
|
12314
12833
|
const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
|
|
12315
12834
|
const storesMatchingHandler = assignedSessionProperties.every((property) => isNodeOfType(property, "Property")) && assignedSessionProperties.some((property) => isNodeOfType(property, "Property") && `${storage.refCurrentKey}.${getStaticPropertyKeyName(property) ?? ""}` === storage.handlerKey && resolveExpressionKey(property.value, context) === usage.handlerKey);
|
|
12316
|
-
if (findEnclosingFunction(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12835
|
+
if (findEnclosingFunction$1(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12317
12836
|
hasUnsafeRefWrite = true;
|
|
12318
12837
|
return false;
|
|
12319
12838
|
}
|
|
@@ -12334,12 +12853,12 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
12334
12853
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
12335
12854
|
};
|
|
12336
12855
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
12337
|
-
const componentFunction = findEnclosingFunction(retainedFunction);
|
|
12856
|
+
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
12338
12857
|
if (!componentFunction || !isFunctionLike$2(componentFunction)) return false;
|
|
12339
12858
|
let didFindCleanupEffect = false;
|
|
12340
12859
|
walkAst(componentFunction.body, (child) => {
|
|
12341
12860
|
if (didFindCleanupEffect) return false;
|
|
12342
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
12861
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
12343
12862
|
const effectStatement = findTransparentExpressionRoot(child).parent;
|
|
12344
12863
|
if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
|
|
12345
12864
|
const effectCallback = getEffectCallback(child);
|
|
@@ -13193,6 +13712,202 @@ const resolveExhaustiveDepsSettings = (settings) => {
|
|
|
13193
13712
|
};
|
|
13194
13713
|
};
|
|
13195
13714
|
//#endregion
|
|
13715
|
+
//#region src/plugin/utils/is-ast-descendant.ts
|
|
13716
|
+
/**
|
|
13717
|
+
* True when `inner` is `outer` itself or any descendant in the AST
|
|
13718
|
+
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
13719
|
+
* match (`true`) or the chain's root (`false`).
|
|
13720
|
+
*
|
|
13721
|
+
* Was duplicated byte-identical in:
|
|
13722
|
+
* - exhaustive-deps-symbol-stability.ts
|
|
13723
|
+
* - semantic/closure-captures.ts
|
|
13724
|
+
*/
|
|
13725
|
+
const isAstDescendant = (inner, outer) => {
|
|
13726
|
+
let current = inner;
|
|
13727
|
+
while (current) {
|
|
13728
|
+
if (current === outer) return true;
|
|
13729
|
+
current = current.parent ?? null;
|
|
13730
|
+
}
|
|
13731
|
+
return false;
|
|
13732
|
+
};
|
|
13733
|
+
//#endregion
|
|
13734
|
+
//#region src/plugin/rules/react-builtins/exhaustive-deps-sole-writer-guard.ts
|
|
13735
|
+
const EQUALITY_BINARY_OPERATORS = new Set(["===", "!=="]);
|
|
13736
|
+
const getUseStateSetterSymbol = (stateSymbol, scopes) => {
|
|
13737
|
+
const declaration = stateSymbol.declarationNode;
|
|
13738
|
+
if (!isNodeOfType(declaration, "VariableDeclarator")) return null;
|
|
13739
|
+
if (!isNodeOfType(declaration.id, "ArrayPattern")) return null;
|
|
13740
|
+
const stateBinding = declaration.id.elements[0];
|
|
13741
|
+
const setterBinding = declaration.id.elements[1];
|
|
13742
|
+
if (stateBinding !== stateSymbol.bindingIdentifier || !isNodeOfType(setterBinding, "Identifier")) return null;
|
|
13743
|
+
const initializer = declaration.init ? stripParenExpression(declaration.init) : null;
|
|
13744
|
+
if (!initializer || !isReactApiCall(initializer, "useState", scopes, {
|
|
13745
|
+
allowGlobalReactNamespace: true,
|
|
13746
|
+
allowUnboundBareCalls: true,
|
|
13747
|
+
resolveNamedAliases: true
|
|
13748
|
+
})) return null;
|
|
13749
|
+
return scopes.symbolFor(setterBinding);
|
|
13750
|
+
};
|
|
13751
|
+
const getDirectSetterCall = (setterSymbol, callback) => {
|
|
13752
|
+
if (setterSymbol.references.length !== 1) return null;
|
|
13753
|
+
const setterReference = setterSymbol.references[0];
|
|
13754
|
+
if (setterReference.flag !== "read") return null;
|
|
13755
|
+
const referenceRoot = findTransparentExpressionRoot(setterReference.identifier);
|
|
13756
|
+
const callExpression = referenceRoot.parent;
|
|
13757
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot || callExpression.arguments.length !== 1 || findEnclosingFunction$1(callExpression) !== callback) return null;
|
|
13758
|
+
const writtenValue = stripParenExpression(callExpression.arguments[0]);
|
|
13759
|
+
if (isNodeOfType(writtenValue, "ArrowFunctionExpression") || isNodeOfType(writtenValue, "FunctionExpression") || isNodeOfType(writtenValue, "SpreadElement")) return null;
|
|
13760
|
+
return {
|
|
13761
|
+
callExpression,
|
|
13762
|
+
writtenValue
|
|
13763
|
+
};
|
|
13764
|
+
};
|
|
13765
|
+
const isGlobalObjectIsCall = (callExpression, scopes) => {
|
|
13766
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
13767
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
13768
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
13769
|
+
const propertyName = getStaticPropertyName(callee);
|
|
13770
|
+
const receiver = stripParenExpression(callee.object);
|
|
13771
|
+
return Boolean(propertyName === "is" && isNodeOfType(receiver, "Identifier") && receiver.name === "Object" && scopes.isGlobalReference(receiver));
|
|
13772
|
+
};
|
|
13773
|
+
const isGlobalNaNReference = (expression, scopes) => {
|
|
13774
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
13775
|
+
return isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression);
|
|
13776
|
+
};
|
|
13777
|
+
const getEqualityComparison = (stateReference, candidate, scopes) => {
|
|
13778
|
+
const unwrappedStateReference = stripParenExpression(stateReference);
|
|
13779
|
+
if (isNodeOfType(candidate, "BinaryExpression") && EQUALITY_BINARY_OPERATORS.has(candidate.operator)) {
|
|
13780
|
+
if (stripParenExpression(candidate.left) === unwrappedStateReference) return isGlobalNaNReference(candidate.right, scopes) ? null : {
|
|
13781
|
+
comparison: candidate,
|
|
13782
|
+
counterpart: candidate.right,
|
|
13783
|
+
areValuesEqualWhenTruthy: candidate.operator === "==="
|
|
13784
|
+
};
|
|
13785
|
+
if (stripParenExpression(candidate.right) === unwrappedStateReference) return isGlobalNaNReference(candidate.left, scopes) ? null : {
|
|
13786
|
+
comparison: candidate,
|
|
13787
|
+
counterpart: candidate.left,
|
|
13788
|
+
areValuesEqualWhenTruthy: candidate.operator === "==="
|
|
13789
|
+
};
|
|
13790
|
+
return null;
|
|
13791
|
+
}
|
|
13792
|
+
if (!isNodeOfType(candidate, "CallExpression") || !isGlobalObjectIsCall(candidate, scopes) || candidate.arguments.length !== 2) return null;
|
|
13793
|
+
const firstArgument = candidate.arguments[0];
|
|
13794
|
+
const secondArgument = candidate.arguments[1];
|
|
13795
|
+
if (isNodeOfType(firstArgument, "SpreadElement") || isNodeOfType(secondArgument, "SpreadElement")) return null;
|
|
13796
|
+
if (stripParenExpression(firstArgument) === unwrappedStateReference) return {
|
|
13797
|
+
comparison: candidate,
|
|
13798
|
+
counterpart: secondArgument,
|
|
13799
|
+
areValuesEqualWhenTruthy: true
|
|
13800
|
+
};
|
|
13801
|
+
if (stripParenExpression(secondArgument) === unwrappedStateReference) return {
|
|
13802
|
+
comparison: candidate,
|
|
13803
|
+
counterpart: firstArgument,
|
|
13804
|
+
areValuesEqualWhenTruthy: true
|
|
13805
|
+
};
|
|
13806
|
+
return null;
|
|
13807
|
+
};
|
|
13808
|
+
const findEqualityComparison = (stateReference, test, scopes) => {
|
|
13809
|
+
let current = stateReference.parent;
|
|
13810
|
+
while (current && isAstDescendant(current, test)) {
|
|
13811
|
+
const comparison = getEqualityComparison(stateReference, current, scopes);
|
|
13812
|
+
if (comparison) return comparison;
|
|
13813
|
+
if (current === test) break;
|
|
13814
|
+
current = current.parent;
|
|
13815
|
+
}
|
|
13816
|
+
return null;
|
|
13817
|
+
};
|
|
13818
|
+
const doesTestOutcomeRequireComparisonOutcome = (comparison, test, testOutcome, comparisonOutcome) => {
|
|
13819
|
+
let requiredChildOutcome = testOutcome;
|
|
13820
|
+
let current = comparison;
|
|
13821
|
+
while (current !== test) {
|
|
13822
|
+
const parent = current.parent;
|
|
13823
|
+
if (!parent || !isAstDescendant(parent, test)) return false;
|
|
13824
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") requiredChildOutcome = !requiredChildOutcome;
|
|
13825
|
+
else if (isNodeOfType(parent, "LogicalExpression")) {
|
|
13826
|
+
if (parent.operator === "&&" && !requiredChildOutcome) return false;
|
|
13827
|
+
if (parent.operator === "||" && requiredChildOutcome) return false;
|
|
13828
|
+
if (parent.operator !== "&&" && parent.operator !== "||") return false;
|
|
13829
|
+
} else if (!TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type)) return false;
|
|
13830
|
+
current = parent;
|
|
13831
|
+
}
|
|
13832
|
+
return requiredChildOutcome === comparisonOutcome;
|
|
13833
|
+
};
|
|
13834
|
+
const resolveImmutableAliasExpression = (expression, scopes) => {
|
|
13835
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
13836
|
+
let current = stripParenExpression(expression);
|
|
13837
|
+
while (isNodeOfType(current, "Identifier")) {
|
|
13838
|
+
const symbol = scopes.symbolFor(current);
|
|
13839
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) break;
|
|
13840
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
13841
|
+
if (!initializer || !isNodeOfType(stripParenExpression(initializer), "Identifier")) break;
|
|
13842
|
+
visitedSymbolIds.add(symbol.id);
|
|
13843
|
+
current = stripParenExpression(initializer);
|
|
13844
|
+
}
|
|
13845
|
+
return current;
|
|
13846
|
+
};
|
|
13847
|
+
const referencesSameValue = (leftExpression, rightExpression, scopes) => {
|
|
13848
|
+
const left = resolveImmutableAliasExpression(leftExpression, scopes);
|
|
13849
|
+
const right = resolveImmutableAliasExpression(rightExpression, scopes);
|
|
13850
|
+
if (left === right) return true;
|
|
13851
|
+
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
13852
|
+
const leftSymbol = scopes.symbolFor(left);
|
|
13853
|
+
const rightSymbol = scopes.symbolFor(right);
|
|
13854
|
+
if (leftSymbol || rightSymbol) return leftSymbol?.id === rightSymbol?.id;
|
|
13855
|
+
return left.name === right.name;
|
|
13856
|
+
}
|
|
13857
|
+
if (isNodeOfType(left, "Literal") && isNodeOfType(right, "Literal")) return Object.is(left.value, right.value);
|
|
13858
|
+
return false;
|
|
13859
|
+
};
|
|
13860
|
+
const hasDeclaredTriggerDependency = (callback) => {
|
|
13861
|
+
const hookCall = findTransparentExpressionRoot(callback).parent;
|
|
13862
|
+
if (!isNodeOfType(hookCall, "CallExpression")) return false;
|
|
13863
|
+
const dependencyArray = hookCall.arguments[1];
|
|
13864
|
+
return Boolean(isNodeOfType(dependencyArray, "ArrayExpression") && dependencyArray.elements.length);
|
|
13865
|
+
};
|
|
13866
|
+
const doesBranchExitEffect = (branch) => {
|
|
13867
|
+
if (isNodeOfType(branch, "ReturnStatement") || isNodeOfType(branch, "ThrowStatement")) return true;
|
|
13868
|
+
if (!isNodeOfType(branch, "BlockStatement")) return false;
|
|
13869
|
+
const terminalStatement = branch.body.at(-1);
|
|
13870
|
+
return Boolean(terminalStatement && doesBranchExitEffect(terminalStatement));
|
|
13871
|
+
};
|
|
13872
|
+
const doesGuardDominateLaterSetter = (guard, setterCall) => {
|
|
13873
|
+
if (!isNodeOfType(guard, "IfStatement") || guard.alternate) return false;
|
|
13874
|
+
if (!doesBranchExitEffect(guard.consequent)) return false;
|
|
13875
|
+
const block = guard.parent;
|
|
13876
|
+
if (!isNodeOfType(block, "BlockStatement")) return false;
|
|
13877
|
+
const guardIndex = block.body.findIndex((statement) => statement === guard);
|
|
13878
|
+
return block.body.some((statement, statementIndex) => statementIndex > guardIndex && isAstDescendant(setterCall, statement));
|
|
13879
|
+
};
|
|
13880
|
+
const findDominatingGuardCounterpart = (stateReference, setterCall, callback, scopes) => {
|
|
13881
|
+
let current = stateReference.parent;
|
|
13882
|
+
while (current && current !== callback) {
|
|
13883
|
+
if (isNodeOfType(current, "IfStatement") && isAstDescendant(stateReference, current.test) && (isAstDescendant(setterCall, current.consequent) || Boolean(current.alternate && isAstDescendant(setterCall, current.alternate)) || doesGuardDominateLaterSetter(current, setterCall))) {
|
|
13884
|
+
const setterRunsWhenTestTruthy = isAstDescendant(setterCall, current.consequent);
|
|
13885
|
+
if (setterRunsWhenTestTruthy === (Boolean(current.alternate && isAstDescendant(setterCall, current.alternate)) || doesGuardDominateLaterSetter(current, setterCall))) return null;
|
|
13886
|
+
const equalityComparison = findEqualityComparison(stateReference, current.test, scopes);
|
|
13887
|
+
if (!equalityComparison) return null;
|
|
13888
|
+
const comparisonOutcomeForDifferentValues = !equalityComparison.areValuesEqualWhenTruthy;
|
|
13889
|
+
if (!doesTestOutcomeRequireComparisonOutcome(equalityComparison.comparison, current.test, setterRunsWhenTestTruthy, comparisonOutcomeForDifferentValues)) return null;
|
|
13890
|
+
return equalityComparison.counterpart;
|
|
13891
|
+
}
|
|
13892
|
+
current = current.parent;
|
|
13893
|
+
}
|
|
13894
|
+
return null;
|
|
13895
|
+
};
|
|
13896
|
+
const isSoleWriterEffectGuardCapture = (stateSymbol, callback, scopes) => {
|
|
13897
|
+
if (!hasDeclaredTriggerDependency(callback)) return false;
|
|
13898
|
+
if (stateSymbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
13899
|
+
const setterSymbol = getUseStateSetterSymbol(stateSymbol, scopes);
|
|
13900
|
+
if (!setterSymbol) return false;
|
|
13901
|
+
const setterWrite = getDirectSetterCall(setterSymbol, callback);
|
|
13902
|
+
if (!setterWrite) return false;
|
|
13903
|
+
const callbackStateReferences = stateSymbol.references.filter((reference) => isAstDescendant(reference.identifier, callback));
|
|
13904
|
+
if (callbackStateReferences.length !== 1) return false;
|
|
13905
|
+
const stateReferenceRoot = findTransparentExpressionRoot(callbackStateReferences[0].identifier);
|
|
13906
|
+
if (isNodeOfType(stateReferenceRoot.parent, "MemberExpression") && stateReferenceRoot.parent.object === stateReferenceRoot) return false;
|
|
13907
|
+
const counterpart = findDominatingGuardCounterpart(callbackStateReferences[0].identifier, setterWrite.callExpression, callback, scopes);
|
|
13908
|
+
return Boolean(counterpart && referencesSameValue(counterpart, setterWrite.writtenValue, scopes));
|
|
13909
|
+
};
|
|
13910
|
+
//#endregion
|
|
13196
13911
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-suppression.ts
|
|
13197
13912
|
const DISABLE_COMMENT_RULE_NAME_PATTERN$1 = /(?:^|[\s,/])exhaustive-deps(?:$|[\s,:])/;
|
|
13198
13913
|
const DISABLE_NEXT_LINE_PATTERN$1 = /\b(?:eslint|oxlint)-disable-next-line\b([^\n]*)/;
|
|
@@ -13264,25 +13979,6 @@ const isExhaustiveDepsSuppressedAt = (filename, nodeStartOffset) => {
|
|
|
13264
13979
|
return index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf16NewlineOffsets)) || index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf8NewlineOffsets));
|
|
13265
13980
|
};
|
|
13266
13981
|
//#endregion
|
|
13267
|
-
//#region src/plugin/utils/is-ast-descendant.ts
|
|
13268
|
-
/**
|
|
13269
|
-
* True when `inner` is `outer` itself or any descendant in the AST
|
|
13270
|
-
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
13271
|
-
* match (`true`) or the chain's root (`false`).
|
|
13272
|
-
*
|
|
13273
|
-
* Was duplicated byte-identical in:
|
|
13274
|
-
* - exhaustive-deps-symbol-stability.ts
|
|
13275
|
-
* - semantic/closure-captures.ts
|
|
13276
|
-
*/
|
|
13277
|
-
const isAstDescendant = (inner, outer) => {
|
|
13278
|
-
let current = inner;
|
|
13279
|
-
while (current) {
|
|
13280
|
-
if (current === outer) return true;
|
|
13281
|
-
current = current.parent ?? null;
|
|
13282
|
-
}
|
|
13283
|
-
return false;
|
|
13284
|
-
};
|
|
13285
|
-
//#endregion
|
|
13286
13982
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
|
|
13287
13983
|
/**
|
|
13288
13984
|
* Symbol-stability helpers consumed by the `exhaustive-deps` rule.
|
|
@@ -13463,6 +14159,7 @@ const EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS = new Set([
|
|
|
13463
14159
|
"useLayoutEffect",
|
|
13464
14160
|
"useInsertionEffect"
|
|
13465
14161
|
]);
|
|
14162
|
+
const SOLE_WRITER_GUARD_HOOKS = new Set(["useEffect", "useLayoutEffect"]);
|
|
13466
14163
|
const buildAdditionalHooksRegex = (additional) => {
|
|
13467
14164
|
if (!additional) return null;
|
|
13468
14165
|
try {
|
|
@@ -13599,7 +14296,7 @@ const stringifyMemberChain = (node) => {
|
|
|
13599
14296
|
}
|
|
13600
14297
|
return null;
|
|
13601
14298
|
};
|
|
13602
|
-
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
14299
|
+
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allowSoleWriterEffectGuards = false) => {
|
|
13603
14300
|
const keys = /* @__PURE__ */ new Set();
|
|
13604
14301
|
const stableCapturedNames = /* @__PURE__ */ new Set();
|
|
13605
14302
|
const moduleScopeCapturedNames = /* @__PURE__ */ new Set();
|
|
@@ -13610,6 +14307,10 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
|
13610
14307
|
const symbol = reference.resolvedSymbol;
|
|
13611
14308
|
if (!symbol) continue;
|
|
13612
14309
|
if (isRecursiveInitializerCapture(symbol, callback)) continue;
|
|
14310
|
+
if (allowSoleWriterEffectGuards && isSoleWriterEffectGuardCapture(symbol, callback, scopes)) {
|
|
14311
|
+
stableCapturedNames.add(symbol.name);
|
|
14312
|
+
continue;
|
|
14313
|
+
}
|
|
13613
14314
|
if (symbolHasStableValue(symbol, scopes)) {
|
|
13614
14315
|
stableCapturedNames.add(symbol.name);
|
|
13615
14316
|
continue;
|
|
@@ -14282,7 +14983,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
14282
14983
|
if (isNodeOfType(stripped, "Identifier")) declaredExactBindingKeys.add(key);
|
|
14283
14984
|
declaredKeyToReportNode.set(key, elementNode);
|
|
14284
14985
|
}
|
|
14285
|
-
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys);
|
|
14986
|
+
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys, SOLE_WRITER_GUARD_HOOKS.has(hookName));
|
|
14286
14987
|
for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
|
|
14287
14988
|
addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
|
|
14288
14989
|
const missingCaptureKeys = [];
|
|
@@ -14722,7 +15423,7 @@ const forbidElements = defineRule({
|
|
|
14722
15423
|
});
|
|
14723
15424
|
//#endregion
|
|
14724
15425
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
14725
|
-
const MESSAGE$
|
|
15426
|
+
const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
14726
15427
|
const forwardRefUsesRef = defineRule({
|
|
14727
15428
|
id: "forward-ref-uses-ref",
|
|
14728
15429
|
title: "forwardRef without ref parameter",
|
|
@@ -14745,7 +15446,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
14745
15446
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
14746
15447
|
context.report({
|
|
14747
15448
|
node: inner,
|
|
14748
|
-
message: MESSAGE$
|
|
15449
|
+
message: MESSAGE$54
|
|
14749
15450
|
});
|
|
14750
15451
|
} })
|
|
14751
15452
|
});
|
|
@@ -14786,7 +15487,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
14786
15487
|
});
|
|
14787
15488
|
//#endregion
|
|
14788
15489
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
14789
|
-
const MESSAGE$
|
|
15490
|
+
const MESSAGE$53 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
14790
15491
|
const DEFAULT_HEADING_TAGS = [
|
|
14791
15492
|
"h1",
|
|
14792
15493
|
"h2",
|
|
@@ -14820,7 +15521,7 @@ const headingHasContent = defineRule({
|
|
|
14820
15521
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
14821
15522
|
context.report({
|
|
14822
15523
|
node,
|
|
14823
|
-
message: MESSAGE$
|
|
15524
|
+
message: MESSAGE$53
|
|
14824
15525
|
});
|
|
14825
15526
|
} };
|
|
14826
15527
|
}
|
|
@@ -14984,7 +15685,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
14984
15685
|
});
|
|
14985
15686
|
//#endregion
|
|
14986
15687
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
14987
|
-
const MESSAGE$
|
|
15688
|
+
const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
14988
15689
|
const resolveSettings$39 = (settings) => {
|
|
14989
15690
|
const reactDoctor = settings?.["react-doctor"];
|
|
14990
15691
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -15031,13 +15732,13 @@ const htmlHasLang = defineRule({
|
|
|
15031
15732
|
if (!lang) {
|
|
15032
15733
|
context.report({
|
|
15033
15734
|
node: node.name,
|
|
15034
|
-
message: MESSAGE$
|
|
15735
|
+
message: MESSAGE$52
|
|
15035
15736
|
});
|
|
15036
15737
|
return;
|
|
15037
15738
|
}
|
|
15038
15739
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
15039
15740
|
node: lang,
|
|
15040
|
-
message: MESSAGE$
|
|
15741
|
+
message: MESSAGE$52
|
|
15041
15742
|
});
|
|
15042
15743
|
} };
|
|
15043
15744
|
}
|
|
@@ -15277,7 +15978,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
15277
15978
|
});
|
|
15278
15979
|
//#endregion
|
|
15279
15980
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
15280
|
-
const MESSAGE$
|
|
15981
|
+
const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
15281
15982
|
const evaluateTitleValue = (value) => {
|
|
15282
15983
|
if (!value) return "missing";
|
|
15283
15984
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -15317,14 +16018,14 @@ const iframeHasTitle = defineRule({
|
|
|
15317
16018
|
if (!titleAttr) {
|
|
15318
16019
|
if (hasSpread || tag === "iframe") context.report({
|
|
15319
16020
|
node: node.name,
|
|
15320
|
-
message: MESSAGE$
|
|
16021
|
+
message: MESSAGE$51
|
|
15321
16022
|
});
|
|
15322
16023
|
return;
|
|
15323
16024
|
}
|
|
15324
16025
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
15325
16026
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
15326
16027
|
node: titleAttr,
|
|
15327
|
-
message: MESSAGE$
|
|
16028
|
+
message: MESSAGE$51
|
|
15328
16029
|
});
|
|
15329
16030
|
} })
|
|
15330
16031
|
});
|
|
@@ -15449,7 +16150,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
15449
16150
|
});
|
|
15450
16151
|
//#endregion
|
|
15451
16152
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
15452
|
-
const MESSAGE$
|
|
16153
|
+
const MESSAGE$50 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
15453
16154
|
const DEFAULT_COMPONENTS = ["img"];
|
|
15454
16155
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
15455
16156
|
"image",
|
|
@@ -15514,7 +16215,7 @@ const imgRedundantAlt = defineRule({
|
|
|
15514
16215
|
if (!altAttribute) return;
|
|
15515
16216
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
15516
16217
|
node: altAttribute,
|
|
15517
|
-
message: MESSAGE$
|
|
16218
|
+
message: MESSAGE$50
|
|
15518
16219
|
});
|
|
15519
16220
|
} };
|
|
15520
16221
|
}
|
|
@@ -16096,14 +16797,14 @@ const isBindingInvokedOnRenderPath = (root, bindingName) => {
|
|
|
16096
16797
|
if (didFindRenderPathInvocation) return false;
|
|
16097
16798
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
16098
16799
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== bindingName) return;
|
|
16099
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
16800
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
16100
16801
|
while (enclosingFunction) {
|
|
16101
16802
|
if (isDeferredCallbackPosition$1(enclosingFunction) || getHandlerNamedBindingName(enclosingFunction)) return;
|
|
16102
16803
|
if (containingFunctionIsComponentOrHook(enclosingFunction)) {
|
|
16103
16804
|
didFindRenderPathInvocation = true;
|
|
16104
16805
|
return;
|
|
16105
16806
|
}
|
|
16106
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
16807
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
16107
16808
|
}
|
|
16108
16809
|
});
|
|
16109
16810
|
return didFindRenderPathInvocation;
|
|
@@ -16138,7 +16839,7 @@ const jotaiSelectAtomInRenderBody = defineRule({
|
|
|
16138
16839
|
};
|
|
16139
16840
|
return { CallExpression(node) {
|
|
16140
16841
|
if (!isImportedSelectAtom(node)) return;
|
|
16141
|
-
const nearestFunctionLike = findEnclosingFunction(node);
|
|
16842
|
+
const nearestFunctionLike = findEnclosingFunction$1(node);
|
|
16142
16843
|
if (!nearestFunctionLike) return;
|
|
16143
16844
|
if (isDeferredCallback(nearestFunctionLike)) return;
|
|
16144
16845
|
if (isBindingUsedAsHandler(nearestFunctionLike)) return;
|
|
@@ -17135,7 +17836,7 @@ const isPersistentCacheSetWrite = (callExpression, newExpression, enclosingFunct
|
|
|
17135
17836
|
return !isAstDescendant(receiverBinding.scopeOwner, enclosingFunction);
|
|
17136
17837
|
};
|
|
17137
17838
|
const isInsideCacheMemo = (node) => {
|
|
17138
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
17839
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
17139
17840
|
let child = node;
|
|
17140
17841
|
let cursor = node.parent ?? null;
|
|
17141
17842
|
while (cursor) {
|
|
@@ -17173,7 +17874,7 @@ const getFunctionName = (functionNode) => {
|
|
|
17173
17874
|
return null;
|
|
17174
17875
|
};
|
|
17175
17876
|
const isUncacheableOptionsMergeUtility = (node) => {
|
|
17176
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
17877
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
17177
17878
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction)) return false;
|
|
17178
17879
|
const functionName = getFunctionName(enclosingFunction);
|
|
17179
17880
|
if (!functionName || isComponentOrHookName(functionName)) return false;
|
|
@@ -17297,7 +17998,7 @@ const GLOBAL_BUILTIN_NAMES = new Set([
|
|
|
17297
17998
|
]);
|
|
17298
17999
|
const STRING_PROTOTYPE_PATH = "String.prototype";
|
|
17299
18000
|
const REGEXP_PROTOTYPE_PATH = "RegExp.prototype";
|
|
17300
|
-
const getStaticStringValue = (argument) => {
|
|
18001
|
+
const getStaticStringValue$1 = (argument) => {
|
|
17301
18002
|
if (!argument) return null;
|
|
17302
18003
|
const unwrappedArgument = stripParenExpression(argument);
|
|
17303
18004
|
if (isNodeOfType(unwrappedArgument, "Literal") && typeof unwrappedArgument.value === "string") return unwrappedArgument.value;
|
|
@@ -17305,7 +18006,7 @@ const getStaticStringValue = (argument) => {
|
|
|
17305
18006
|
return null;
|
|
17306
18007
|
};
|
|
17307
18008
|
const getEffectiveRegExpFlags = (patternArgument, flagsArgument) => {
|
|
17308
|
-
if (flagsArgument) return getStaticStringValue(flagsArgument);
|
|
18009
|
+
if (flagsArgument) return getStaticStringValue$1(flagsArgument);
|
|
17309
18010
|
if (!patternArgument) return "";
|
|
17310
18011
|
const unwrappedPattern = stripParenExpression(patternArgument);
|
|
17311
18012
|
if (isNodeOfType(unwrappedPattern, "Literal") && unwrappedPattern.value instanceof RegExp) return unwrappedPattern.value.flags;
|
|
@@ -17364,13 +18065,6 @@ const isSafeStatefulReplaceAllSearch = (node, flags, context) => {
|
|
|
17364
18065
|
const callee = stripParenExpression(replaceAllCall.callee);
|
|
17365
18066
|
return isNodeOfType(callee, "MemberExpression") && !callee.computed && !callee.optional && !replaceAllCall.optional && isNodeOfType(callee.property, "Identifier") && callee.property.name === "replaceAll" && isProvenNativeStringReceiver(callee.object, context);
|
|
17366
18067
|
};
|
|
17367
|
-
const getDestructuredBindingPropertyName = (bindingIdentifier) => {
|
|
17368
|
-
let bindingNode = bindingIdentifier;
|
|
17369
|
-
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
17370
|
-
const property = bindingNode.parent;
|
|
17371
|
-
if (!isNodeOfType(property, "Property") || property.value !== bindingNode || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
17372
|
-
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
17373
|
-
};
|
|
17374
18068
|
const extendGlobalPath = (basePath, propertyName) => {
|
|
17375
18069
|
if (basePath === "global") {
|
|
17376
18070
|
if (propertyName === null || GLOBAL_OBJECT_NAMES.has(propertyName)) return "global";
|
|
@@ -17455,7 +18149,7 @@ const getCallRegExpHazard = (node, context, symbolCache) => {
|
|
|
17455
18149
|
const mutationHazard = targetPath === "global" ? "globalRegExpReplaced" : "replaceAllIntegrityLost";
|
|
17456
18150
|
const guardedPropertyName = targetPath === "global" ? "RegExp" : "replaceAll";
|
|
17457
18151
|
if (isSinglePropertyMutation) {
|
|
17458
|
-
const propertyName = getStaticStringValue(node.arguments?.[1]);
|
|
18152
|
+
const propertyName = getStaticStringValue$1(node.arguments?.[1]);
|
|
17459
18153
|
return propertyName === null || propertyName === guardedPropertyName ? mutationHazard : "none";
|
|
17460
18154
|
}
|
|
17461
18155
|
if (!isPropertyCollectionMutation) return "none";
|
|
@@ -17484,7 +18178,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
17484
18178
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
17485
18179
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
17486
18180
|
};
|
|
17487
|
-
const MESSAGE$
|
|
18181
|
+
const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
17488
18182
|
const jsHoistRegexp = defineRule({
|
|
17489
18183
|
id: "js-hoist-regexp",
|
|
17490
18184
|
title: "RegExp built inside a loop",
|
|
@@ -17501,7 +18195,7 @@ const jsHoistRegexp = defineRule({
|
|
|
17501
18195
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
17502
18196
|
context.report({
|
|
17503
18197
|
node,
|
|
17504
|
-
message: MESSAGE$
|
|
18198
|
+
message: MESSAGE$49
|
|
17505
18199
|
});
|
|
17506
18200
|
};
|
|
17507
18201
|
return createLoopAwareVisitors({
|
|
@@ -20121,7 +20815,7 @@ const jsxMaxDepth = defineRule({
|
|
|
20121
20815
|
});
|
|
20122
20816
|
//#endregion
|
|
20123
20817
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
20124
|
-
const MESSAGE$
|
|
20818
|
+
const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
20125
20819
|
const LITERAL_TEXT_TAGS = new Set([
|
|
20126
20820
|
"code",
|
|
20127
20821
|
"pre",
|
|
@@ -20191,7 +20885,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
20191
20885
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
20192
20886
|
context.report({
|
|
20193
20887
|
node,
|
|
20194
|
-
message: MESSAGE$
|
|
20888
|
+
message: MESSAGE$48
|
|
20195
20889
|
});
|
|
20196
20890
|
} })
|
|
20197
20891
|
});
|
|
@@ -20222,7 +20916,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
20222
20916
|
};
|
|
20223
20917
|
//#endregion
|
|
20224
20918
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
20225
|
-
const MESSAGE$
|
|
20919
|
+
const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
20226
20920
|
const CONTEXT_MODULES$1 = [
|
|
20227
20921
|
"react",
|
|
20228
20922
|
"use-context-selector",
|
|
@@ -20320,7 +21014,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
20320
21014
|
if (!isConstructedValue(innerExpression)) continue;
|
|
20321
21015
|
context.report({
|
|
20322
21016
|
node: attribute,
|
|
20323
|
-
message: MESSAGE$
|
|
21017
|
+
message: MESSAGE$47
|
|
20324
21018
|
});
|
|
20325
21019
|
}
|
|
20326
21020
|
}
|
|
@@ -21175,7 +21869,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
21175
21869
|
];
|
|
21176
21870
|
//#endregion
|
|
21177
21871
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
21178
|
-
const MESSAGE$
|
|
21872
|
+
const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
21179
21873
|
const isDataArrayPropName = (propName) => {
|
|
21180
21874
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
21181
21875
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -21262,7 +21956,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
21262
21956
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
21263
21957
|
context.report({
|
|
21264
21958
|
node,
|
|
21265
|
-
message: MESSAGE$
|
|
21959
|
+
message: MESSAGE$46
|
|
21266
21960
|
});
|
|
21267
21961
|
}
|
|
21268
21962
|
};
|
|
@@ -21520,7 +22214,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
21520
22214
|
]);
|
|
21521
22215
|
//#endregion
|
|
21522
22216
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
21523
|
-
const MESSAGE$
|
|
22217
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
21524
22218
|
const isAccessorPredicateName = (propName) => {
|
|
21525
22219
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
21526
22220
|
if (propName.length <= prefix.length) continue;
|
|
@@ -21726,7 +22420,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
21726
22420
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
21727
22421
|
context.report({
|
|
21728
22422
|
node,
|
|
21729
|
-
message: MESSAGE$
|
|
22423
|
+
message: MESSAGE$45
|
|
21730
22424
|
});
|
|
21731
22425
|
}
|
|
21732
22426
|
};
|
|
@@ -21946,7 +22640,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
21946
22640
|
];
|
|
21947
22641
|
//#endregion
|
|
21948
22642
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
21949
|
-
const MESSAGE$
|
|
22643
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
21950
22644
|
const isConfigObjectPropName = (propName) => {
|
|
21951
22645
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
21952
22646
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -22035,7 +22729,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22035
22729
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
22036
22730
|
context.report({
|
|
22037
22731
|
node,
|
|
22038
|
-
message: MESSAGE$
|
|
22732
|
+
message: MESSAGE$44
|
|
22039
22733
|
});
|
|
22040
22734
|
}
|
|
22041
22735
|
};
|
|
@@ -22043,7 +22737,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22043
22737
|
});
|
|
22044
22738
|
//#endregion
|
|
22045
22739
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
22046
|
-
const MESSAGE$
|
|
22740
|
+
const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
22047
22741
|
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;
|
|
22048
22742
|
const resolveSettings$29 = (settings) => {
|
|
22049
22743
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -22081,7 +22775,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
22081
22775
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
22082
22776
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
22083
22777
|
node: attribute,
|
|
22084
|
-
message: MESSAGE$
|
|
22778
|
+
message: MESSAGE$43
|
|
22085
22779
|
});
|
|
22086
22780
|
}
|
|
22087
22781
|
} };
|
|
@@ -22701,7 +23395,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
22701
23395
|
});
|
|
22702
23396
|
//#endregion
|
|
22703
23397
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
22704
|
-
const MESSAGE$
|
|
23398
|
+
const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
|
|
22705
23399
|
const resolveSettings$25 = (settings) => {
|
|
22706
23400
|
const reactDoctor = settings?.["react-doctor"];
|
|
22707
23401
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -22744,7 +23438,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
22744
23438
|
}
|
|
22745
23439
|
context.report({
|
|
22746
23440
|
node: attribute,
|
|
22747
|
-
message: MESSAGE$
|
|
23441
|
+
message: MESSAGE$42
|
|
22748
23442
|
});
|
|
22749
23443
|
didReportInFile = true;
|
|
22750
23444
|
return;
|
|
@@ -23020,7 +23714,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
23020
23714
|
});
|
|
23021
23715
|
//#endregion
|
|
23022
23716
|
//#region src/plugin/rules/a11y/lang.ts
|
|
23023
|
-
const MESSAGE$
|
|
23717
|
+
const MESSAGE$41 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
|
|
23024
23718
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
23025
23719
|
"aa",
|
|
23026
23720
|
"ab",
|
|
@@ -23500,7 +24194,7 @@ const lang = defineRule({
|
|
|
23500
24194
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
23501
24195
|
context.report({
|
|
23502
24196
|
node: langAttr,
|
|
23503
|
-
message: MESSAGE$
|
|
24197
|
+
message: MESSAGE$41
|
|
23504
24198
|
});
|
|
23505
24199
|
return;
|
|
23506
24200
|
}
|
|
@@ -23509,7 +24203,7 @@ const lang = defineRule({
|
|
|
23509
24203
|
if (value === null) return;
|
|
23510
24204
|
if (!isValidLangTag(value)) context.report({
|
|
23511
24205
|
node: langAttr,
|
|
23512
|
-
message: MESSAGE$
|
|
24206
|
+
message: MESSAGE$41
|
|
23513
24207
|
});
|
|
23514
24208
|
} })
|
|
23515
24209
|
});
|
|
@@ -23560,7 +24254,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
23560
24254
|
});
|
|
23561
24255
|
//#endregion
|
|
23562
24256
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
23563
|
-
const MESSAGE$
|
|
24257
|
+
const MESSAGE$40 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
23564
24258
|
const DEFAULT_AUDIO = ["audio"];
|
|
23565
24259
|
const DEFAULT_VIDEO = ["video"];
|
|
23566
24260
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -23649,7 +24343,7 @@ const mediaHasCaption = defineRule({
|
|
|
23649
24343
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
23650
24344
|
context.report({
|
|
23651
24345
|
node: node.name,
|
|
23652
|
-
message: MESSAGE$
|
|
24346
|
+
message: MESSAGE$40
|
|
23653
24347
|
});
|
|
23654
24348
|
return;
|
|
23655
24349
|
}
|
|
@@ -23668,7 +24362,7 @@ const mediaHasCaption = defineRule({
|
|
|
23668
24362
|
return kindValue.value.toLowerCase() === "captions";
|
|
23669
24363
|
})) context.report({
|
|
23670
24364
|
node: node.name,
|
|
23671
|
-
message: MESSAGE$
|
|
24365
|
+
message: MESSAGE$40
|
|
23672
24366
|
});
|
|
23673
24367
|
} };
|
|
23674
24368
|
}
|
|
@@ -25211,7 +25905,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
25211
25905
|
});
|
|
25212
25906
|
//#endregion
|
|
25213
25907
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
25214
|
-
const MESSAGE$
|
|
25908
|
+
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
25215
25909
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
25216
25910
|
const noAccessKey = defineRule({
|
|
25217
25911
|
id: "no-access-key",
|
|
@@ -25230,7 +25924,7 @@ const noAccessKey = defineRule({
|
|
|
25230
25924
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
25231
25925
|
context.report({
|
|
25232
25926
|
node: accessKey,
|
|
25233
|
-
message: MESSAGE$
|
|
25927
|
+
message: MESSAGE$39
|
|
25234
25928
|
});
|
|
25235
25929
|
return;
|
|
25236
25930
|
}
|
|
@@ -25240,7 +25934,7 @@ const noAccessKey = defineRule({
|
|
|
25240
25934
|
if (isUndefinedIdentifier(expression)) return;
|
|
25241
25935
|
context.report({
|
|
25242
25936
|
node: accessKey,
|
|
25243
|
-
message: MESSAGE$
|
|
25937
|
+
message: MESSAGE$39
|
|
25244
25938
|
});
|
|
25245
25939
|
}
|
|
25246
25940
|
} };
|
|
@@ -26117,7 +26811,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
26117
26811
|
};
|
|
26118
26812
|
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
26119
26813
|
if (!setterRef.resolved) return false;
|
|
26120
|
-
const componentFunction = findEnclosingFunction(effectNode);
|
|
26814
|
+
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
26121
26815
|
if (!componentFunction) return false;
|
|
26122
26816
|
for (const reference of setterRef.resolved.references) {
|
|
26123
26817
|
if (reference.init) continue;
|
|
@@ -27095,7 +27789,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
27095
27789
|
});
|
|
27096
27790
|
//#endregion
|
|
27097
27791
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
27098
|
-
const MESSAGE$
|
|
27792
|
+
const MESSAGE$38 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
|
|
27099
27793
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
27100
27794
|
"button",
|
|
27101
27795
|
"embed",
|
|
@@ -27207,7 +27901,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
27207
27901
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
27208
27902
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
27209
27903
|
node: ariaHidden,
|
|
27210
|
-
message: MESSAGE$
|
|
27904
|
+
message: MESSAGE$38
|
|
27211
27905
|
});
|
|
27212
27906
|
} })
|
|
27213
27907
|
});
|
|
@@ -28129,7 +28823,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
28129
28823
|
};
|
|
28130
28824
|
//#endregion
|
|
28131
28825
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
28132
|
-
const MESSAGE$
|
|
28826
|
+
const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
|
|
28133
28827
|
const SECOND_INDEX_METHODS = new Set([
|
|
28134
28828
|
"every",
|
|
28135
28829
|
"filter",
|
|
@@ -28248,14 +28942,14 @@ const noArrayIndexKey = defineRule({
|
|
|
28248
28942
|
if (propName !== "key") continue;
|
|
28249
28943
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
28250
28944
|
node: property,
|
|
28251
|
-
message: MESSAGE$
|
|
28945
|
+
message: MESSAGE$37
|
|
28252
28946
|
});
|
|
28253
28947
|
}
|
|
28254
28948
|
} })
|
|
28255
28949
|
});
|
|
28256
28950
|
//#endregion
|
|
28257
28951
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
28258
|
-
const MESSAGE$
|
|
28952
|
+
const MESSAGE$36 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
|
|
28259
28953
|
const noAsyncEffectCallback = defineRule({
|
|
28260
28954
|
id: "no-async-effect-callback",
|
|
28261
28955
|
title: "Async effect callback",
|
|
@@ -28273,13 +28967,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
28273
28967
|
if (!callback.async) return;
|
|
28274
28968
|
context.report({
|
|
28275
28969
|
node: callback,
|
|
28276
|
-
message: MESSAGE$
|
|
28970
|
+
message: MESSAGE$36
|
|
28277
28971
|
});
|
|
28278
28972
|
} })
|
|
28279
28973
|
});
|
|
28280
28974
|
//#endregion
|
|
28281
28975
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
28282
|
-
const MESSAGE$
|
|
28976
|
+
const MESSAGE$35 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
28283
28977
|
const resolveSettings$21 = (settings) => {
|
|
28284
28978
|
const reactDoctor = settings?.["react-doctor"];
|
|
28285
28979
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -28376,7 +29070,7 @@ const noAutofocus = defineRule({
|
|
|
28376
29070
|
if (isConditionallyRendered(node)) return;
|
|
28377
29071
|
context.report({
|
|
28378
29072
|
node: autoFocusAttribute,
|
|
28379
|
-
message: MESSAGE$
|
|
29073
|
+
message: MESSAGE$35
|
|
28380
29074
|
});
|
|
28381
29075
|
} };
|
|
28382
29076
|
}
|
|
@@ -28890,138 +29584,6 @@ const noBarrelImport = defineRule({
|
|
|
28890
29584
|
}
|
|
28891
29585
|
});
|
|
28892
29586
|
//#endregion
|
|
28893
|
-
//#region src/plugin/utils/has-static-property-write-before.ts
|
|
28894
|
-
const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
28895
|
-
const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
|
|
28896
|
-
"CatchClause",
|
|
28897
|
-
"ConditionalExpression",
|
|
28898
|
-
"DoWhileStatement",
|
|
28899
|
-
"ForInStatement",
|
|
28900
|
-
"ForOfStatement",
|
|
28901
|
-
"ForStatement",
|
|
28902
|
-
"IfStatement",
|
|
28903
|
-
"LogicalExpression",
|
|
28904
|
-
"SwitchCase",
|
|
28905
|
-
"SwitchStatement",
|
|
28906
|
-
"TryStatement",
|
|
28907
|
-
"WhileStatement"
|
|
28908
|
-
]);
|
|
28909
|
-
const getResolvedStaticPropertyName = (memberExpression, scopes) => {
|
|
28910
|
-
if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
|
|
28911
|
-
const directPropertyName = getStaticPropertyName(memberExpression);
|
|
28912
|
-
if (directPropertyName || !memberExpression.computed) return directPropertyName;
|
|
28913
|
-
const property = stripParenExpression(memberExpression.property);
|
|
28914
|
-
if (!isNodeOfType(property, "Identifier")) return null;
|
|
28915
|
-
const propertySymbol = resolveConstIdentifierAlias(property, scopes);
|
|
28916
|
-
const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
|
|
28917
|
-
return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
|
|
28918
|
-
};
|
|
28919
|
-
const collectScopeSymbols = (scope, symbols) => {
|
|
28920
|
-
symbols.push(...scope.symbols);
|
|
28921
|
-
for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
|
|
28922
|
-
};
|
|
28923
|
-
const getEquivalentSymbols = (identifier, scopes) => {
|
|
28924
|
-
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
28925
|
-
if (!rootSymbol) return [];
|
|
28926
|
-
let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
|
|
28927
|
-
if (!symbolsByRootId) {
|
|
28928
|
-
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
28929
|
-
equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
28930
|
-
}
|
|
28931
|
-
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
28932
|
-
if (cachedSymbols) return cachedSymbols;
|
|
28933
|
-
const allSymbols = [];
|
|
28934
|
-
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
28935
|
-
const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
|
|
28936
|
-
symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
|
|
28937
|
-
return equivalentSymbols;
|
|
28938
|
-
};
|
|
28939
|
-
const findExecutionBoundary = (node) => {
|
|
28940
|
-
let current = node;
|
|
28941
|
-
while (current) {
|
|
28942
|
-
if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
|
|
28943
|
-
current = current.parent ?? null;
|
|
28944
|
-
}
|
|
28945
|
-
return null;
|
|
28946
|
-
};
|
|
28947
|
-
const isOnUnconditionalPath = (node, boundary) => {
|
|
28948
|
-
let current = node.parent ?? null;
|
|
28949
|
-
while (current && current !== boundary) {
|
|
28950
|
-
if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
|
|
28951
|
-
current = current.parent ?? null;
|
|
28952
|
-
}
|
|
28953
|
-
return current === boundary;
|
|
28954
|
-
};
|
|
28955
|
-
const findFunctionBindingIdentifier = (functionNode) => {
|
|
28956
|
-
if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
|
|
28957
|
-
const parent = functionNode.parent;
|
|
28958
|
-
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
28959
|
-
return null;
|
|
28960
|
-
};
|
|
28961
|
-
const findDirectCall = (identifier) => {
|
|
28962
|
-
let callee = identifier;
|
|
28963
|
-
let parent = callee.parent;
|
|
28964
|
-
while (parent && stripParenExpression(parent) === identifier) {
|
|
28965
|
-
callee = parent;
|
|
28966
|
-
parent = callee.parent;
|
|
28967
|
-
}
|
|
28968
|
-
return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
28969
|
-
};
|
|
28970
|
-
const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
28971
|
-
if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
|
|
28972
|
-
visitedFunctionNodes.add(functionNode);
|
|
28973
|
-
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
28974
|
-
if (!referenceBoundary) return false;
|
|
28975
|
-
const invocationCalls = [];
|
|
28976
|
-
const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
|
|
28977
|
-
if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
|
|
28978
|
-
const call = findDirectCall(reference.identifier);
|
|
28979
|
-
if (call) invocationCalls.push(call);
|
|
28980
|
-
}
|
|
28981
|
-
else {
|
|
28982
|
-
const call = findDirectCall(functionNode);
|
|
28983
|
-
if (call) invocationCalls.push(call);
|
|
28984
|
-
}
|
|
28985
|
-
return invocationCalls.some((call) => {
|
|
28986
|
-
if (call.range[0] >= referenceNode.range[0]) return false;
|
|
28987
|
-
const callBoundary = findExecutionBoundary(call);
|
|
28988
|
-
if (!callBoundary) return false;
|
|
28989
|
-
if (callBoundary === referenceBoundary) return true;
|
|
28990
|
-
if (!isFunctionLike$2(callBoundary)) return false;
|
|
28991
|
-
return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
|
|
28992
|
-
});
|
|
28993
|
-
};
|
|
28994
|
-
const isMemberWriteTarget = (memberExpression) => {
|
|
28995
|
-
const parent = memberExpression.parent;
|
|
28996
|
-
if (!parent) return false;
|
|
28997
|
-
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
|
|
28998
|
-
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
|
|
28999
|
-
return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
|
|
29000
|
-
};
|
|
29001
|
-
const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
29002
|
-
let parent = reference.identifier.parent;
|
|
29003
|
-
while (parent && stripParenExpression(parent) === reference.identifier) parent = parent.parent;
|
|
29004
|
-
if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== reference.identifier || getResolvedStaticPropertyName(parent, scopes) !== propertyName || !isMemberWriteTarget(parent)) return false;
|
|
29005
|
-
const writeBoundary = findExecutionBoundary(parent);
|
|
29006
|
-
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
29007
|
-
if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(parent, writeBoundary)) return false;
|
|
29008
|
-
if (writeBoundary === referenceBoundary) return parent.range[0] < referenceNode.range[0];
|
|
29009
|
-
return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
|
|
29010
|
-
});
|
|
29011
|
-
const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
29012
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
29013
|
-
return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
|
|
29014
|
-
};
|
|
29015
|
-
//#endregion
|
|
29016
|
-
//#region src/plugin/utils/has-symbol-write-before.ts
|
|
29017
|
-
const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
29018
|
-
if (reference.flag === "read") return false;
|
|
29019
|
-
const writeFunction = findEnclosingFunction(reference.identifier);
|
|
29020
|
-
if (writeFunction === findEnclosingFunction(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
|
|
29021
|
-
if (!writeFunction) return true;
|
|
29022
|
-
return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
|
|
29023
|
-
});
|
|
29024
|
-
//#endregion
|
|
29025
29587
|
//#region src/plugin/utils/has-stable-call-target.ts
|
|
29026
29588
|
const hasStableCallTarget = (callExpression, scopes) => {
|
|
29027
29589
|
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -29349,7 +29911,7 @@ const noChainStateUpdates = defineRule({
|
|
|
29349
29911
|
});
|
|
29350
29912
|
//#endregion
|
|
29351
29913
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
29352
|
-
const MESSAGE$
|
|
29914
|
+
const MESSAGE$34 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
29353
29915
|
const noChildrenProp = defineRule({
|
|
29354
29916
|
id: "no-children-prop",
|
|
29355
29917
|
title: "Children passed as a prop",
|
|
@@ -29361,7 +29923,7 @@ const noChildrenProp = defineRule({
|
|
|
29361
29923
|
if (node.name.name !== "children") return;
|
|
29362
29924
|
context.report({
|
|
29363
29925
|
node: node.name,
|
|
29364
|
-
message: MESSAGE$
|
|
29926
|
+
message: MESSAGE$34
|
|
29365
29927
|
});
|
|
29366
29928
|
},
|
|
29367
29929
|
CallExpression(node) {
|
|
@@ -29374,7 +29936,7 @@ const noChildrenProp = defineRule({
|
|
|
29374
29936
|
const propertyKey = property.key;
|
|
29375
29937
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
29376
29938
|
node: propertyKey,
|
|
29377
|
-
message: MESSAGE$
|
|
29939
|
+
message: MESSAGE$34
|
|
29378
29940
|
});
|
|
29379
29941
|
}
|
|
29380
29942
|
}
|
|
@@ -29382,7 +29944,7 @@ const noChildrenProp = defineRule({
|
|
|
29382
29944
|
});
|
|
29383
29945
|
//#endregion
|
|
29384
29946
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
29385
|
-
const MESSAGE$
|
|
29947
|
+
const MESSAGE$33 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
29386
29948
|
const noCloneElement = defineRule({
|
|
29387
29949
|
id: "no-clone-element",
|
|
29388
29950
|
title: "cloneElement makes child props fragile",
|
|
@@ -29395,7 +29957,7 @@ const noCloneElement = defineRule({
|
|
|
29395
29957
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
29396
29958
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
29397
29959
|
node: callee,
|
|
29398
|
-
message: MESSAGE$
|
|
29960
|
+
message: MESSAGE$33
|
|
29399
29961
|
});
|
|
29400
29962
|
return;
|
|
29401
29963
|
}
|
|
@@ -29408,7 +29970,7 @@ const noCloneElement = defineRule({
|
|
|
29408
29970
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
29409
29971
|
context.report({
|
|
29410
29972
|
node: callee,
|
|
29411
|
-
message: MESSAGE$
|
|
29973
|
+
message: MESSAGE$33
|
|
29412
29974
|
});
|
|
29413
29975
|
}
|
|
29414
29976
|
} })
|
|
@@ -29428,7 +29990,7 @@ const getCallMethodName = (callee) => {
|
|
|
29428
29990
|
};
|
|
29429
29991
|
//#endregion
|
|
29430
29992
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
29431
|
-
const MESSAGE$
|
|
29993
|
+
const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
29432
29994
|
const CONTEXT_MODULES = [
|
|
29433
29995
|
"react",
|
|
29434
29996
|
"use-context-selector",
|
|
@@ -29476,13 +30038,13 @@ const noCreateContextInRender = defineRule({
|
|
|
29476
30038
|
if (!componentOrHookName) return;
|
|
29477
30039
|
context.report({
|
|
29478
30040
|
node,
|
|
29479
|
-
message: `${MESSAGE$
|
|
30041
|
+
message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
|
|
29480
30042
|
});
|
|
29481
30043
|
} })
|
|
29482
30044
|
});
|
|
29483
30045
|
//#endregion
|
|
29484
30046
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
29485
|
-
const MESSAGE$
|
|
30047
|
+
const MESSAGE$31 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
|
|
29486
30048
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
29487
30049
|
const parent = functionNode.parent;
|
|
29488
30050
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -29490,8 +30052,8 @@ const isUseMemoCallbackArgument = (functionNode) => {
|
|
|
29490
30052
|
return isReactFunctionCall(parent, "useMemo");
|
|
29491
30053
|
};
|
|
29492
30054
|
const findEnclosingRenderFunction = (node) => {
|
|
29493
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
29494
|
-
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
30055
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
30056
|
+
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
29495
30057
|
return enclosingFunction;
|
|
29496
30058
|
};
|
|
29497
30059
|
const noCreateRefInFunctionComponent = defineRule({
|
|
@@ -29512,7 +30074,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
29512
30074
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
|
|
29513
30075
|
context.report({
|
|
29514
30076
|
node,
|
|
29515
|
-
message: MESSAGE$
|
|
30077
|
+
message: MESSAGE$31
|
|
29516
30078
|
});
|
|
29517
30079
|
} })
|
|
29518
30080
|
});
|
|
@@ -29652,7 +30214,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
29652
30214
|
});
|
|
29653
30215
|
//#endregion
|
|
29654
30216
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
29655
|
-
const MESSAGE$
|
|
30217
|
+
const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
29656
30218
|
const noDanger = defineRule({
|
|
29657
30219
|
id: "no-danger",
|
|
29658
30220
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -29666,7 +30228,7 @@ const noDanger = defineRule({
|
|
|
29666
30228
|
if (!propAttribute) return;
|
|
29667
30229
|
context.report({
|
|
29668
30230
|
node: propAttribute.name,
|
|
29669
|
-
message: MESSAGE$
|
|
30231
|
+
message: MESSAGE$30
|
|
29670
30232
|
});
|
|
29671
30233
|
},
|
|
29672
30234
|
CallExpression(node) {
|
|
@@ -29678,7 +30240,7 @@ const noDanger = defineRule({
|
|
|
29678
30240
|
const propertyKey = property.key;
|
|
29679
30241
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
29680
30242
|
node: propertyKey,
|
|
29681
|
-
message: MESSAGE$
|
|
30243
|
+
message: MESSAGE$30
|
|
29682
30244
|
});
|
|
29683
30245
|
}
|
|
29684
30246
|
}
|
|
@@ -29701,7 +30263,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
29701
30263
|
};
|
|
29702
30264
|
//#endregion
|
|
29703
30265
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
29704
|
-
const MESSAGE$
|
|
30266
|
+
const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
29705
30267
|
const mergePropsShape = (target, source) => {
|
|
29706
30268
|
target.hasDangerously ||= source.hasDangerously;
|
|
29707
30269
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -29776,7 +30338,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29776
30338
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
29777
30339
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
29778
30340
|
node: opening,
|
|
29779
|
-
message: MESSAGE$
|
|
30341
|
+
message: MESSAGE$29
|
|
29780
30342
|
});
|
|
29781
30343
|
},
|
|
29782
30344
|
CallExpression(node) {
|
|
@@ -29789,7 +30351,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29789
30351
|
const positionalChildren = node.arguments.slice(2);
|
|
29790
30352
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
29791
30353
|
node,
|
|
29792
|
-
message: MESSAGE$
|
|
30354
|
+
message: MESSAGE$29
|
|
29793
30355
|
});
|
|
29794
30356
|
}
|
|
29795
30357
|
})
|
|
@@ -30542,7 +31104,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
30542
31104
|
const isEffectDrivenResync = (useStateCall) => {
|
|
30543
31105
|
const setterName = getStateSetterName(useStateCall);
|
|
30544
31106
|
if (!setterName) return false;
|
|
30545
|
-
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31107
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
30546
31108
|
if (!componentFunction) return false;
|
|
30547
31109
|
let isExempt = false;
|
|
30548
31110
|
walkAst(componentFunction, (child) => {
|
|
@@ -30646,7 +31208,7 @@ const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
|
|
|
30646
31208
|
const setterName = getStateSetterName(useStateCall);
|
|
30647
31209
|
if (!setterName) return false;
|
|
30648
31210
|
const stateValueName = getStateValueName(useStateCall);
|
|
30649
|
-
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31211
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
30650
31212
|
if (!componentFunction) return false;
|
|
30651
31213
|
let isExempt = false;
|
|
30652
31214
|
walkAst(componentFunction, (child) => {
|
|
@@ -30692,7 +31254,7 @@ const noDerivedUseState = defineRule({
|
|
|
30692
31254
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
30693
31255
|
if (isEffectDrivenResync(node)) return;
|
|
30694
31256
|
if (isNextjsDataFetchingPage(node)) return;
|
|
30695
|
-
const componentFunction = findEnclosingFunction(node);
|
|
31257
|
+
const componentFunction = findEnclosingFunction$1(node);
|
|
30696
31258
|
if (componentFunction) {
|
|
30697
31259
|
if (isDefaultedDestructuredProp(componentFunction, propName)) return;
|
|
30698
31260
|
if (isDraftCommittedToParent(componentFunction, getStateValueName(node), propStackTracker.isPropName)) return;
|
|
@@ -30754,7 +31316,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
30754
31316
|
//#endregion
|
|
30755
31317
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
30756
31318
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
30757
|
-
const MESSAGE$
|
|
31319
|
+
const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
30758
31320
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
30759
31321
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
30760
31322
|
let ancestor = setStateCall.parent;
|
|
@@ -30882,7 +31444,7 @@ const noDidMountSetState = defineRule({
|
|
|
30882
31444
|
}
|
|
30883
31445
|
context.report({
|
|
30884
31446
|
node: node.callee,
|
|
30885
|
-
message: MESSAGE$
|
|
31447
|
+
message: MESSAGE$28
|
|
30886
31448
|
});
|
|
30887
31449
|
} };
|
|
30888
31450
|
}
|
|
@@ -30890,7 +31452,7 @@ const noDidMountSetState = defineRule({
|
|
|
30890
31452
|
//#endregion
|
|
30891
31453
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
30892
31454
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
30893
|
-
const MESSAGE$
|
|
31455
|
+
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
30894
31456
|
const EQUALITY_OPERATORS = new Set([
|
|
30895
31457
|
"==",
|
|
30896
31458
|
"===",
|
|
@@ -31064,7 +31626,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
31064
31626
|
if (isInsideDiffGuard(node)) return;
|
|
31065
31627
|
context.report({
|
|
31066
31628
|
node: node.callee,
|
|
31067
|
-
message: MESSAGE$
|
|
31629
|
+
message: MESSAGE$27
|
|
31068
31630
|
});
|
|
31069
31631
|
} };
|
|
31070
31632
|
}
|
|
@@ -31087,7 +31649,7 @@ const isStateMemberExpression = (node) => {
|
|
|
31087
31649
|
};
|
|
31088
31650
|
//#endregion
|
|
31089
31651
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
31090
|
-
const MESSAGE$
|
|
31652
|
+
const MESSAGE$26 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
|
|
31091
31653
|
const shouldIgnoreMutation = (node) => {
|
|
31092
31654
|
let isConstructor = false;
|
|
31093
31655
|
let isInsideCallExpression = false;
|
|
@@ -31109,7 +31671,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
31109
31671
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
31110
31672
|
context.report({
|
|
31111
31673
|
node: reportNode,
|
|
31112
|
-
message: MESSAGE$
|
|
31674
|
+
message: MESSAGE$26
|
|
31113
31675
|
});
|
|
31114
31676
|
};
|
|
31115
31677
|
const noDirectMutationState = defineRule({
|
|
@@ -31460,7 +32022,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
31460
32022
|
});
|
|
31461
32023
|
//#endregion
|
|
31462
32024
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
31463
|
-
const MESSAGE$
|
|
32025
|
+
const MESSAGE$25 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
31464
32026
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
31465
32027
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
31466
32028
|
const noDocumentWrite = defineRule({
|
|
@@ -31477,7 +32039,7 @@ const noDocumentWrite = defineRule({
|
|
|
31477
32039
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
31478
32040
|
context.report({
|
|
31479
32041
|
node,
|
|
31480
|
-
message: MESSAGE$
|
|
32042
|
+
message: MESSAGE$25
|
|
31481
32043
|
});
|
|
31482
32044
|
} })
|
|
31483
32045
|
});
|
|
@@ -32530,7 +33092,7 @@ const isFunctionUsedOutsideHandlers = (analysis, functionNode, componentFunction
|
|
|
32530
33092
|
if (isInsideInlineEventHandler(identifier, componentFunction)) return false;
|
|
32531
33093
|
const parent = identifier.parent;
|
|
32532
33094
|
if (parent && isNodeOfType(parent, "CallExpression") && parent.callee === identifier) {
|
|
32533
|
-
if (findEnclosingFunction(parent) === functionNode) return false;
|
|
33095
|
+
if (findEnclosingFunction$1(parent) === functionNode) return false;
|
|
32534
33096
|
if (isInsideProvenEventHandler(analysis, parent, componentFunction, false)) return false;
|
|
32535
33097
|
}
|
|
32536
33098
|
return true;
|
|
@@ -32576,7 +33138,7 @@ const isStateWrittenOnlyFromEventHandlers = (analysis, stateReference) => {
|
|
|
32576
33138
|
if (!setterVariable) return false;
|
|
32577
33139
|
const stateDeclarator = getUseStateDecl(analysis, stateReference);
|
|
32578
33140
|
if (!stateDeclarator) return false;
|
|
32579
|
-
const componentFunction = findEnclosingFunction(stateDeclarator);
|
|
33141
|
+
const componentFunction = findEnclosingFunction$1(stateDeclarator);
|
|
32580
33142
|
if (!componentFunction) return false;
|
|
32581
33143
|
let hasWriter = false;
|
|
32582
33144
|
for (const reference of setterVariable.references) {
|
|
@@ -33638,8 +34200,8 @@ const isAwaitedInFunction = (request, functionNode) => {
|
|
|
33638
34200
|
return false;
|
|
33639
34201
|
};
|
|
33640
34202
|
const isCompletionSinkForRequest = (completionSink, request) => {
|
|
33641
|
-
const requestFunction = findEnclosingFunction(request);
|
|
33642
|
-
const completionSinkFunction = findEnclosingFunction(completionSink);
|
|
34203
|
+
const requestFunction = findEnclosingFunction$1(request);
|
|
34204
|
+
const completionSinkFunction = findEnclosingFunction$1(completionSink);
|
|
33643
34205
|
if (!requestFunction || !completionSinkFunction) return false;
|
|
33644
34206
|
if (requestFunction === completionSinkFunction) {
|
|
33645
34207
|
if (!isAwaitedInFunction(request, requestFunction)) return false;
|
|
@@ -33695,7 +34257,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
33695
34257
|
"ReactDOM",
|
|
33696
34258
|
"ReactDom"
|
|
33697
34259
|
]);
|
|
33698
|
-
const MESSAGE$
|
|
34260
|
+
const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
33699
34261
|
const noFindDomNode = defineRule({
|
|
33700
34262
|
id: "no-find-dom-node",
|
|
33701
34263
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -33706,7 +34268,7 @@ const noFindDomNode = defineRule({
|
|
|
33706
34268
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
33707
34269
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
33708
34270
|
node: callee,
|
|
33709
|
-
message: MESSAGE$
|
|
34271
|
+
message: MESSAGE$24
|
|
33710
34272
|
});
|
|
33711
34273
|
return;
|
|
33712
34274
|
}
|
|
@@ -33717,7 +34279,7 @@ const noFindDomNode = defineRule({
|
|
|
33717
34279
|
if (callee.property.name !== "findDOMNode") return;
|
|
33718
34280
|
context.report({
|
|
33719
34281
|
node: callee.property,
|
|
33720
|
-
message: MESSAGE$
|
|
34282
|
+
message: MESSAGE$24
|
|
33721
34283
|
});
|
|
33722
34284
|
}
|
|
33723
34285
|
} })
|
|
@@ -33926,13 +34488,6 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
33926
34488
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
33927
34489
|
};
|
|
33928
34490
|
//#endregion
|
|
33929
|
-
//#region src/plugin/utils/is-type-only-import.ts
|
|
33930
|
-
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
33931
|
-
if (!specifiers || specifiers.length === 0) return false;
|
|
33932
|
-
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
33933
|
-
};
|
|
33934
|
-
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
33935
|
-
//#endregion
|
|
33936
34491
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
33937
34492
|
const isLibraryDevPage = (filename) => {
|
|
33938
34493
|
if (!filename) return false;
|
|
@@ -34496,7 +35051,7 @@ const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
|
|
|
34496
35051
|
return attribute ? !isEventHandlerAttribute(attribute) : true;
|
|
34497
35052
|
}
|
|
34498
35053
|
if (isNodeOfType(parentNode, "ReturnStatement")) {
|
|
34499
|
-
if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
|
|
35054
|
+
if (findEnclosingFunction$1(parentNode) === componentOrHookNode) return true;
|
|
34500
35055
|
}
|
|
34501
35056
|
if (parentNode === componentOrHookNode) return isFunctionLike$2(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
|
|
34502
35057
|
if (isFunctionLike$2(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
|
|
@@ -34619,7 +35174,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34619
35174
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
34620
35175
|
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
34621
35176
|
if (!componentOrHookNode) return;
|
|
34622
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
35177
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
34623
35178
|
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
|
|
34624
35179
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
34625
35180
|
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
@@ -34631,7 +35186,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34631
35186
|
});
|
|
34632
35187
|
//#endregion
|
|
34633
35188
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
34634
|
-
const MESSAGE$
|
|
35189
|
+
const MESSAGE$23 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
|
|
34635
35190
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
34636
35191
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
34637
35192
|
title: "Lazy image with high fetchPriority",
|
|
@@ -34645,7 +35200,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
34645
35200
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
34646
35201
|
context.report({
|
|
34647
35202
|
node: node.name,
|
|
34648
|
-
message: MESSAGE$
|
|
35203
|
+
message: MESSAGE$23
|
|
34649
35204
|
});
|
|
34650
35205
|
} })
|
|
34651
35206
|
});
|
|
@@ -34834,7 +35389,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
34834
35389
|
});
|
|
34835
35390
|
//#endregion
|
|
34836
35391
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
34837
|
-
const MESSAGE$
|
|
35392
|
+
const MESSAGE$22 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
34838
35393
|
const REACT_USE_REF_OPTIONS = {
|
|
34839
35394
|
allowGlobalReactNamespace: false,
|
|
34840
35395
|
allowUnboundBareCalls: false
|
|
@@ -34935,7 +35490,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34935
35490
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
34936
35491
|
context.report({
|
|
34937
35492
|
node: indeterminateAttribute,
|
|
34938
|
-
message: MESSAGE$
|
|
35493
|
+
message: MESSAGE$22
|
|
34939
35494
|
});
|
|
34940
35495
|
},
|
|
34941
35496
|
CallExpression(node) {
|
|
@@ -34944,7 +35499,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34944
35499
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
34945
35500
|
context.report({
|
|
34946
35501
|
node,
|
|
34947
|
-
message: MESSAGE$
|
|
35502
|
+
message: MESSAGE$22
|
|
34948
35503
|
});
|
|
34949
35504
|
}
|
|
34950
35505
|
};
|
|
@@ -35067,10 +35622,10 @@ const isInsideInstanceField = (node) => {
|
|
|
35067
35622
|
return false;
|
|
35068
35623
|
};
|
|
35069
35624
|
const isOneShotModuleInitialization = (node, context) => {
|
|
35070
|
-
let functionNode = findEnclosingFunction(node);
|
|
35625
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
35071
35626
|
while (functionNode) {
|
|
35072
35627
|
if (!executesDuringRender(functionNode, context.scopes)) return false;
|
|
35073
|
-
functionNode = findEnclosingFunction(functionNode);
|
|
35628
|
+
functionNode = findEnclosingFunction$1(functionNode);
|
|
35074
35629
|
}
|
|
35075
35630
|
return !isInsideInstanceField(node);
|
|
35076
35631
|
};
|
|
@@ -35232,7 +35787,7 @@ const noIsMounted = defineRule({
|
|
|
35232
35787
|
});
|
|
35233
35788
|
//#endregion
|
|
35234
35789
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
35235
|
-
const MESSAGE$
|
|
35790
|
+
const MESSAGE$21 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
|
|
35236
35791
|
const isJsonMethodCall = (node, method) => {
|
|
35237
35792
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
35238
35793
|
const callee = node.callee;
|
|
@@ -35296,13 +35851,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
35296
35851
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
35297
35852
|
context.report({
|
|
35298
35853
|
node,
|
|
35299
|
-
message: MESSAGE$
|
|
35854
|
+
message: MESSAGE$21
|
|
35300
35855
|
});
|
|
35301
35856
|
} })
|
|
35302
35857
|
});
|
|
35303
35858
|
//#endregion
|
|
35304
35859
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
35305
|
-
const MESSAGE$
|
|
35860
|
+
const MESSAGE$20 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
35306
35861
|
const isJsxElementTypeReference = (node) => {
|
|
35307
35862
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
35308
35863
|
const typeName = node.typeName;
|
|
@@ -35356,7 +35911,7 @@ const noJsxElementType = defineRule({
|
|
|
35356
35911
|
if (isJsxImported) return;
|
|
35357
35912
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
35358
35913
|
node: typeAnnotation,
|
|
35359
|
-
message: MESSAGE$
|
|
35914
|
+
message: MESSAGE$20
|
|
35360
35915
|
});
|
|
35361
35916
|
}
|
|
35362
35917
|
};
|
|
@@ -35588,6 +36143,221 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
35588
36143
|
}
|
|
35589
36144
|
});
|
|
35590
36145
|
//#endregion
|
|
36146
|
+
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
36147
|
+
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
36148
|
+
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
36149
|
+
const expression = stripParenExpression(node);
|
|
36150
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
36151
|
+
const propertyName = getStaticPropertyName(expression);
|
|
36152
|
+
const receiver = stripParenExpression(expression.object);
|
|
36153
|
+
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
36154
|
+
}
|
|
36155
|
+
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36156
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
36157
|
+
const symbol = scopes.symbolFor(expression);
|
|
36158
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
36159
|
+
visitedSymbolIds.add(symbol.id);
|
|
36160
|
+
if (isImportedFromReact(symbol)) {
|
|
36161
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
36162
|
+
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
36163
|
+
}
|
|
36164
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36165
|
+
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
36166
|
+
};
|
|
36167
|
+
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36168
|
+
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
36169
|
+
visitedClassNodes.add(classNode);
|
|
36170
|
+
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36171
|
+
};
|
|
36172
|
+
//#endregion
|
|
36173
|
+
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
36174
|
+
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
36175
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
36176
|
+
let containsReactHookCall = false;
|
|
36177
|
+
walkAst(functionNode.body, (node) => {
|
|
36178
|
+
if (containsReactHookCall) return false;
|
|
36179
|
+
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
36180
|
+
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36181
|
+
containsReactHookCall = true;
|
|
36182
|
+
return false;
|
|
36183
|
+
}
|
|
36184
|
+
});
|
|
36185
|
+
return containsReactHookCall;
|
|
36186
|
+
};
|
|
36187
|
+
//#endregion
|
|
36188
|
+
//#region src/plugin/utils/function-returns-props-children.ts
|
|
36189
|
+
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
36190
|
+
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
36191
|
+
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
36192
|
+
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
36193
|
+
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
36194
|
+
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
36195
|
+
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
36196
|
+
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
36197
|
+
const propertyValue = stripParenExpression(property.value);
|
|
36198
|
+
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
36199
|
+
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
36200
|
+
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
36201
|
+
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
36202
|
+
}
|
|
36203
|
+
}
|
|
36204
|
+
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
36205
|
+
const candidate = stripParenExpression(expression);
|
|
36206
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
36207
|
+
const symbol = scopes.symbolFor(candidate);
|
|
36208
|
+
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
36209
|
+
}
|
|
36210
|
+
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
36211
|
+
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
36212
|
+
const receiver = stripParenExpression(candidate.object);
|
|
36213
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
36214
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
36215
|
+
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
36216
|
+
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
36217
|
+
}, controlFlow);
|
|
36218
|
+
};
|
|
36219
|
+
//#endregion
|
|
36220
|
+
//#region src/plugin/utils/function-returns-only-null.ts
|
|
36221
|
+
const isNullExpression = (expression) => {
|
|
36222
|
+
const candidate = stripParenExpression(expression);
|
|
36223
|
+
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
36224
|
+
};
|
|
36225
|
+
const functionReturnsOnlyNull = (functionNode) => {
|
|
36226
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
36227
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
36228
|
+
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
36229
|
+
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
36230
|
+
};
|
|
36231
|
+
//#endregion
|
|
36232
|
+
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
36233
|
+
const findFactoryRoot = (node) => {
|
|
36234
|
+
const candidate = stripParenExpression(node);
|
|
36235
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
36236
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
36237
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
36238
|
+
return null;
|
|
36239
|
+
};
|
|
36240
|
+
const findFactoryPropertyName = (node) => {
|
|
36241
|
+
const candidate = stripParenExpression(node);
|
|
36242
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
36243
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
36244
|
+
return null;
|
|
36245
|
+
};
|
|
36246
|
+
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
36247
|
+
const candidate = stripParenExpression(expression);
|
|
36248
|
+
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
36249
|
+
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
36250
|
+
if (!factoryRoot) return false;
|
|
36251
|
+
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
36252
|
+
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
36253
|
+
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
36254
|
+
if (!symbol || symbol.kind !== "import") return false;
|
|
36255
|
+
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
36256
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
36257
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
36258
|
+
};
|
|
36259
|
+
//#endregion
|
|
36260
|
+
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
36261
|
+
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
36262
|
+
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
36263
|
+
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
36264
|
+
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36265
|
+
const candidate = stripParenExpression(expression);
|
|
36266
|
+
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
36267
|
+
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
36268
|
+
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
36269
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
36270
|
+
const symbol = scopes.symbolFor(candidate);
|
|
36271
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
36272
|
+
visitedSymbolIds.add(symbol.id);
|
|
36273
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
36274
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
36275
|
+
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
36276
|
+
}
|
|
36277
|
+
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
36278
|
+
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
36279
|
+
const factoryCallee = stripParenExpression(candidate.callee);
|
|
36280
|
+
if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
|
|
36281
|
+
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36282
|
+
const wrappedComponent = candidate.arguments[0];
|
|
36283
|
+
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
36284
|
+
}
|
|
36285
|
+
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
36286
|
+
const factory = candidate.arguments[0];
|
|
36287
|
+
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
36288
|
+
const unwrappedFactory = stripParenExpression(factory);
|
|
36289
|
+
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
36290
|
+
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
36291
|
+
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
36292
|
+
const returnedExpression = returnStatements[0]?.argument;
|
|
36293
|
+
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
36294
|
+
};
|
|
36295
|
+
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
36296
|
+
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
36297
|
+
for (const candidateSymbol of candidateSymbols) {
|
|
36298
|
+
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
36299
|
+
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
36300
|
+
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
36301
|
+
continue;
|
|
36302
|
+
}
|
|
36303
|
+
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
36304
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
36305
|
+
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
36306
|
+
continue;
|
|
36307
|
+
}
|
|
36308
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
36309
|
+
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
36310
|
+
continue;
|
|
36311
|
+
}
|
|
36312
|
+
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
36313
|
+
}
|
|
36314
|
+
return false;
|
|
36315
|
+
};
|
|
36316
|
+
//#endregion
|
|
36317
|
+
//#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
|
|
36318
|
+
const REACT_COMPONENT_TYPE_NAMES = new Set([
|
|
36319
|
+
"ComponentClass",
|
|
36320
|
+
"ComponentType",
|
|
36321
|
+
"FC",
|
|
36322
|
+
"FunctionComponent"
|
|
36323
|
+
]);
|
|
36324
|
+
const findVisibleSymbol = (identifier, scopes) => {
|
|
36325
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
36326
|
+
let scope = scopes.scopeFor(identifier);
|
|
36327
|
+
while (true) {
|
|
36328
|
+
const symbol = scope.symbolsByName.get(identifier.name);
|
|
36329
|
+
if (symbol) return symbol;
|
|
36330
|
+
if (!scope.parent) return null;
|
|
36331
|
+
scope = scope.parent;
|
|
36332
|
+
}
|
|
36333
|
+
};
|
|
36334
|
+
const isReactNamespaceType = (identifier, scopes) => {
|
|
36335
|
+
const symbol = findVisibleSymbol(identifier, scopes);
|
|
36336
|
+
return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
36337
|
+
};
|
|
36338
|
+
const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
|
|
36339
|
+
if (!typeNode) return false;
|
|
36340
|
+
if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36341
|
+
if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
|
|
36342
|
+
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
36343
|
+
const typeName = typeNode.typeName;
|
|
36344
|
+
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);
|
|
36345
|
+
if (!isNodeOfType(typeName, "Identifier")) return false;
|
|
36346
|
+
const typeSymbol = findVisibleSymbol(typeName, scopes);
|
|
36347
|
+
if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
|
|
36348
|
+
if (isImportedFromReact(typeSymbol)) {
|
|
36349
|
+
const importedName = getImportedName(typeSymbol.declarationNode);
|
|
36350
|
+
return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
|
|
36351
|
+
}
|
|
36352
|
+
if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
|
|
36353
|
+
visitedSymbolIds.add(typeSymbol.id);
|
|
36354
|
+
return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36355
|
+
};
|
|
36356
|
+
const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
|
|
36357
|
+
const bindingIdentifier = symbol.bindingIdentifier;
|
|
36358
|
+
return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
|
|
36359
|
+
};
|
|
36360
|
+
//#endregion
|
|
35591
36361
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
35592
36362
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
35593
36363
|
"childContextTypes",
|
|
@@ -35598,15 +36368,6 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
35598
36368
|
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.`;
|
|
35599
36369
|
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.";
|
|
35600
36370
|
};
|
|
35601
|
-
const isInsideClassBody = (node) => {
|
|
35602
|
-
let current = node.parent;
|
|
35603
|
-
while (current) {
|
|
35604
|
-
if (isNodeOfType(current, "ClassBody")) return true;
|
|
35605
|
-
if (isFunctionLike$2(current)) return false;
|
|
35606
|
-
current = current.parent;
|
|
35607
|
-
}
|
|
35608
|
-
return false;
|
|
35609
|
-
};
|
|
35610
36371
|
const noLegacyContextApi = defineRule({
|
|
35611
36372
|
id: "no-legacy-context-api",
|
|
35612
36373
|
title: "Legacy context API",
|
|
@@ -35621,6 +36382,7 @@ const noLegacyContextApi = defineRule({
|
|
|
35621
36382
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
35622
36383
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
35623
36384
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
36385
|
+
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
35624
36386
|
context.report({
|
|
35625
36387
|
node: memberNode.key,
|
|
35626
36388
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -35628,6 +36390,8 @@ const noLegacyContextApi = defineRule({
|
|
|
35628
36390
|
};
|
|
35629
36391
|
return {
|
|
35630
36392
|
ClassBody(node) {
|
|
36393
|
+
const classNode = node.parent;
|
|
36394
|
+
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
35631
36395
|
for (const member of node.body ?? []) checkMember(member);
|
|
35632
36396
|
},
|
|
35633
36397
|
AssignmentExpression(node) {
|
|
@@ -35637,9 +36401,11 @@ const noLegacyContextApi = defineRule({
|
|
|
35637
36401
|
if (left.computed) return;
|
|
35638
36402
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
35639
36403
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
35640
|
-
if (
|
|
35641
|
-
|
|
35642
|
-
if (
|
|
36404
|
+
if (left.property.name === "getChildContext") return;
|
|
36405
|
+
const component = stripParenExpression(left.object);
|
|
36406
|
+
if (!isNodeOfType(component, "Identifier")) return;
|
|
36407
|
+
const symbol = context.scopes.symbolFor(component);
|
|
36408
|
+
if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
|
|
35643
36409
|
context.report({
|
|
35644
36410
|
node: left,
|
|
35645
36411
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -35843,10 +36609,10 @@ const getMethodCalls = (functionNode, scopes) => {
|
|
|
35843
36609
|
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
35844
36610
|
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
35845
36611
|
visitedFunctionNodes.add(functionNode);
|
|
35846
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36612
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
35847
36613
|
const immediateCall = getDirectCallForExpression(functionNode);
|
|
35848
36614
|
if (immediateCall) {
|
|
35849
|
-
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
36615
|
+
const immediateCallFunction = findEnclosingFunction$1(immediateCall);
|
|
35850
36616
|
if (immediateCallFunction === usageFunction) {
|
|
35851
36617
|
const immediateCallStart = getRangeStart(immediateCall);
|
|
35852
36618
|
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
@@ -35855,7 +36621,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
35855
36621
|
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
35856
36622
|
}
|
|
35857
36623
|
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
35858
|
-
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
36624
|
+
const methodCallFunction = findEnclosingFunction$1(methodCall);
|
|
35859
36625
|
if (methodCallFunction === usageFunction) {
|
|
35860
36626
|
const methodCallStart = getRangeStart(methodCall);
|
|
35861
36627
|
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
@@ -35878,7 +36644,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
35878
36644
|
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
35879
36645
|
const call = getDirectCallForExpression(child);
|
|
35880
36646
|
if (!call) return false;
|
|
35881
|
-
const callFunction = findEnclosingFunction(call);
|
|
36647
|
+
const callFunction = findEnclosingFunction$1(call);
|
|
35882
36648
|
if (callFunction === usageFunction) {
|
|
35883
36649
|
const callStart = getRangeStart(call);
|
|
35884
36650
|
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
@@ -35909,14 +36675,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
|
|
|
35909
36675
|
visitedMutationSymbolIds.add(symbol.id);
|
|
35910
36676
|
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
35911
36677
|
if (typeof usageBoundary !== "number") return true;
|
|
35912
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36678
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
35913
36679
|
return symbol.references.some((reference) => {
|
|
35914
36680
|
const referenceStart = getRangeStart(reference.identifier);
|
|
35915
36681
|
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
35916
36682
|
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
35917
36683
|
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
35918
36684
|
if (referenceStart === null) return true;
|
|
35919
|
-
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
36685
|
+
const mutationFunction = findEnclosingFunction$1(reference.identifier);
|
|
35920
36686
|
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
35921
36687
|
if (!mutationFunction) return usageFunction !== null;
|
|
35922
36688
|
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
@@ -36461,7 +37227,7 @@ const noMoment = defineRule({
|
|
|
36461
37227
|
});
|
|
36462
37228
|
//#endregion
|
|
36463
37229
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
36464
|
-
const MESSAGE$
|
|
37230
|
+
const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
36465
37231
|
const resolveSettings$16 = (settings) => {
|
|
36466
37232
|
const reactDoctor = settings?.["react-doctor"];
|
|
36467
37233
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -36498,13 +37264,6 @@ const isReactNamespaceExpression = (node, scopes) => {
|
|
|
36498
37264
|
return isReactNamespaceImport(node, scopes);
|
|
36499
37265
|
};
|
|
36500
37266
|
const isReactHocMemberReference = (node, scopes) => Boolean(isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && REACT_HOC_NAMES.has(node.property.name) && isReactNamespaceExpression(node.object, scopes));
|
|
36501
|
-
const getDestructuredPropertyName$1 = (symbol) => {
|
|
36502
|
-
const property = symbol.bindingIdentifier.parent;
|
|
36503
|
-
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
36504
|
-
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
36505
|
-
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
36506
|
-
return null;
|
|
36507
|
-
};
|
|
36508
37267
|
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
36509
37268
|
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
36510
37269
|
visitedSymbolIds.add(symbol.id);
|
|
@@ -36514,7 +37273,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
|
36514
37273
|
}
|
|
36515
37274
|
const init = symbol.initializer;
|
|
36516
37275
|
if (!init) return false;
|
|
36517
|
-
const destructuredPropertyName =
|
|
37276
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
36518
37277
|
if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
|
|
36519
37278
|
if (isReactHocMemberReference(init, scopes)) return true;
|
|
36520
37279
|
if (isNodeOfType(init, "Identifier")) {
|
|
@@ -36820,7 +37579,7 @@ const noMultiComp = defineRule({
|
|
|
36820
37579
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
36821
37580
|
for (const component of flagged.slice(1)) context.report({
|
|
36822
37581
|
node: component.reportNode,
|
|
36823
|
-
message: MESSAGE$
|
|
37582
|
+
message: MESSAGE$19
|
|
36824
37583
|
});
|
|
36825
37584
|
} };
|
|
36826
37585
|
}
|
|
@@ -37033,7 +37792,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
37033
37792
|
};
|
|
37034
37793
|
//#endregion
|
|
37035
37794
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
37036
|
-
const MESSAGE$
|
|
37795
|
+
const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
|
|
37037
37796
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
37038
37797
|
"copyWithin",
|
|
37039
37798
|
"fill",
|
|
@@ -37242,7 +38001,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37242
38001
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
37243
38002
|
context.report({
|
|
37244
38003
|
node: options.crossFileConsumerCallSite,
|
|
37245
|
-
message: `${MESSAGE$
|
|
38004
|
+
message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
37246
38005
|
});
|
|
37247
38006
|
return;
|
|
37248
38007
|
}
|
|
@@ -37251,7 +38010,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37251
38010
|
reportedNodes.add(mutation.node);
|
|
37252
38011
|
context.report({
|
|
37253
38012
|
node: mutation.node,
|
|
37254
|
-
message: MESSAGE$
|
|
38013
|
+
message: MESSAGE$18
|
|
37255
38014
|
});
|
|
37256
38015
|
}
|
|
37257
38016
|
};
|
|
@@ -37658,7 +38417,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
37658
38417
|
});
|
|
37659
38418
|
//#endregion
|
|
37660
38419
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
37661
|
-
const MESSAGE$
|
|
38420
|
+
const MESSAGE$17 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
37662
38421
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
37663
38422
|
"onKeyDown",
|
|
37664
38423
|
"onKeyUp",
|
|
@@ -37777,7 +38536,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37777
38536
|
if (numeric === null) {
|
|
37778
38537
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
37779
38538
|
node: tabIndex,
|
|
37780
|
-
message: MESSAGE$
|
|
38539
|
+
message: MESSAGE$17
|
|
37781
38540
|
});
|
|
37782
38541
|
return;
|
|
37783
38542
|
}
|
|
@@ -37799,7 +38558,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37799
38558
|
if (!roleAttribute) {
|
|
37800
38559
|
context.report({
|
|
37801
38560
|
node: tabIndex,
|
|
37802
|
-
message: MESSAGE$
|
|
38561
|
+
message: MESSAGE$17
|
|
37803
38562
|
});
|
|
37804
38563
|
return;
|
|
37805
38564
|
}
|
|
@@ -37813,7 +38572,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37813
38572
|
}
|
|
37814
38573
|
context.report({
|
|
37815
38574
|
node: tabIndex,
|
|
37816
|
-
message: MESSAGE$
|
|
38575
|
+
message: MESSAGE$17
|
|
37817
38576
|
});
|
|
37818
38577
|
} };
|
|
37819
38578
|
}
|
|
@@ -38268,12 +39027,6 @@ const getDeclarationKind = (declarator) => {
|
|
|
38268
39027
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
38269
39028
|
};
|
|
38270
39029
|
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
38271
|
-
const getDestructuredPropertyName = (bindingIdentifier) => {
|
|
38272
|
-
const property = bindingIdentifier.parent;
|
|
38273
|
-
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
38274
|
-
if (property.computed) return isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? String(property.key.value) : null;
|
|
38275
|
-
return isNodeOfType(property.key, "Identifier") ? property.key.name : null;
|
|
38276
|
-
};
|
|
38277
39030
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
38278
39031
|
const unwrappedExpression = stripParenExpression(expression);
|
|
38279
39032
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -38283,7 +39036,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
38283
39036
|
if (isProp(analysis, callbackReference)) {
|
|
38284
39037
|
if (isWholePropsObjectReference(analysis, callbackReference)) return null;
|
|
38285
39038
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
38286
|
-
return (bindingIdentifier &&
|
|
39039
|
+
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
38287
39040
|
}
|
|
38288
39041
|
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
38289
39042
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -38293,7 +39046,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
38293
39046
|
visitedVariables.add(callbackVariable);
|
|
38294
39047
|
if (isNodeOfType(declarator.id, "ObjectPattern")) {
|
|
38295
39048
|
const bindingIdentifier = callbackVariable.defs[0]?.name;
|
|
38296
|
-
const propertyName = bindingIdentifier ?
|
|
39049
|
+
const propertyName = bindingIdentifier ? getDestructuredBindingPropertyName(bindingIdentifier) : null;
|
|
38297
39050
|
const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
|
|
38298
39051
|
return propertyName && propsReference ? propertyName : null;
|
|
38299
39052
|
}
|
|
@@ -38396,7 +39149,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
38396
39149
|
const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
|
|
38397
39150
|
if (!bindingProvenance) return null;
|
|
38398
39151
|
const { refCall, variables } = bindingProvenance;
|
|
38399
|
-
const componentFunction = findEnclosingFunction(effectCall);
|
|
39152
|
+
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
38400
39153
|
if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
38401
39154
|
if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
|
|
38402
39155
|
const callbackPropNames = /* @__PURE__ */ new Set();
|
|
@@ -39232,174 +39985,6 @@ const noPropCallbackInRender = defineRule({
|
|
|
39232
39985
|
} })
|
|
39233
39986
|
});
|
|
39234
39987
|
//#endregion
|
|
39235
|
-
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
39236
|
-
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
39237
|
-
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
39238
|
-
const expression = stripParenExpression(node);
|
|
39239
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39240
|
-
const propertyName = getStaticPropertyName(expression);
|
|
39241
|
-
const receiver = stripParenExpression(expression.object);
|
|
39242
|
-
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
39243
|
-
}
|
|
39244
|
-
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39245
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39246
|
-
const symbol = scopes.symbolFor(expression);
|
|
39247
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
39248
|
-
visitedSymbolIds.add(symbol.id);
|
|
39249
|
-
if (isImportedFromReact(symbol)) {
|
|
39250
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
39251
|
-
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
39252
|
-
}
|
|
39253
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39254
|
-
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
39255
|
-
};
|
|
39256
|
-
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39257
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
39258
|
-
visitedClassNodes.add(classNode);
|
|
39259
|
-
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39260
|
-
};
|
|
39261
|
-
//#endregion
|
|
39262
|
-
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
39263
|
-
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
39264
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
39265
|
-
let containsReactHookCall = false;
|
|
39266
|
-
walkAst(functionNode.body, (node) => {
|
|
39267
|
-
if (containsReactHookCall) return false;
|
|
39268
|
-
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
39269
|
-
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39270
|
-
containsReactHookCall = true;
|
|
39271
|
-
return false;
|
|
39272
|
-
}
|
|
39273
|
-
});
|
|
39274
|
-
return containsReactHookCall;
|
|
39275
|
-
};
|
|
39276
|
-
//#endregion
|
|
39277
|
-
//#region src/plugin/utils/function-returns-props-children.ts
|
|
39278
|
-
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
39279
|
-
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
39280
|
-
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
39281
|
-
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
39282
|
-
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
39283
|
-
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
39284
|
-
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
39285
|
-
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
39286
|
-
const propertyValue = stripParenExpression(property.value);
|
|
39287
|
-
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
39288
|
-
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
39289
|
-
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
39290
|
-
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
39291
|
-
}
|
|
39292
|
-
}
|
|
39293
|
-
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
39294
|
-
const candidate = stripParenExpression(expression);
|
|
39295
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
39296
|
-
const symbol = scopes.symbolFor(candidate);
|
|
39297
|
-
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
39298
|
-
}
|
|
39299
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
39300
|
-
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
39301
|
-
const receiver = stripParenExpression(candidate.object);
|
|
39302
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
39303
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
39304
|
-
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
39305
|
-
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
39306
|
-
}, controlFlow);
|
|
39307
|
-
};
|
|
39308
|
-
//#endregion
|
|
39309
|
-
//#region src/plugin/utils/function-returns-only-null.ts
|
|
39310
|
-
const isNullExpression = (expression) => {
|
|
39311
|
-
const candidate = stripParenExpression(expression);
|
|
39312
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39313
|
-
};
|
|
39314
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
39315
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
39316
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39317
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39318
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39319
|
-
};
|
|
39320
|
-
//#endregion
|
|
39321
|
-
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
39322
|
-
const findFactoryRoot = (node) => {
|
|
39323
|
-
const candidate = stripParenExpression(node);
|
|
39324
|
-
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
39325
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
39326
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
39327
|
-
return null;
|
|
39328
|
-
};
|
|
39329
|
-
const findFactoryPropertyName = (node) => {
|
|
39330
|
-
const candidate = stripParenExpression(node);
|
|
39331
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
39332
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
39333
|
-
return null;
|
|
39334
|
-
};
|
|
39335
|
-
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
39336
|
-
const candidate = stripParenExpression(expression);
|
|
39337
|
-
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
39338
|
-
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
39339
|
-
if (!factoryRoot) return false;
|
|
39340
|
-
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
39341
|
-
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
39342
|
-
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
39343
|
-
if (!symbol || symbol.kind !== "import") return false;
|
|
39344
|
-
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
39345
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
39346
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
39347
|
-
};
|
|
39348
|
-
//#endregion
|
|
39349
|
-
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
39350
|
-
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
39351
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
39352
|
-
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39353
|
-
const candidate = stripParenExpression(expression);
|
|
39354
|
-
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
39355
|
-
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
39356
|
-
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
39357
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
39358
|
-
const symbol = scopes.symbolFor(candidate);
|
|
39359
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
39360
|
-
visitedSymbolIds.add(symbol.id);
|
|
39361
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
39362
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
39363
|
-
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
39364
|
-
}
|
|
39365
|
-
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
39366
|
-
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
39367
|
-
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39368
|
-
const wrappedComponent = candidate.arguments[0];
|
|
39369
|
-
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
39370
|
-
}
|
|
39371
|
-
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
39372
|
-
const factory = candidate.arguments[0];
|
|
39373
|
-
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
39374
|
-
const unwrappedFactory = stripParenExpression(factory);
|
|
39375
|
-
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
39376
|
-
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
39377
|
-
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
39378
|
-
const returnedExpression = returnStatements[0]?.argument;
|
|
39379
|
-
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
39380
|
-
};
|
|
39381
|
-
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
39382
|
-
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
39383
|
-
for (const candidateSymbol of candidateSymbols) {
|
|
39384
|
-
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
39385
|
-
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
39386
|
-
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
39387
|
-
continue;
|
|
39388
|
-
}
|
|
39389
|
-
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
39390
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
39391
|
-
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
39392
|
-
continue;
|
|
39393
|
-
}
|
|
39394
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
39395
|
-
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
39396
|
-
continue;
|
|
39397
|
-
}
|
|
39398
|
-
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
39399
|
-
}
|
|
39400
|
-
return false;
|
|
39401
|
-
};
|
|
39402
|
-
//#endregion
|
|
39403
39988
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
39404
39989
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
39405
39990
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -39552,7 +40137,7 @@ const isModuleScopedBinding = (identifier) => {
|
|
|
39552
40137
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
39553
40138
|
if (!binding) return false;
|
|
39554
40139
|
if (isNodeOfType(binding.scopeOwner, "Program")) return true;
|
|
39555
|
-
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
|
|
40140
|
+
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction$1(binding.scopeOwner) === null;
|
|
39556
40141
|
return false;
|
|
39557
40142
|
};
|
|
39558
40143
|
const looksLikeFreshUpdateExpression = (expression) => {
|
|
@@ -39607,7 +40192,7 @@ const noRandomKey = defineRule({
|
|
|
39607
40192
|
});
|
|
39608
40193
|
//#endregion
|
|
39609
40194
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
39610
|
-
const MESSAGE$
|
|
40195
|
+
const MESSAGE$16 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
39611
40196
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
39612
40197
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
39613
40198
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -39633,13 +40218,13 @@ const noReactChildren = defineRule({
|
|
|
39633
40218
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
39634
40219
|
context.report({
|
|
39635
40220
|
node: calleeOuter,
|
|
39636
|
-
message: MESSAGE$
|
|
40221
|
+
message: MESSAGE$16
|
|
39637
40222
|
});
|
|
39638
40223
|
return;
|
|
39639
40224
|
}
|
|
39640
40225
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
39641
40226
|
node: calleeOuter,
|
|
39642
|
-
message: MESSAGE$
|
|
40227
|
+
message: MESSAGE$16
|
|
39643
40228
|
});
|
|
39644
40229
|
} })
|
|
39645
40230
|
});
|
|
@@ -40320,7 +40905,7 @@ const noRenderPropChildren = defineRule({
|
|
|
40320
40905
|
});
|
|
40321
40906
|
//#endregion
|
|
40322
40907
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
40323
|
-
const MESSAGE$
|
|
40908
|
+
const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
40324
40909
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
40325
40910
|
const isUsedAsReturnValue = (parent) => {
|
|
40326
40911
|
if (!parent) return false;
|
|
@@ -40338,7 +40923,7 @@ const noRenderReturnValue = defineRule({
|
|
|
40338
40923
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
40339
40924
|
context.report({
|
|
40340
40925
|
node: node.callee,
|
|
40341
|
-
message: MESSAGE$
|
|
40926
|
+
message: MESSAGE$15
|
|
40342
40927
|
});
|
|
40343
40928
|
} })
|
|
40344
40929
|
});
|
|
@@ -41147,7 +41732,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
41147
41732
|
});
|
|
41148
41733
|
//#endregion
|
|
41149
41734
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
41150
|
-
const MESSAGE$
|
|
41735
|
+
const MESSAGE$14 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
41151
41736
|
const noSetState = defineRule({
|
|
41152
41737
|
id: "no-set-state",
|
|
41153
41738
|
title: "Local class state forbidden",
|
|
@@ -41162,7 +41747,7 @@ const noSetState = defineRule({
|
|
|
41162
41747
|
if (!getParentComponent(node)) return;
|
|
41163
41748
|
context.report({
|
|
41164
41749
|
node: node.callee,
|
|
41165
|
-
message: MESSAGE$
|
|
41750
|
+
message: MESSAGE$14
|
|
41166
41751
|
});
|
|
41167
41752
|
} })
|
|
41168
41753
|
});
|
|
@@ -41469,9 +42054,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
|
41469
42054
|
return isReturnedFromEffect;
|
|
41470
42055
|
};
|
|
41471
42056
|
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
41472
|
-
let functionNode = findEnclosingFunction(node);
|
|
42057
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
41473
42058
|
while (functionNode) {
|
|
41474
|
-
const outerFunction = findEnclosingFunction(functionNode);
|
|
42059
|
+
const outerFunction = findEnclosingFunction$1(functionNode);
|
|
41475
42060
|
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
41476
42061
|
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
41477
42062
|
functionNode = outerFunction;
|
|
@@ -41479,7 +42064,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
|
41479
42064
|
return false;
|
|
41480
42065
|
};
|
|
41481
42066
|
const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
|
|
41482
|
-
const enclosingFunction = findEnclosingFunction(clearCall);
|
|
42067
|
+
const enclosingFunction = findEnclosingFunction$1(clearCall);
|
|
41483
42068
|
if (!isFunctionLike$2(enclosingFunction)) return false;
|
|
41484
42069
|
if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
41485
42070
|
const clearStart = getRangeStart(clearCall);
|
|
@@ -41532,7 +42117,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
41532
42117
|
};
|
|
41533
42118
|
//#endregion
|
|
41534
42119
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
41535
|
-
const MESSAGE$
|
|
42120
|
+
const MESSAGE$13 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
|
|
41536
42121
|
const DEFAULT_HANDLERS = [
|
|
41537
42122
|
"onClick",
|
|
41538
42123
|
"onMouseDown",
|
|
@@ -41647,7 +42232,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41647
42232
|
if (!roleAttribute || !roleAttribute.value) {
|
|
41648
42233
|
context.report({
|
|
41649
42234
|
node: node.name,
|
|
41650
|
-
message: MESSAGE$
|
|
42235
|
+
message: MESSAGE$13
|
|
41651
42236
|
});
|
|
41652
42237
|
return;
|
|
41653
42238
|
}
|
|
@@ -41657,7 +42242,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41657
42242
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
41658
42243
|
context.report({
|
|
41659
42244
|
node: node.name,
|
|
41660
|
-
message: MESSAGE$
|
|
42245
|
+
message: MESSAGE$13
|
|
41661
42246
|
});
|
|
41662
42247
|
return;
|
|
41663
42248
|
}
|
|
@@ -41665,7 +42250,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41665
42250
|
if (!settings.allowExpressionValues) {
|
|
41666
42251
|
context.report({
|
|
41667
42252
|
node: node.name,
|
|
41668
|
-
message: MESSAGE$
|
|
42253
|
+
message: MESSAGE$13
|
|
41669
42254
|
});
|
|
41670
42255
|
return;
|
|
41671
42256
|
}
|
|
@@ -41673,7 +42258,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41673
42258
|
if (isStaticNullishExpression(expression)) {
|
|
41674
42259
|
context.report({
|
|
41675
42260
|
node: node.name,
|
|
41676
|
-
message: MESSAGE$
|
|
42261
|
+
message: MESSAGE$13
|
|
41677
42262
|
});
|
|
41678
42263
|
return;
|
|
41679
42264
|
}
|
|
@@ -41683,7 +42268,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41683
42268
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
41684
42269
|
context.report({
|
|
41685
42270
|
node: node.name,
|
|
41686
|
-
message: MESSAGE$
|
|
42271
|
+
message: MESSAGE$13
|
|
41687
42272
|
});
|
|
41688
42273
|
return;
|
|
41689
42274
|
}
|
|
@@ -41691,7 +42276,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41691
42276
|
}
|
|
41692
42277
|
context.report({
|
|
41693
42278
|
node: node.name,
|
|
41694
|
-
message: MESSAGE$
|
|
42279
|
+
message: MESSAGE$13
|
|
41695
42280
|
});
|
|
41696
42281
|
} };
|
|
41697
42282
|
}
|
|
@@ -41797,7 +42382,7 @@ const noStringRefs = defineRule({
|
|
|
41797
42382
|
});
|
|
41798
42383
|
//#endregion
|
|
41799
42384
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
41800
|
-
const MESSAGE$
|
|
42385
|
+
const MESSAGE$12 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
|
|
41801
42386
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
41802
42387
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
41803
42388
|
const noSyncXhr = defineRule({
|
|
@@ -41818,7 +42403,7 @@ const noSyncXhr = defineRule({
|
|
|
41818
42403
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
41819
42404
|
context.report({
|
|
41820
42405
|
node,
|
|
41821
|
-
message: MESSAGE$
|
|
42406
|
+
message: MESSAGE$12
|
|
41822
42407
|
});
|
|
41823
42408
|
};
|
|
41824
42409
|
return {
|
|
@@ -41843,7 +42428,7 @@ const noSyncXhr = defineRule({
|
|
|
41843
42428
|
});
|
|
41844
42429
|
//#endregion
|
|
41845
42430
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
41846
|
-
const MESSAGE$
|
|
42431
|
+
const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
|
|
41847
42432
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
41848
42433
|
let ancestor = node.parent;
|
|
41849
42434
|
while (ancestor) {
|
|
@@ -41931,7 +42516,7 @@ const noThisInSfc = defineRule({
|
|
|
41931
42516
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
41932
42517
|
context.report({
|
|
41933
42518
|
node,
|
|
41934
|
-
message: MESSAGE$
|
|
42519
|
+
message: MESSAGE$11
|
|
41935
42520
|
});
|
|
41936
42521
|
} };
|
|
41937
42522
|
}
|
|
@@ -42309,7 +42894,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
|
|
|
42309
42894
|
return false;
|
|
42310
42895
|
};
|
|
42311
42896
|
const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
|
|
42312
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
42897
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
42313
42898
|
if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
42314
42899
|
let currentNode = node;
|
|
42315
42900
|
while (currentNode !== enclosingFunction) {
|
|
@@ -43576,7 +44161,11 @@ const resolveSettings$8 = (settings) => {
|
|
|
43576
44161
|
propNamePattern: ruleSettings.propNamePattern ?? "render*"
|
|
43577
44162
|
};
|
|
43578
44163
|
};
|
|
43579
|
-
const
|
|
44164
|
+
const isReactCreateElementCall = (node, scopes) => isReactApiCall(node, "createElement", scopes, {
|
|
44165
|
+
allowGlobalReactNamespace: true,
|
|
44166
|
+
resolveNamedAliases: true
|
|
44167
|
+
});
|
|
44168
|
+
const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
43580
44169
|
let found = false;
|
|
43581
44170
|
walkAst(root, (node) => {
|
|
43582
44171
|
if (found) return false;
|
|
@@ -43585,17 +44174,17 @@ const expressionContainsJsxOrCreateElement = (root) => {
|
|
|
43585
44174
|
found = true;
|
|
43586
44175
|
return false;
|
|
43587
44176
|
}
|
|
43588
|
-
if (isNodeOfType(node, "CallExpression") &&
|
|
44177
|
+
if (isNodeOfType(node, "CallExpression") && isReactCreateElementCall(node, scopes)) {
|
|
43589
44178
|
found = true;
|
|
43590
44179
|
return false;
|
|
43591
44180
|
}
|
|
43592
44181
|
});
|
|
43593
44182
|
return found;
|
|
43594
44183
|
};
|
|
43595
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
|
|
43596
|
-
const isReactClassComponent = (classNode) => {
|
|
44184
|
+
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => expressionContainsJsxOrCreateElement(expression, scopes), controlFlow);
|
|
44185
|
+
const isReactClassComponent = (classNode, scopes) => {
|
|
43597
44186
|
if (isEs6Component(classNode)) return true;
|
|
43598
|
-
return expressionContainsJsxOrCreateElement(classNode);
|
|
44187
|
+
return expressionContainsJsxOrCreateElement(classNode, scopes);
|
|
43599
44188
|
};
|
|
43600
44189
|
const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
43601
44190
|
let walker = node.parent;
|
|
@@ -43612,7 +44201,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
|
43612
44201
|
};
|
|
43613
44202
|
}
|
|
43614
44203
|
if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
|
|
43615
|
-
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
|
|
44204
|
+
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker, scopes)) return {
|
|
43616
44205
|
component: walker,
|
|
43617
44206
|
name: walker.id.name
|
|
43618
44207
|
};
|
|
@@ -43701,12 +44290,12 @@ const isObjectCallbackCandidate = (node) => {
|
|
|
43701
44290
|
}
|
|
43702
44291
|
return false;
|
|
43703
44292
|
};
|
|
43704
|
-
const hocCallContainsComponent = (call) => {
|
|
44293
|
+
const hocCallContainsComponent = (call, scopes) => {
|
|
43705
44294
|
const firstArgument = call.arguments[0];
|
|
43706
44295
|
if (!firstArgument) return false;
|
|
43707
|
-
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument);
|
|
43708
|
-
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument);
|
|
43709
|
-
return expressionContainsJsxOrCreateElement(firstArgument);
|
|
44296
|
+
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument, scopes);
|
|
44297
|
+
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument, scopes);
|
|
44298
|
+
return expressionContainsJsxOrCreateElement(firstArgument, scopes);
|
|
43710
44299
|
};
|
|
43711
44300
|
const isFirstArgumentOfHocCall = (node) => {
|
|
43712
44301
|
const parent = node.parent;
|
|
@@ -43739,19 +44328,35 @@ const isReturnOfMapCallback = (node) => {
|
|
|
43739
44328
|
}
|
|
43740
44329
|
return false;
|
|
43741
44330
|
};
|
|
43742
|
-
const isCloneElementCall = (node) => {
|
|
43743
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
43744
|
-
const callee = node.callee;
|
|
43745
|
-
if (isNodeOfType(callee, "Identifier")) return callee.name === "cloneElement";
|
|
43746
|
-
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "cloneElement";
|
|
43747
|
-
};
|
|
43748
44331
|
const TS_VALUE_PASSTHROUGH_TYPES = new Set([
|
|
43749
44332
|
"TSAsExpression",
|
|
43750
44333
|
"TSNonNullExpression",
|
|
43751
44334
|
"TSSatisfiesExpression",
|
|
43752
44335
|
"TSTypeAssertion"
|
|
43753
44336
|
]);
|
|
43754
|
-
const
|
|
44337
|
+
const ELEMENT_TYPE_PROP_NAMES = new Set([
|
|
44338
|
+
"as",
|
|
44339
|
+
"body",
|
|
44340
|
+
"calendarcontainer",
|
|
44341
|
+
"component",
|
|
44342
|
+
"fallback",
|
|
44343
|
+
"tooltip"
|
|
44344
|
+
]);
|
|
44345
|
+
const isElementTypeJsxAttribute = (node) => {
|
|
44346
|
+
if (!isNodeOfType(node, "JSXAttribute")) return false;
|
|
44347
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
|
|
44348
|
+
const attributeName = node.name.name;
|
|
44349
|
+
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
44350
|
+
};
|
|
44351
|
+
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
44352
|
+
allowGlobalReactNamespace: true,
|
|
44353
|
+
resolveNamedAliases: true
|
|
44354
|
+
});
|
|
44355
|
+
const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
|
|
44356
|
+
allowGlobalReactNamespace: true,
|
|
44357
|
+
resolveNamedAliases: true
|
|
44358
|
+
});
|
|
44359
|
+
const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
43755
44360
|
let valueNode = identifier;
|
|
43756
44361
|
let parent = valueNode.parent;
|
|
43757
44362
|
while (parent) {
|
|
@@ -43761,20 +44366,26 @@ const isRenderFlowingReadReference = (identifier) => {
|
|
|
43761
44366
|
continue;
|
|
43762
44367
|
}
|
|
43763
44368
|
switch (parent.type) {
|
|
43764
|
-
case "
|
|
43765
|
-
case "
|
|
43766
|
-
case "
|
|
44369
|
+
case "JSXOpeningElement": return parent.name === valueNode;
|
|
44370
|
+
case "JSXExpressionContainer": return Boolean(parent.parent && isElementTypeJsxAttribute(parent.parent));
|
|
44371
|
+
case "ReturnStatement": return false;
|
|
44372
|
+
case "ArrowFunctionExpression": return false;
|
|
43767
44373
|
case "CallExpression":
|
|
43768
44374
|
if (parent.callee === valueNode) return false;
|
|
43769
|
-
if (
|
|
44375
|
+
if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
|
|
44376
|
+
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
43770
44377
|
valueNode = parent;
|
|
43771
44378
|
parent = parent.parent;
|
|
43772
44379
|
continue;
|
|
43773
|
-
case "VariableDeclarator":
|
|
43774
|
-
|
|
43775
|
-
const
|
|
43776
|
-
|
|
44380
|
+
case "VariableDeclarator": {
|
|
44381
|
+
if (parent.init !== valueNode || !isNodeOfType(parent.id, "Identifier")) return false;
|
|
44382
|
+
const aliasSymbol = scopes.symbolFor(parent.id);
|
|
44383
|
+
if (!aliasSymbol || aliasSymbol.kind !== "const" || visitedSymbols.has(aliasSymbol.id)) return false;
|
|
44384
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
44385
|
+
nextVisitedSymbols.add(aliasSymbol.id);
|
|
44386
|
+
return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
|
|
43777
44387
|
}
|
|
44388
|
+
case "AssignmentExpression": return false;
|
|
43778
44389
|
case "Property":
|
|
43779
44390
|
if (parent.value !== valueNode) return false;
|
|
43780
44391
|
valueNode = parent;
|
|
@@ -43812,6 +44423,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43812
44423
|
severity: "warn",
|
|
43813
44424
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
43814
44425
|
category: "Performance",
|
|
44426
|
+
tags: ["react-jsx-only"],
|
|
43815
44427
|
create: (context) => {
|
|
43816
44428
|
const settings = resolveSettings$8(context.settings);
|
|
43817
44429
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -43874,7 +44486,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43874
44486
|
},
|
|
43875
44487
|
Identifier(node) {
|
|
43876
44488
|
if (!isReactComponentName(node.name)) return;
|
|
43877
|
-
if (!isRenderFlowingReadReference(node)) return;
|
|
44489
|
+
if (!isRenderFlowingReadReference(node, context.scopes)) return;
|
|
43878
44490
|
recordInstantiation(node, node.name);
|
|
43879
44491
|
},
|
|
43880
44492
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -43883,28 +44495,33 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43883
44495
|
ClassDeclaration(node) {
|
|
43884
44496
|
if (!node.id) return;
|
|
43885
44497
|
if (!isReactComponentName(node.id.name)) return;
|
|
43886
|
-
if (!isReactClassComponent(node)) return;
|
|
44498
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43887
44499
|
enqueueCandidate(node, null);
|
|
43888
44500
|
},
|
|
43889
44501
|
ClassExpression(node) {
|
|
43890
44502
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
43891
44503
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
43892
|
-
if (!isReactClassComponent(node)) return;
|
|
44504
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43893
44505
|
enqueueCandidate(node, null);
|
|
43894
44506
|
},
|
|
43895
44507
|
CallExpression(node) {
|
|
43896
|
-
if (
|
|
44508
|
+
if (isReactCreateElementCall(node, context.scopes)) {
|
|
43897
44509
|
const firstArgument = node.arguments[0];
|
|
43898
44510
|
if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
|
|
43899
44511
|
else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
|
|
43900
44512
|
}
|
|
43901
|
-
|
|
43902
|
-
if (!
|
|
43903
|
-
|
|
44513
|
+
const isReactLazy = isReactLazyCall(node, context.scopes);
|
|
44514
|
+
if (!isReactLazy && !isHocCallee$1(node)) return;
|
|
44515
|
+
if (!isReactLazy && !hocCallContainsComponent(node, context.scopes)) return;
|
|
44516
|
+
const inferredName = inferFunctionLikeName(node);
|
|
44517
|
+
const propInfo = isComponentDeclaredInProp(node);
|
|
44518
|
+
if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
|
|
44519
|
+
enqueueCandidate(node, propInfo === null ? inferredName : null);
|
|
43904
44520
|
},
|
|
43905
44521
|
"Program:exit"() {
|
|
43906
44522
|
for (const report of queuedReports) {
|
|
43907
44523
|
if (report.requiredInstantiationName !== null) {
|
|
44524
|
+
if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
|
|
43908
44525
|
if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
|
|
43909
44526
|
}
|
|
43910
44527
|
context.report({
|
|
@@ -44078,7 +44695,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
44078
44695
|
//#endregion
|
|
44079
44696
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
44080
44697
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
44081
|
-
const MESSAGE$
|
|
44698
|
+
const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
44082
44699
|
const resolveSettings$7 = (settings) => {
|
|
44083
44700
|
const reactDoctor = settings?.["react-doctor"];
|
|
44084
44701
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -44112,7 +44729,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
44112
44729
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
44113
44730
|
context.report({
|
|
44114
44731
|
node: node.callee,
|
|
44115
|
-
message: MESSAGE$
|
|
44732
|
+
message: MESSAGE$10
|
|
44116
44733
|
});
|
|
44117
44734
|
} };
|
|
44118
44735
|
}
|
|
@@ -44937,7 +45554,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
|
|
|
44937
45554
|
const symbol = scopes.symbolFor(identifier);
|
|
44938
45555
|
if (symbol) {
|
|
44939
45556
|
if (symbol.kind !== "parameter") return null;
|
|
44940
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45557
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
44941
45558
|
if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
|
|
44942
45559
|
return null;
|
|
44943
45560
|
}
|
|
@@ -44983,7 +45600,7 @@ const isChildrenMemberExpression = (node, scopes) => {
|
|
|
44983
45600
|
const symbol = scopes.symbolFor(propsObject);
|
|
44984
45601
|
if (symbol) {
|
|
44985
45602
|
if (symbol.kind !== "parameter") return false;
|
|
44986
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45603
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
44987
45604
|
return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
|
|
44988
45605
|
}
|
|
44989
45606
|
return hasComponentLikeAncestorFunction(node);
|
|
@@ -45108,7 +45725,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
45108
45725
|
});
|
|
45109
45726
|
//#endregion
|
|
45110
45727
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
45111
|
-
const MESSAGE$
|
|
45728
|
+
const MESSAGE$9 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
|
|
45112
45729
|
const preactPreferOndblclick = defineRule({
|
|
45113
45730
|
id: "preact-prefer-ondblclick",
|
|
45114
45731
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -45123,7 +45740,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
45123
45740
|
if (!onDoubleClickAttribute) return;
|
|
45124
45741
|
context.report({
|
|
45125
45742
|
node: onDoubleClickAttribute,
|
|
45126
|
-
message: MESSAGE$
|
|
45743
|
+
message: MESSAGE$9
|
|
45127
45744
|
});
|
|
45128
45745
|
} })
|
|
45129
45746
|
});
|
|
@@ -45374,7 +45991,7 @@ const preferExplicitVariants = defineRule({
|
|
|
45374
45991
|
});
|
|
45375
45992
|
//#endregion
|
|
45376
45993
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
45377
|
-
const MESSAGE$
|
|
45994
|
+
const MESSAGE$8 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
|
|
45378
45995
|
const resolveSettings$4 = (settings) => {
|
|
45379
45996
|
const reactDoctor = settings?.["react-doctor"];
|
|
45380
45997
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -45413,7 +46030,7 @@ const preferFunctionComponent = defineRule({
|
|
|
45413
46030
|
const reportNode = node.id ?? node;
|
|
45414
46031
|
context.report({
|
|
45415
46032
|
node: reportNode,
|
|
45416
|
-
message: MESSAGE$
|
|
46033
|
+
message: MESSAGE$8
|
|
45417
46034
|
});
|
|
45418
46035
|
};
|
|
45419
46036
|
return {
|
|
@@ -45635,7 +46252,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
|
|
|
45635
46252
|
//#endregion
|
|
45636
46253
|
//#region src/plugin/utils/enclosing-component-or-hook-scope.ts
|
|
45637
46254
|
const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
|
|
45638
|
-
const functionNode = findEnclosingFunction(startNode);
|
|
46255
|
+
const functionNode = findEnclosingFunction$1(startNode);
|
|
45639
46256
|
if (!functionNode) return null;
|
|
45640
46257
|
const displayName = componentOrHookDisplayNameForFunction(functionNode);
|
|
45641
46258
|
if (!displayName) return null;
|
|
@@ -46697,7 +47314,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
|
|
|
46697
47314
|
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
46698
47315
|
const isHookReturnForwardingSpread = (objectExpression) => {
|
|
46699
47316
|
const objectParent = objectExpression.parent;
|
|
46700
|
-
const enclosingFunction = findEnclosingFunction(objectExpression);
|
|
47317
|
+
const enclosingFunction = findEnclosingFunction$1(objectExpression);
|
|
46701
47318
|
if (!enclosingFunction) return false;
|
|
46702
47319
|
if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
|
|
46703
47320
|
const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
|
|
@@ -46991,7 +47608,258 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
46991
47608
|
}
|
|
46992
47609
|
});
|
|
46993
47610
|
//#endregion
|
|
47611
|
+
//#region src/plugin/utils/is-node-conditionally-executed.ts
|
|
47612
|
+
const isNodeConditionallyExecuted = (node, boundary) => {
|
|
47613
|
+
let child = node;
|
|
47614
|
+
let parent = child.parent ?? null;
|
|
47615
|
+
while (parent && parent !== boundary) {
|
|
47616
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) return true;
|
|
47617
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) return true;
|
|
47618
|
+
if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) return true;
|
|
47619
|
+
child = parent;
|
|
47620
|
+
parent = child.parent ?? null;
|
|
47621
|
+
}
|
|
47622
|
+
return false;
|
|
47623
|
+
};
|
|
47624
|
+
//#endregion
|
|
47625
|
+
//#region src/plugin/rules/tanstack-query/utils/resolve-tanstack-query-hook-name.ts
|
|
47626
|
+
const resolveTanstackNamespaceHookName = (memberExpression, contextNode, scopes) => {
|
|
47627
|
+
const hookName = getStaticPropertyKeyName(memberExpression, { allowComputedString: true });
|
|
47628
|
+
const namespaceObject = stripParenExpression(memberExpression.object);
|
|
47629
|
+
if (!hookName || !TANSTACK_QUERY_HOOKS.has(hookName)) return null;
|
|
47630
|
+
if (!isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
47631
|
+
const resolvedNamespaceSymbol = resolveConstIdentifierAlias(namespaceObject, scopes);
|
|
47632
|
+
if (resolvedNamespaceSymbol?.kind !== "import") return null;
|
|
47633
|
+
const namespaceBinding = getImportBindingForName(contextNode, resolvedNamespaceSymbol.name);
|
|
47634
|
+
return namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source) ? hookName : null;
|
|
47635
|
+
};
|
|
47636
|
+
const resolveTanstackQueryHookName = (callExpression, scopes) => {
|
|
47637
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
47638
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
47639
|
+
const resolvedSymbol = resolveConstIdentifierAlias(callee, scopes);
|
|
47640
|
+
if (!resolvedSymbol) return null;
|
|
47641
|
+
if (resolvedSymbol.kind === "const" && resolvedSymbol.initializer) {
|
|
47642
|
+
const initializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47643
|
+
return isNodeOfType(initializer, "MemberExpression") ? resolveTanstackNamespaceHookName(initializer, callExpression, scopes) : null;
|
|
47644
|
+
}
|
|
47645
|
+
if (resolvedSymbol.kind !== "import") return null;
|
|
47646
|
+
const importBinding = getImportBindingForName(callExpression, resolvedSymbol.name);
|
|
47647
|
+
if (importBinding === null) return null;
|
|
47648
|
+
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
47649
|
+
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
47650
|
+
}
|
|
47651
|
+
if (isNodeOfType(callee, "MemberExpression")) return resolveTanstackNamespaceHookName(callee, callExpression, scopes);
|
|
47652
|
+
return null;
|
|
47653
|
+
};
|
|
47654
|
+
const resolveTanstackQueryHookNameFromInitializer = (initializer, scopes) => {
|
|
47655
|
+
const unwrappedInitializer = stripParenExpression(initializer);
|
|
47656
|
+
if (isNodeOfType(unwrappedInitializer, "CallExpression")) return resolveTanstackQueryHookName(unwrappedInitializer, scopes);
|
|
47657
|
+
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return null;
|
|
47658
|
+
const resolvedSymbol = resolveConstIdentifierAlias(unwrappedInitializer, scopes);
|
|
47659
|
+
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
47660
|
+
const resolvedInitializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47661
|
+
if (!isNodeOfType(resolvedInitializer, "CallExpression")) return null;
|
|
47662
|
+
return resolveTanstackQueryHookName(resolvedInitializer, scopes);
|
|
47663
|
+
};
|
|
47664
|
+
//#endregion
|
|
46994
47665
|
//#region src/plugin/rules/tanstack-query/query-no-query-in-effect.ts
|
|
47666
|
+
const isTanstackQueryResult = (expression, context) => Boolean(resolveTanstackQueryHookNameFromInitializer(expression, context.scopes));
|
|
47667
|
+
const isStaticRefetchMember = (memberExpression) => getStaticPropertyKeyName(memberExpression, { allowComputedString: true }) === "refetch";
|
|
47668
|
+
const resolveCalledFunction = (callee, context) => {
|
|
47669
|
+
const unwrappedCallee = stripParenExpression(callee);
|
|
47670
|
+
if (isFunctionLike$2(unwrappedCallee)) return unwrappedCallee;
|
|
47671
|
+
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
47672
|
+
const symbol = resolveConstIdentifierAlias(unwrappedCallee, context.scopes);
|
|
47673
|
+
if (!symbol) return null;
|
|
47674
|
+
const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
|
|
47675
|
+
return candidate && isFunctionLike$2(candidate) ? candidate : null;
|
|
47676
|
+
};
|
|
47677
|
+
const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
47678
|
+
if (!isFunctionLike$2(functionNode)) return true;
|
|
47679
|
+
if (functionNode.generator) return true;
|
|
47680
|
+
const boundaryStart = getRangeStart(boundary);
|
|
47681
|
+
if (boundaryStart === null) return true;
|
|
47682
|
+
let hasSuspension = false;
|
|
47683
|
+
walkAst(functionNode, (node) => {
|
|
47684
|
+
if (node !== functionNode && isFunctionLike$2(node)) return false;
|
|
47685
|
+
if (!isNodeOfType(node, "AwaitExpression") || !isNodeReachableWithinFunction(node, context)) return;
|
|
47686
|
+
const suspensionStart = getRangeStart(node);
|
|
47687
|
+
if (suspensionStart !== null && suspensionStart < boundaryStart) {
|
|
47688
|
+
hasSuspension = true;
|
|
47689
|
+
return false;
|
|
47690
|
+
}
|
|
47691
|
+
});
|
|
47692
|
+
return hasSuspension;
|
|
47693
|
+
};
|
|
47694
|
+
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47695
|
+
let enclosingFunction = findEnclosingFunction$1(functionNode);
|
|
47696
|
+
while (enclosingFunction) {
|
|
47697
|
+
if (enclosingFunction === ancestor) return true;
|
|
47698
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47699
|
+
}
|
|
47700
|
+
return false;
|
|
47701
|
+
};
|
|
47702
|
+
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 === "??="));
|
|
47703
|
+
const functionInvokesTarget = (callerFunction, targetFunction, context, visitedFunctions, canCrossSuspension = false) => {
|
|
47704
|
+
if (visitedFunctions.has(callerFunction)) return false;
|
|
47705
|
+
visitedFunctions.add(callerFunction);
|
|
47706
|
+
let invokesTarget = false;
|
|
47707
|
+
walkAst(callerFunction, (node) => {
|
|
47708
|
+
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47709
|
+
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47710
|
+
if (!isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context) || !canCrossSuspension && hasSuspensionBefore(callerFunction, node, context)) return;
|
|
47711
|
+
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47712
|
+
if (calledFunction === targetFunction || calledFunction && functionInvokesTarget(calledFunction, targetFunction, context, visitedFunctions, canCrossSuspension)) {
|
|
47713
|
+
invokesTarget = true;
|
|
47714
|
+
return false;
|
|
47715
|
+
}
|
|
47716
|
+
});
|
|
47717
|
+
return invokesTarget;
|
|
47718
|
+
};
|
|
47719
|
+
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47720
|
+
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47721
|
+
const boundaryStart = getRangeStart(boundary);
|
|
47722
|
+
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47723
|
+
let isInvokedBefore = false;
|
|
47724
|
+
walkAst(boundaryFunction, (node) => {
|
|
47725
|
+
if (node !== boundaryFunction && isFunctionLike$2(node)) return false;
|
|
47726
|
+
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47727
|
+
const callStart = getRangeStart(node);
|
|
47728
|
+
if (callStart === null || callStart >= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, boundaryFunction, context) || hasSuspensionBefore(boundaryFunction, node, context)) return;
|
|
47729
|
+
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47730
|
+
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set())) {
|
|
47731
|
+
isInvokedBefore = true;
|
|
47732
|
+
return false;
|
|
47733
|
+
}
|
|
47734
|
+
});
|
|
47735
|
+
return isInvokedBefore;
|
|
47736
|
+
};
|
|
47737
|
+
const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, context) => {
|
|
47738
|
+
const boundaryStart = getRangeStart(boundary);
|
|
47739
|
+
if (boundaryStart === null) return false;
|
|
47740
|
+
let isInvokedAfter = false;
|
|
47741
|
+
walkAst(callerFunction, (node) => {
|
|
47742
|
+
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47743
|
+
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47744
|
+
const callStart = getRangeStart(node);
|
|
47745
|
+
if (callStart === null || callStart <= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context)) return;
|
|
47746
|
+
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47747
|
+
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set(), true)) {
|
|
47748
|
+
isInvokedAfter = true;
|
|
47749
|
+
return false;
|
|
47750
|
+
}
|
|
47751
|
+
});
|
|
47752
|
+
return isInvokedAfter;
|
|
47753
|
+
};
|
|
47754
|
+
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47755
|
+
const writeStart = getRangeStart(writeNode);
|
|
47756
|
+
const boundaryStart = getRangeStart(boundary);
|
|
47757
|
+
const writeFunction = findEnclosingFunction$1(writeNode);
|
|
47758
|
+
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47759
|
+
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47760
|
+
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47761
|
+
const renderFunction = deferredExecutionFunction ? findEnclosingFunction$1(deferredExecutionFunction) : null;
|
|
47762
|
+
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47763
|
+
if (!writeFunction) return writeStart < boundaryStart;
|
|
47764
|
+
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
47765
|
+
if (Boolean(renderFunction && (writeFunction === renderFunction || isFunctionAncestor(writeFunction, renderFunction)))) return true;
|
|
47766
|
+
if (deferredExecutionFunction && boundaryFunction !== deferredExecutionFunction) {
|
|
47767
|
+
if (hasSuspensionBefore(boundaryFunction, boundary, context) && isFunctionInvokedBefore(boundaryFunction, writeNode, context)) return true;
|
|
47768
|
+
return isFunctionInvokedAfter(boundaryFunction, writeNode, writeFunction, context);
|
|
47769
|
+
}
|
|
47770
|
+
return writeStart < boundaryStart;
|
|
47771
|
+
}
|
|
47772
|
+
if (renderFunction && isFunctionAncestor(renderFunction, writeFunction) && !hasSuspensionBefore(writeFunction, writeNode, context) && functionInvokesTarget(renderFunction, writeFunction, context, /* @__PURE__ */ new Set())) return true;
|
|
47773
|
+
return !hasSuspensionBefore(writeFunction, writeNode, context) && isFunctionInvokedBefore(writeFunction, boundary, context);
|
|
47774
|
+
};
|
|
47775
|
+
const getStaticStringValue = (node) => {
|
|
47776
|
+
const unwrappedNode = stripParenExpression(node);
|
|
47777
|
+
if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "string") return unwrappedNode.value;
|
|
47778
|
+
if (isNodeOfType(unwrappedNode, "TemplateLiteral") && unwrappedNode.expressions.length === 0) return getStaticTemplateLiteralValue(unwrappedNode);
|
|
47779
|
+
return null;
|
|
47780
|
+
};
|
|
47781
|
+
const isSameRefetchMember = (target, candidate, context) => {
|
|
47782
|
+
const unwrappedTarget = stripParenExpression(target);
|
|
47783
|
+
const unwrappedCandidate = stripParenExpression(candidate);
|
|
47784
|
+
if (!isNodeOfType(unwrappedTarget, "Identifier") || !isNodeOfType(unwrappedCandidate, "MemberExpression") || !isStaticRefetchMember(unwrappedCandidate)) return false;
|
|
47785
|
+
const candidateTarget = stripParenExpression(unwrappedCandidate.object);
|
|
47786
|
+
if (!isNodeOfType(candidateTarget, "Identifier")) return false;
|
|
47787
|
+
const targetSymbol = resolveConstIdentifierAlias(unwrappedTarget, context.scopes);
|
|
47788
|
+
const candidateSymbol = resolveConstIdentifierAlias(candidateTarget, context.scopes);
|
|
47789
|
+
return Boolean(targetSymbol && candidateSymbol?.id === targetSymbol.id);
|
|
47790
|
+
};
|
|
47791
|
+
const getRefetchMutationTarget = (node, context) => {
|
|
47792
|
+
if (isNodeOfType(node, "MemberExpression") && isStaticRefetchMember(node)) {
|
|
47793
|
+
const parent = node.parent;
|
|
47794
|
+
if (isNodeOfType(parent, "AssignmentExpression") && parent.left === node && isSameRefetchMember(node.object, parent.right, context)) return null;
|
|
47795
|
+
return isNodeOfType(parent, "AssignmentExpression") && parent.left === node || isNodeOfType(parent, "UpdateExpression") && parent.argument === node || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" ? node.object : null;
|
|
47796
|
+
}
|
|
47797
|
+
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
47798
|
+
const callee = stripParenExpression(node.callee);
|
|
47799
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47800
|
+
const receiver = stripParenExpression(callee.object);
|
|
47801
|
+
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || context.scopes.symbolFor(receiver)) return null;
|
|
47802
|
+
const methodName = getStaticPropertyKeyName(callee, { allowComputedString: true });
|
|
47803
|
+
const target = node.arguments[0];
|
|
47804
|
+
if (!target || isNodeOfType(target, "SpreadElement")) return null;
|
|
47805
|
+
if (methodName === "defineProperty") {
|
|
47806
|
+
const propertyKey = node.arguments[1];
|
|
47807
|
+
if (!propertyKey || getStaticStringValue(propertyKey) !== "refetch") return null;
|
|
47808
|
+
const descriptor = node.arguments[2];
|
|
47809
|
+
if (isNodeOfType(descriptor, "ObjectExpression")) {
|
|
47810
|
+
const valueProperty = descriptor.properties.find((property) => isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "value");
|
|
47811
|
+
if (isNodeOfType(valueProperty, "Property") && isSameRefetchMember(target, valueProperty.value, context)) return null;
|
|
47812
|
+
}
|
|
47813
|
+
return target;
|
|
47814
|
+
}
|
|
47815
|
+
if (methodName !== "assign") return null;
|
|
47816
|
+
let finalRefetchValue = null;
|
|
47817
|
+
for (const source of node.arguments.slice(1)) {
|
|
47818
|
+
if (!isNodeOfType(source, "ObjectExpression")) continue;
|
|
47819
|
+
for (const property of source.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "refetch") finalRefetchValue = property.value;
|
|
47820
|
+
}
|
|
47821
|
+
if (!finalRefetchValue || isSameRefetchMember(target, finalRefetchValue, context)) return null;
|
|
47822
|
+
return target;
|
|
47823
|
+
};
|
|
47824
|
+
const hasRefetchMemberWriteBefore = (expression, boundary, context, deferredExecutionFunction) => {
|
|
47825
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
47826
|
+
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47827
|
+
const resultSymbol = resolveConstIdentifierAlias(unwrappedExpression, context.scopes);
|
|
47828
|
+
if (!resultSymbol) return false;
|
|
47829
|
+
const program = findProgramRoot(expression);
|
|
47830
|
+
if (!program) return true;
|
|
47831
|
+
if (getRangeStart(boundary) === null) return true;
|
|
47832
|
+
let hasWrite = false;
|
|
47833
|
+
walkAst(program, (node) => {
|
|
47834
|
+
const mutationTarget = getRefetchMutationTarget(node, context);
|
|
47835
|
+
if (!mutationTarget) return;
|
|
47836
|
+
if (!isWriteExecutedBefore(node, boundary, context, deferredExecutionFunction)) return;
|
|
47837
|
+
const object = stripParenExpression(mutationTarget);
|
|
47838
|
+
if (!isNodeOfType(object, "Identifier")) return;
|
|
47839
|
+
if (resolveConstIdentifierAlias(object, context.scopes)?.id === resultSymbol.id) {
|
|
47840
|
+
hasWrite = true;
|
|
47841
|
+
return false;
|
|
47842
|
+
}
|
|
47843
|
+
});
|
|
47844
|
+
return hasWrite;
|
|
47845
|
+
};
|
|
47846
|
+
const isTanstackRefetchExpression = (expression, context, deferredExecutionFunction, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47847
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
47848
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return isStaticRefetchMember(unwrappedExpression) && isTanstackQueryResult(unwrappedExpression.object, context) && !hasRefetchMemberWriteBefore(unwrappedExpression.object, unwrappedExpression, context, deferredExecutionFunction);
|
|
47849
|
+
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47850
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
47851
|
+
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read") || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
47852
|
+
visitedSymbolIds.add(symbol.id);
|
|
47853
|
+
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
47854
|
+
if (isNodeOfType(bindingProperty, "Property") && getStaticPropertyKeyName(bindingProperty, { allowComputedString: true }) === "refetch") {
|
|
47855
|
+
const initializer = symbol.declarationNode.init;
|
|
47856
|
+
return Boolean(initializer && isTanstackQueryResult(initializer, context) && !hasRefetchMemberWriteBefore(initializer, symbol.declarationNode, context, null));
|
|
47857
|
+
}
|
|
47858
|
+
return Boolean(symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer && isTanstackRefetchExpression(symbol.initializer, context, null, visitedSymbolIds));
|
|
47859
|
+
};
|
|
47860
|
+
const isTanstackRefetchCall = (callExpression, context, effectCallback) => {
|
|
47861
|
+
return isTanstackRefetchExpression(callExpression.callee, context, effectCallback);
|
|
47862
|
+
};
|
|
46995
47863
|
const queryNoQueryInEffect = defineRule({
|
|
46996
47864
|
id: "query-no-query-in-effect",
|
|
46997
47865
|
title: "Query refetch inside useEffect",
|
|
@@ -47007,8 +47875,7 @@ const queryNoQueryInEffect = defineRule({
|
|
|
47007
47875
|
walkAst(callback, (child) => {
|
|
47008
47876
|
if (child !== callback && isFunctionLike$2(child) && !effectInvokedFunctions.has(child)) return false;
|
|
47009
47877
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
47010
|
-
|
|
47011
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "refetch" || isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "refetch") context.report({
|
|
47878
|
+
if (isTanstackRefetchCall(child, context, callback)) context.report({
|
|
47012
47879
|
node: child,
|
|
47013
47880
|
message: "refetch() inside useEffect duplicates work React Query already does, causing extra fetches."
|
|
47014
47881
|
});
|
|
@@ -47017,28 +47884,6 @@ const queryNoQueryInEffect = defineRule({
|
|
|
47017
47884
|
});
|
|
47018
47885
|
//#endregion
|
|
47019
47886
|
//#region src/plugin/rules/tanstack-query/query-no-rest-destructuring.ts
|
|
47020
|
-
const resolveTanstackQueryHookName = (callExpression) => {
|
|
47021
|
-
const callee = callExpression.callee;
|
|
47022
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
47023
|
-
const importBinding = getImportBindingForName(callExpression, callee.name);
|
|
47024
|
-
if (importBinding === null) return TANSTACK_QUERY_HOOKS.has(callee.name) ? callee.name : null;
|
|
47025
|
-
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
47026
|
-
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
47027
|
-
}
|
|
47028
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && TANSTACK_QUERY_HOOKS.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) {
|
|
47029
|
-
const namespaceBinding = getImportBindingForName(callExpression, callee.object.name);
|
|
47030
|
-
if (namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source)) return callee.property.name;
|
|
47031
|
-
}
|
|
47032
|
-
return null;
|
|
47033
|
-
};
|
|
47034
|
-
const resolveHookNameFromInitializer = (initializer, scopes) => {
|
|
47035
|
-
if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
|
|
47036
|
-
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
47037
|
-
const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
|
|
47038
|
-
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
47039
|
-
if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
|
|
47040
|
-
return resolveTanstackQueryHookName(resolvedSymbol.initializer);
|
|
47041
|
-
};
|
|
47042
47887
|
const queryNoRestDestructuring = defineRule({
|
|
47043
47888
|
id: "query-no-rest-destructuring",
|
|
47044
47889
|
title: "Rest destructuring on query result",
|
|
@@ -47050,7 +47895,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
47050
47895
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
47051
47896
|
if (!node.init) return;
|
|
47052
47897
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
47053
|
-
const hookName =
|
|
47898
|
+
const hookName = resolveTanstackQueryHookNameFromInitializer(node.init, context.scopes);
|
|
47054
47899
|
if (!hookName) return;
|
|
47055
47900
|
context.report({
|
|
47056
47901
|
node: node.id,
|
|
@@ -47185,8 +48030,8 @@ const queryStableQueryClient = defineRule({
|
|
|
47185
48030
|
create: (context) => ({ NewExpression(node) {
|
|
47186
48031
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
|
|
47187
48032
|
if (isStableHookWrapperArgument(node)) return;
|
|
47188
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
47189
|
-
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
48033
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
48034
|
+
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47190
48035
|
if (!enclosingFunction) return;
|
|
47191
48036
|
if (!isComponentFunction(enclosingFunction)) return;
|
|
47192
48037
|
context.report({
|
|
@@ -47281,7 +48126,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
47281
48126
|
const comparatorArgument = node.arguments?.[1];
|
|
47282
48127
|
if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
|
|
47283
48128
|
} else {
|
|
47284
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
48129
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
47285
48130
|
if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
|
|
47286
48131
|
}
|
|
47287
48132
|
const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
|
|
@@ -47294,7 +48139,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
47294
48139
|
});
|
|
47295
48140
|
//#endregion
|
|
47296
48141
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
47297
|
-
const MESSAGE$
|
|
48142
|
+
const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
|
|
47298
48143
|
const reactInJsxScope = defineRule({
|
|
47299
48144
|
id: "react-in-jsx-scope",
|
|
47300
48145
|
title: "React not in scope for JSX",
|
|
@@ -47306,19 +48151,190 @@ const reactInJsxScope = defineRule({
|
|
|
47306
48151
|
if (findVariableInitializer(node, "React")) return;
|
|
47307
48152
|
context.report({
|
|
47308
48153
|
node: node.name,
|
|
47309
|
-
message: MESSAGE$
|
|
48154
|
+
message: MESSAGE$7
|
|
47310
48155
|
});
|
|
47311
48156
|
},
|
|
47312
48157
|
JSXFragment(node) {
|
|
47313
48158
|
if (findVariableInitializer(node, "React")) return;
|
|
47314
48159
|
context.report({
|
|
47315
48160
|
node: node.openingFragment,
|
|
47316
|
-
message: MESSAGE$
|
|
48161
|
+
message: MESSAGE$7
|
|
47317
48162
|
});
|
|
47318
48163
|
}
|
|
47319
48164
|
})
|
|
47320
48165
|
});
|
|
47321
48166
|
//#endregion
|
|
48167
|
+
//#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
|
|
48168
|
+
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.";
|
|
48169
|
+
const REACT_MARKDOWN_MODULE = "react-markdown";
|
|
48170
|
+
const REHYPE_RAW_MODULE = "rehype-raw";
|
|
48171
|
+
const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
|
|
48172
|
+
const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
|
|
48173
|
+
const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
|
|
48174
|
+
const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
|
|
48175
|
+
const DEFAULT_EXPORT_NAMES = new Set(["default"]);
|
|
48176
|
+
const getImportDeclaration = (symbol) => {
|
|
48177
|
+
if (symbol.kind !== "import") return null;
|
|
48178
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
48179
|
+
return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
|
|
48180
|
+
};
|
|
48181
|
+
const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
|
|
48182
|
+
const isDefaultImportSymbol = (symbol, moduleName) => {
|
|
48183
|
+
if (!isImportFromModule(symbol, moduleName)) return false;
|
|
48184
|
+
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
48185
|
+
};
|
|
48186
|
+
const resolveImportedIdentifier = (node, scopes) => {
|
|
48187
|
+
if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
|
|
48188
|
+
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
48189
|
+
return symbol?.kind === "import" ? symbol : null;
|
|
48190
|
+
};
|
|
48191
|
+
const getStaticMemberParts = (node) => {
|
|
48192
|
+
if (isNodeOfType(node, "MemberExpression")) {
|
|
48193
|
+
if (node.computed || !isNodeOfType(node.property, "Identifier")) return null;
|
|
48194
|
+
return {
|
|
48195
|
+
object: node.object,
|
|
48196
|
+
propertyName: node.property.name
|
|
48197
|
+
};
|
|
48198
|
+
}
|
|
48199
|
+
if (isNodeOfType(node, "JSXMemberExpression")) {
|
|
48200
|
+
if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
|
|
48201
|
+
return {
|
|
48202
|
+
object: node.object,
|
|
48203
|
+
propertyName: node.property.name
|
|
48204
|
+
};
|
|
48205
|
+
}
|
|
48206
|
+
return null;
|
|
48207
|
+
};
|
|
48208
|
+
const isNamespaceMemberFromModule = (node, moduleName, memberNames, scopes) => {
|
|
48209
|
+
const memberParts = getStaticMemberParts(node);
|
|
48210
|
+
if (!memberParts || !memberNames.has(memberParts.propertyName)) return false;
|
|
48211
|
+
const namespaceSymbol = resolveImportedIdentifier(memberParts.object, scopes);
|
|
48212
|
+
return Boolean(namespaceSymbol && isImportFromModule(namespaceSymbol, moduleName) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
|
|
48213
|
+
};
|
|
48214
|
+
const isReactMarkdownComponent = (node, scopes) => {
|
|
48215
|
+
if (isNodeOfType(node, "JSXIdentifier")) {
|
|
48216
|
+
const symbol = resolveImportedIdentifier(node, scopes);
|
|
48217
|
+
if (!symbol || !isImportFromModule(symbol, REACT_MARKDOWN_MODULE)) return false;
|
|
48218
|
+
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return true;
|
|
48219
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
48220
|
+
return importedName === "default" || Boolean(importedName && REACT_MARKDOWN_NAMED_EXPORTS.has(importedName));
|
|
48221
|
+
}
|
|
48222
|
+
return isNamespaceMemberFromModule(node, REACT_MARKDOWN_MODULE, REACT_MARKDOWN_NAMESPACE_EXPORTS, scopes);
|
|
48223
|
+
};
|
|
48224
|
+
const isPluginFromModule = (rawNode, moduleName, scopes, visitedSymbolIds) => {
|
|
48225
|
+
const node = stripParenExpression(rawNode);
|
|
48226
|
+
if (isNodeOfType(node, "Identifier")) {
|
|
48227
|
+
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
48228
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
48229
|
+
if (isDefaultImportSymbol(symbol, moduleName)) return true;
|
|
48230
|
+
if (symbol.kind !== "const" || !symbol.initializer) return false;
|
|
48231
|
+
visitedSymbolIds.add(symbol.id);
|
|
48232
|
+
return isPluginFromModule(symbol.initializer, moduleName, scopes, visitedSymbolIds);
|
|
48233
|
+
}
|
|
48234
|
+
if (isNamespaceMemberFromModule(node, moduleName, DEFAULT_EXPORT_NAMES, scopes)) return true;
|
|
48235
|
+
if (!isNodeOfType(node, "ArrayExpression")) return false;
|
|
48236
|
+
for (const element of node.elements) {
|
|
48237
|
+
if (!element || isNodeOfType(element, "SpreadElement")) continue;
|
|
48238
|
+
return isPluginFromModule(element, moduleName, scopes, visitedSymbolIds);
|
|
48239
|
+
}
|
|
48240
|
+
return false;
|
|
48241
|
+
};
|
|
48242
|
+
const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
|
|
48243
|
+
const node = stripParenExpression(rawNode);
|
|
48244
|
+
if (isNodeOfType(node, "Identifier")) {
|
|
48245
|
+
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
48246
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
48247
|
+
visitedSymbolIds.add(symbol.id);
|
|
48248
|
+
return collectPluginEntries(symbol.initializer, scopes, visitedSymbolIds);
|
|
48249
|
+
}
|
|
48250
|
+
if (!isNodeOfType(node, "ArrayExpression")) return null;
|
|
48251
|
+
const entries = [];
|
|
48252
|
+
for (const element of node.elements) {
|
|
48253
|
+
if (!element) continue;
|
|
48254
|
+
if (isNodeOfType(element, "SpreadElement")) {
|
|
48255
|
+
const spreadEntries = collectPluginEntries(element.argument, scopes, new Set(visitedSymbolIds));
|
|
48256
|
+
if (spreadEntries === null) return null;
|
|
48257
|
+
entries.push(...spreadEntries);
|
|
48258
|
+
continue;
|
|
48259
|
+
}
|
|
48260
|
+
entries.push(element);
|
|
48261
|
+
}
|
|
48262
|
+
return entries;
|
|
48263
|
+
};
|
|
48264
|
+
const findEffectiveExplicitAttribute = (attributes, attributeName) => {
|
|
48265
|
+
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
48266
|
+
const attribute = attributes[attributeIndex];
|
|
48267
|
+
if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
|
|
48268
|
+
if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
|
|
48269
|
+
}
|
|
48270
|
+
return null;
|
|
48271
|
+
};
|
|
48272
|
+
const getAttributeExpression = (attribute) => {
|
|
48273
|
+
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
48274
|
+
return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
|
|
48275
|
+
};
|
|
48276
|
+
const isDomPurifyNamespace = (node, scopes) => {
|
|
48277
|
+
const symbol = resolveImportedIdentifier(node, scopes);
|
|
48278
|
+
if (!symbol) return false;
|
|
48279
|
+
const importDeclaration = getImportDeclaration(symbol);
|
|
48280
|
+
if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
|
|
48281
|
+
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
48282
|
+
};
|
|
48283
|
+
const isDomPurifySanitizeCall = (node, scopes) => {
|
|
48284
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
48285
|
+
const memberParts = getStaticMemberParts(stripParenExpression(node.callee));
|
|
48286
|
+
return Boolean(memberParts && memberParts.propertyName === "sanitize" && isDomPurifyNamespace(memberParts.object, scopes));
|
|
48287
|
+
};
|
|
48288
|
+
const isStaticOrSanitizedMarkdownExpression = (rawNode, scopes, visitedSymbolIds) => {
|
|
48289
|
+
const node = stripParenExpression(rawNode);
|
|
48290
|
+
if (isNodeOfType(node, "Literal")) return true;
|
|
48291
|
+
if (isNodeOfType(node, "TemplateLiteral")) return node.expressions.every((expression) => isStaticOrSanitizedMarkdownExpression(expression, scopes, new Set(visitedSymbolIds)));
|
|
48292
|
+
if (isDomPurifySanitizeCall(node, scopes)) return true;
|
|
48293
|
+
if (isNodeOfType(node, "ConditionalExpression")) return isStaticOrSanitizedMarkdownExpression(node.consequent, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.alternate, scopes, new Set(visitedSymbolIds));
|
|
48294
|
+
if (isNodeOfType(node, "BinaryExpression") && node.operator === "+") return isStaticOrSanitizedMarkdownExpression(node.left, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.right, scopes, new Set(visitedSymbolIds));
|
|
48295
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
48296
|
+
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
48297
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
48298
|
+
visitedSymbolIds.add(symbol.id);
|
|
48299
|
+
return isStaticOrSanitizedMarkdownExpression(symbol.initializer, scopes, visitedSymbolIds);
|
|
48300
|
+
};
|
|
48301
|
+
const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
|
|
48302
|
+
const jsxElement = openingElement.parent;
|
|
48303
|
+
if (!isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
48304
|
+
const meaningfulChildren = jsxElement.children.filter((child) => !isNodeOfType(child, "JSXText") || child.value.trim().length > 0);
|
|
48305
|
+
if (meaningfulChildren.length > 0) return meaningfulChildren.some((child) => {
|
|
48306
|
+
if (isNodeOfType(child, "JSXText")) return false;
|
|
48307
|
+
if (!isNodeOfType(child, "JSXExpressionContainer")) return true;
|
|
48308
|
+
if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
|
|
48309
|
+
return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
|
|
48310
|
+
});
|
|
48311
|
+
const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
|
|
48312
|
+
if (!childrenAttribute) return false;
|
|
48313
|
+
const childrenExpression = getAttributeExpression(childrenAttribute);
|
|
48314
|
+
return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
|
|
48315
|
+
};
|
|
48316
|
+
const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
48317
|
+
id: "react-markdown-unsanitized-raw-html",
|
|
48318
|
+
title: "Unsanitized raw HTML in React Markdown",
|
|
48319
|
+
severity: "warn",
|
|
48320
|
+
recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
|
|
48321
|
+
create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
|
|
48322
|
+
if (!isReactMarkdownComponent(node.name, context.scopes)) return;
|
|
48323
|
+
const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
|
|
48324
|
+
if (!pluginsAttribute) return;
|
|
48325
|
+
const pluginsExpression = getAttributeExpression(pluginsAttribute);
|
|
48326
|
+
if (!pluginsExpression) return;
|
|
48327
|
+
const pluginEntries = collectPluginEntries(pluginsExpression, context.scopes, /* @__PURE__ */ new Set());
|
|
48328
|
+
if (pluginEntries === null) return;
|
|
48329
|
+
if (!pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_RAW_MODULE, context.scopes, /* @__PURE__ */ new Set()))) return;
|
|
48330
|
+
if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
|
|
48331
|
+
context.report({
|
|
48332
|
+
node,
|
|
48333
|
+
message: MESSAGE$6
|
|
48334
|
+
});
|
|
48335
|
+
} }))
|
|
48336
|
+
});
|
|
48337
|
+
//#endregion
|
|
47322
48338
|
//#region src/plugin/utils/collect-react-redux-selector-aliases.ts
|
|
47323
48339
|
const REACT_REDUX_MODULE = "react-redux";
|
|
47324
48340
|
const collectReactReduxSelectorAliases = (programRoot) => {
|
|
@@ -50215,7 +51231,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
|
|
|
50215
51231
|
};
|
|
50216
51232
|
const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
|
|
50217
51233
|
const isInsideStyleFactoryCallback = (node) => {
|
|
50218
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
51234
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
50219
51235
|
if (!enclosingFunction) return false;
|
|
50220
51236
|
const callExpression = enclosingFunction.parent;
|
|
50221
51237
|
if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -55746,22 +56762,6 @@ const isInsideClassComponent = (node) => {
|
|
|
55746
56762
|
}
|
|
55747
56763
|
return false;
|
|
55748
56764
|
};
|
|
55749
|
-
const hasShortCircuitAncestor = (descendant, ancestor) => {
|
|
55750
|
-
let current = descendant.parent;
|
|
55751
|
-
while (current && current !== ancestor) {
|
|
55752
|
-
if (isNodeOfType(current, "ConditionalExpression") && (isWithinRange(descendant, current.consequent) || isWithinRange(descendant, current.alternate))) return true;
|
|
55753
|
-
if (isNodeOfType(current, "LogicalExpression") && (current.operator === "&&" || current.operator === "||" || current.operator === "??") && isWithinRange(descendant, current.right)) return true;
|
|
55754
|
-
if (isNodeOfType(current, "AssignmentPattern") && isWithinRange(descendant, current.right)) return true;
|
|
55755
|
-
current = current.parent ?? null;
|
|
55756
|
-
}
|
|
55757
|
-
return false;
|
|
55758
|
-
};
|
|
55759
|
-
const isWithinRange = (descendant, sibling) => {
|
|
55760
|
-
const descendantSpan = descendant;
|
|
55761
|
-
const siblingSpan = sibling;
|
|
55762
|
-
if (typeof descendantSpan.start !== "number" || typeof siblingSpan.start !== "number" || typeof siblingSpan.end !== "number") return false;
|
|
55763
|
-
return descendantSpan.start >= siblingSpan.start && (descendantSpan.end ?? siblingSpan.end) <= siblingSpan.end;
|
|
55764
|
-
};
|
|
55765
56765
|
const isInsideTry = (descendant, ancestor) => {
|
|
55766
56766
|
let current = descendant.parent;
|
|
55767
56767
|
while (current && current !== ancestor) {
|
|
@@ -55953,7 +56953,7 @@ const rulesOfHooks = defineRule({
|
|
|
55953
56953
|
});
|
|
55954
56954
|
return;
|
|
55955
56955
|
}
|
|
55956
|
-
if (
|
|
56956
|
+
if (isNodeConditionallyExecuted(node, enclosing.node)) {
|
|
55957
56957
|
context.report({
|
|
55958
56958
|
node: node.callee,
|
|
55959
56959
|
message: buildConditionalMessage(hookName)
|
|
@@ -57087,12 +58087,14 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
|
|
|
57087
58087
|
}
|
|
57088
58088
|
return false;
|
|
57089
58089
|
};
|
|
57090
|
-
const declarationAwaitsGate = (declaration) => {
|
|
58090
|
+
const declarationAwaitsGate = (declaration, context) => {
|
|
57091
58091
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
57092
58092
|
for (const declarator of declaration.declarations ?? []) {
|
|
57093
58093
|
if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
|
|
57094
58094
|
const argument = declarator.init.argument;
|
|
57095
58095
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
58096
|
+
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
58097
|
+
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
57096
58098
|
const calleeName = getCalleeName$2(argument);
|
|
57097
58099
|
if (!calleeName) continue;
|
|
57098
58100
|
if (isAuthGuardName(calleeName)) return true;
|
|
@@ -57120,7 +58122,7 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
57120
58122
|
if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
|
|
57121
58123
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
57122
58124
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
57123
|
-
if (declarationAwaitsGate(currentStatement)) continue;
|
|
58125
|
+
if (declarationAwaitsGate(currentStatement, context)) continue;
|
|
57124
58126
|
context.report({
|
|
57125
58127
|
node: nextStatement,
|
|
57126
58128
|
message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
|
|
@@ -58106,7 +59108,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
58106
59108
|
const isReturnedFromCustomHook = (functionNode) => {
|
|
58107
59109
|
const parent = functionNode.parent;
|
|
58108
59110
|
if (isNodeOfType(parent, "ReturnStatement")) {
|
|
58109
|
-
const outerFunction = findEnclosingFunction(parent);
|
|
59111
|
+
const outerFunction = findEnclosingFunction$1(parent);
|
|
58110
59112
|
const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
|
|
58111
59113
|
return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
|
|
58112
59114
|
}
|
|
@@ -58123,13 +59125,13 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
58123
59125
|
return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
|
|
58124
59126
|
};
|
|
58125
59127
|
const isDeferredNavigateCall = (callNode) => {
|
|
58126
|
-
let enclosingFunction = findEnclosingFunction(callNode);
|
|
59128
|
+
let enclosingFunction = findEnclosingFunction$1(callNode);
|
|
58127
59129
|
while (enclosingFunction) {
|
|
58128
59130
|
if (isDeferredCallbackPosition(enclosingFunction)) return true;
|
|
58129
59131
|
if (isWiredAsEventHandler(enclosingFunction)) return true;
|
|
58130
59132
|
if (isReturnedFromCustomHook(enclosingFunction)) return true;
|
|
58131
59133
|
if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
|
|
58132
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
59134
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
58133
59135
|
}
|
|
58134
59136
|
return false;
|
|
58135
59137
|
};
|
|
@@ -62568,6 +63570,17 @@ const reactDoctorRules = [
|
|
|
62568
63570
|
requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
|
|
62569
63571
|
}
|
|
62570
63572
|
},
|
|
63573
|
+
{
|
|
63574
|
+
key: "react-doctor/react-markdown-unsanitized-raw-html",
|
|
63575
|
+
id: "react-markdown-unsanitized-raw-html",
|
|
63576
|
+
source: "react-doctor",
|
|
63577
|
+
originallyExternal: false,
|
|
63578
|
+
rule: {
|
|
63579
|
+
...reactMarkdownUnsanitizedRawHtml,
|
|
63580
|
+
framework: "global",
|
|
63581
|
+
category: "Security"
|
|
63582
|
+
}
|
|
63583
|
+
},
|
|
62571
63584
|
{
|
|
62572
63585
|
key: "react-doctor/redux-useselector-inline-derivation",
|
|
62573
63586
|
id: "redux-useselector-inline-derivation",
|