oxlint-plugin-react-doctor 0.7.7-dev.bdd1321 → 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 +2163 -727
- 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 {
|
|
@@ -11003,6 +11499,14 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
|
|
|
11003
11499
|
"catch",
|
|
11004
11500
|
"finally"
|
|
11005
11501
|
]);
|
|
11502
|
+
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11503
|
+
const getPromiseChainCallForCallback = (candidate) => {
|
|
11504
|
+
let callbackContainer = candidate.parent;
|
|
11505
|
+
while (callbackContainer && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callbackContainer.type)) callbackContainer = callbackContainer.parent;
|
|
11506
|
+
if (!isNodeOfType(callbackContainer, "CallExpression")) return null;
|
|
11507
|
+
if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
|
|
11508
|
+
return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
|
|
11509
|
+
};
|
|
11006
11510
|
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
11007
11511
|
const invokedFunctions = new Set([effectCallback]);
|
|
11008
11512
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
@@ -11014,7 +11518,6 @@ const collectEffectInvokedFunctions = (effectCallback) => {
|
|
|
11014
11518
|
invokedFunctions.add(strippedCandidate);
|
|
11015
11519
|
pendingFunctions.push(strippedCandidate);
|
|
11016
11520
|
};
|
|
11017
|
-
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11018
11521
|
while (pendingFunctions.length > 0) {
|
|
11019
11522
|
const currentFunction = pendingFunctions.pop();
|
|
11020
11523
|
if (!currentFunction) break;
|
|
@@ -11086,7 +11589,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
|
|
|
11086
11589
|
//#endregion
|
|
11087
11590
|
//#region src/plugin/utils/enclosing-component-or-hook-name.ts
|
|
11088
11591
|
const enclosingComponentOrHookName = (node) => {
|
|
11089
|
-
const functionNode = findEnclosingFunction(node);
|
|
11592
|
+
const functionNode = findEnclosingFunction$1(node);
|
|
11090
11593
|
return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
|
|
11091
11594
|
};
|
|
11092
11595
|
//#endregion
|
|
@@ -11143,11 +11646,11 @@ const executesDuringRender = (functionNode, scopes) => {
|
|
|
11143
11646
|
//#endregion
|
|
11144
11647
|
//#region src/plugin/utils/find-render-phase-component-or-hook.ts
|
|
11145
11648
|
const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
11146
|
-
let functionNode = findEnclosingFunction(node);
|
|
11649
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
11147
11650
|
while (functionNode) {
|
|
11148
11651
|
if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
|
|
11149
11652
|
if (!executesDuringRender(functionNode, scopes)) return null;
|
|
11150
|
-
functionNode = findEnclosingFunction(functionNode);
|
|
11653
|
+
functionNode = findEnclosingFunction$1(functionNode);
|
|
11151
11654
|
}
|
|
11152
11655
|
return null;
|
|
11153
11656
|
};
|
|
@@ -11222,6 +11725,50 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
|
|
|
11222
11725
|
return true;
|
|
11223
11726
|
};
|
|
11224
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
|
|
11225
11772
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
11226
11773
|
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
11227
11774
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
@@ -11350,36 +11897,15 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
11350
11897
|
});
|
|
11351
11898
|
return usages.filter((usage) => isNodeReachableWithinFunction(usage.node, context));
|
|
11352
11899
|
};
|
|
11353
|
-
const isNodeReachableWithinFunction = (node, context) => {
|
|
11354
|
-
const owner = context.cfg.enclosingFunction(node);
|
|
11355
|
-
if (!owner) return true;
|
|
11356
|
-
const functionCfg = context.cfg.cfgFor(owner);
|
|
11357
|
-
if (!functionCfg) return true;
|
|
11358
|
-
const targetBlock = functionCfg.blockOf(node);
|
|
11359
|
-
if (!targetBlock) return true;
|
|
11360
|
-
const visitedBlocks = new Set([functionCfg.entry]);
|
|
11361
|
-
const pendingBlocks = [functionCfg.entry];
|
|
11362
|
-
while (pendingBlocks.length > 0) {
|
|
11363
|
-
const currentBlock = pendingBlocks.pop();
|
|
11364
|
-
if (!currentBlock) break;
|
|
11365
|
-
if (currentBlock === targetBlock) return true;
|
|
11366
|
-
for (const edge of currentBlock.successors) {
|
|
11367
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
11368
|
-
visitedBlocks.add(edge.to);
|
|
11369
|
-
pendingBlocks.push(edge.to);
|
|
11370
|
-
}
|
|
11371
|
-
}
|
|
11372
|
-
return false;
|
|
11373
|
-
};
|
|
11374
11900
|
const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
|
|
11375
11901
|
let pathAnchor = usageNode;
|
|
11376
|
-
let pathOwner = findEnclosingFunction(pathAnchor);
|
|
11902
|
+
let pathOwner = findEnclosingFunction$1(pathAnchor);
|
|
11377
11903
|
while (pathOwner && isSynchronousIteratorCallback(pathOwner)) {
|
|
11378
11904
|
if (matchingNodes.length > 0 && matchingNodes.every((matchingNode) => context.cfg.enclosingFunction(matchingNode) === pathOwner)) break;
|
|
11379
11905
|
const iteratorCall = pathOwner.parent;
|
|
11380
11906
|
if (!isNodeOfType(iteratorCall, "CallExpression")) break;
|
|
11381
11907
|
pathAnchor = iteratorCall;
|
|
11382
|
-
pathOwner = findEnclosingFunction(pathAnchor);
|
|
11908
|
+
pathOwner = findEnclosingFunction$1(pathAnchor);
|
|
11383
11909
|
}
|
|
11384
11910
|
const owner = context.cfg.enclosingFunction(pathAnchor);
|
|
11385
11911
|
if (!owner) return false;
|
|
@@ -11608,7 +12134,7 @@ const findCollectionMappingCall = (callbackNode) => {
|
|
|
11608
12134
|
return callee.property.name === "map" && callNode.arguments?.[0] === callbackNode ? callNode : null;
|
|
11609
12135
|
};
|
|
11610
12136
|
const findMappedResourceCollectionKey = (resourceNode, context) => {
|
|
11611
|
-
const callbackNode = findEnclosingFunction(resourceNode);
|
|
12137
|
+
const callbackNode = findEnclosingFunction$1(resourceNode);
|
|
11612
12138
|
if (!callbackNode || !isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression")) return null;
|
|
11613
12139
|
const mappingCall = findCollectionMappingCall(callbackNode);
|
|
11614
12140
|
if (!mappingCall) return null;
|
|
@@ -11719,10 +12245,10 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
11719
12245
|
});
|
|
11720
12246
|
if (invocationCalls.length !== 1) return null;
|
|
11721
12247
|
const invocationCall = invocationCalls[0];
|
|
11722
|
-
return findEnclosingFunction(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
12248
|
+
return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
11723
12249
|
};
|
|
11724
12250
|
const resolveCleanupPathAnchor = (usageNode, effectCallback, context) => {
|
|
11725
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
12251
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
11726
12252
|
if (!usageFunction || usageFunction === effectCallback) return usageNode;
|
|
11727
12253
|
return findSingleDirectInvocation(usageFunction, effectCallback, context) ?? usageNode;
|
|
11728
12254
|
};
|
|
@@ -11737,7 +12263,7 @@ const resolveSingleAssignedCleanupFunction = (expression, usage, context) => {
|
|
|
11737
12263
|
const assignmentReference = assignmentReferences[0];
|
|
11738
12264
|
const assignmentTarget = findTransparentExpressionRoot(assignmentReference.identifier);
|
|
11739
12265
|
const assignmentNode = assignmentTarget.parent;
|
|
11740
|
-
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;
|
|
11741
12267
|
const assignedValue = stripParenExpression(assignmentNode.right);
|
|
11742
12268
|
return isFunctionLike$2(assignedValue) ? assignedValue : null;
|
|
11743
12269
|
};
|
|
@@ -11826,12 +12352,12 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
11826
12352
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
11827
12353
|
};
|
|
11828
12354
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
11829
|
-
const componentFunction = findEnclosingFunction(callback);
|
|
12355
|
+
const componentFunction = findEnclosingFunction$1(callback);
|
|
11830
12356
|
if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
|
|
11831
12357
|
let didFindUnmountCleanup = false;
|
|
11832
12358
|
walkAst(componentFunction.body, (child) => {
|
|
11833
12359
|
if (didFindUnmountCleanup) return false;
|
|
11834
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
|
|
12360
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
|
|
11835
12361
|
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
11836
12362
|
const dependencyList = child.arguments?.[1];
|
|
11837
12363
|
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
@@ -11844,10 +12370,98 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
11844
12370
|
return didFindUnmountCleanup;
|
|
11845
12371
|
};
|
|
11846
12372
|
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
12373
|
+
const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
|
|
12374
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
12375
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
|
|
12376
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
12377
|
+
if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
|
|
12378
|
+
return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
|
|
12379
|
+
}
|
|
12380
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
|
|
12381
|
+
"===",
|
|
12382
|
+
"==",
|
|
12383
|
+
"!==",
|
|
12384
|
+
"!="
|
|
12385
|
+
].includes(unwrappedExpression.operator)) {
|
|
12386
|
+
const leftValue = readStaticBoolean(unwrappedExpression.left);
|
|
12387
|
+
const rightValue = readStaticBoolean(unwrappedExpression.right);
|
|
12388
|
+
const booleanValue = leftValue ?? rightValue;
|
|
12389
|
+
const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
|
|
12390
|
+
if (booleanValue === null || comparedKey === null) return [];
|
|
12391
|
+
return [{
|
|
12392
|
+
key: comparedKey,
|
|
12393
|
+
value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
|
|
12394
|
+
}];
|
|
12395
|
+
}
|
|
12396
|
+
const expressionKey = resolveExpressionKey(unwrappedExpression, context);
|
|
12397
|
+
return expressionKey === null ? [] : [{
|
|
12398
|
+
key: expressionKey,
|
|
12399
|
+
value: blockedExpressionValue
|
|
12400
|
+
}];
|
|
12401
|
+
};
|
|
12402
|
+
const isDirectEarlyReturnConsequent = (ifStatement) => {
|
|
12403
|
+
if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
|
|
12404
|
+
if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
|
|
12405
|
+
return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
|
|
12406
|
+
};
|
|
12407
|
+
const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
|
|
12408
|
+
if (!isFunctionLike$2(callback) || callback.async) return [];
|
|
12409
|
+
const guardStates = [];
|
|
12410
|
+
walkAst(callback.body, (child) => {
|
|
12411
|
+
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
12412
|
+
if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
|
|
12413
|
+
});
|
|
12414
|
+
let descendant = usageNode;
|
|
12415
|
+
let ancestor = descendant.parent;
|
|
12416
|
+
while (ancestor && ancestor !== callback) {
|
|
12417
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
|
|
12418
|
+
descendant = ancestor;
|
|
12419
|
+
ancestor = ancestor.parent;
|
|
12420
|
+
}
|
|
12421
|
+
return guardStates;
|
|
12422
|
+
};
|
|
12423
|
+
const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
|
|
12424
|
+
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
12425
|
+
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
12426
|
+
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction) || cleanupFunction.async) return false;
|
|
12427
|
+
let didInvalidateGuard = false;
|
|
12428
|
+
walkAst(cleanupFunction.body, (child) => {
|
|
12429
|
+
if (didInvalidateGuard) return false;
|
|
12430
|
+
if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
|
|
12431
|
+
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveExpressionKey(child.left, context) === guardState.key && readStaticBoolean(child.right) === guardState.value && context.cfg.isUnconditionalFromEntry(child)) {
|
|
12432
|
+
didInvalidateGuard = true;
|
|
12433
|
+
return false;
|
|
12434
|
+
}
|
|
12435
|
+
});
|
|
12436
|
+
return didInvalidateGuard;
|
|
12437
|
+
};
|
|
12438
|
+
const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, context) => {
|
|
12439
|
+
const usageStart = getRangeStart(usageNode);
|
|
12440
|
+
if (!isFunctionLike$2(callback) || usageStart === null) return true;
|
|
12441
|
+
let didWriteGuard = false;
|
|
12442
|
+
walkAst(callback.body, (child) => {
|
|
12443
|
+
if (didWriteGuard) return false;
|
|
12444
|
+
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
12445
|
+
const childStart = getRangeStart(child);
|
|
12446
|
+
if (childStart === null || childStart >= usageStart) return;
|
|
12447
|
+
const writtenExpression = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : null;
|
|
12448
|
+
if (writtenExpression && resolveExpressionKey(writtenExpression, context) === guardState.key) {
|
|
12449
|
+
didWriteGuard = true;
|
|
12450
|
+
return false;
|
|
12451
|
+
}
|
|
12452
|
+
});
|
|
12453
|
+
return didWriteGuard;
|
|
12454
|
+
};
|
|
12455
|
+
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
12456
|
+
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
12457
|
+
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
12458
|
+
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
12459
|
+
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
12460
|
+
};
|
|
11847
12461
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
11848
12462
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
11849
12463
|
if (callback.async) return false;
|
|
11850
|
-
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;
|
|
11851
12465
|
if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
|
|
11852
12466
|
const matchingCleanupReturns = [];
|
|
11853
12467
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
@@ -11883,6 +12497,7 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
11883
12497
|
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
|
|
11884
12498
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
11885
12499
|
});
|
|
12500
|
+
if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
|
|
11886
12501
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
11887
12502
|
};
|
|
11888
12503
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
@@ -12015,7 +12630,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
|
12015
12630
|
parentNode = currentNode.parent;
|
|
12016
12631
|
}
|
|
12017
12632
|
if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
|
|
12018
|
-
const effectCallback = findEnclosingFunction(parentNode);
|
|
12633
|
+
const effectCallback = findEnclosingFunction$1(parentNode);
|
|
12019
12634
|
const effectCall = effectCallback?.parent;
|
|
12020
12635
|
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
12021
12636
|
};
|
|
@@ -12025,13 +12640,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
12025
12640
|
if (!bindingIdentifier) return false;
|
|
12026
12641
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
12027
12642
|
if (!symbol) return false;
|
|
12028
|
-
return symbol.references.some((reference) => findEnclosingFunction(reference.identifier) !== functionNode);
|
|
12643
|
+
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
12029
12644
|
};
|
|
12030
12645
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
12031
12646
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
12032
|
-
const releaseFunction = findEnclosingFunction(releaseNode);
|
|
12647
|
+
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
12033
12648
|
if (!releaseFunction) return true;
|
|
12034
|
-
if (releaseFunction === findEnclosingFunction(usage.node)) return true;
|
|
12649
|
+
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
12035
12650
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
12036
12651
|
};
|
|
12037
12652
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -12214,10 +12829,10 @@ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction
|
|
|
12214
12829
|
}
|
|
12215
12830
|
if (assignedKey !== storage.refCurrentKey) return;
|
|
12216
12831
|
const assignedValue = stripParenExpression(child.right);
|
|
12217
|
-
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction(child) === cleanupFunction) return;
|
|
12832
|
+
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction$1(child) === cleanupFunction) return;
|
|
12218
12833
|
const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
|
|
12219
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);
|
|
12220
|
-
if (findEnclosingFunction(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12835
|
+
if (findEnclosingFunction$1(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12221
12836
|
hasUnsafeRefWrite = true;
|
|
12222
12837
|
return false;
|
|
12223
12838
|
}
|
|
@@ -12238,12 +12853,12 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
12238
12853
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
12239
12854
|
};
|
|
12240
12855
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
12241
|
-
const componentFunction = findEnclosingFunction(retainedFunction);
|
|
12856
|
+
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
12242
12857
|
if (!componentFunction || !isFunctionLike$2(componentFunction)) return false;
|
|
12243
12858
|
let didFindCleanupEffect = false;
|
|
12244
12859
|
walkAst(componentFunction.body, (child) => {
|
|
12245
12860
|
if (didFindCleanupEffect) return false;
|
|
12246
|
-
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;
|
|
12247
12862
|
const effectStatement = findTransparentExpressionRoot(child).parent;
|
|
12248
12863
|
if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
|
|
12249
12864
|
const effectCallback = getEffectCallback(child);
|
|
@@ -13097,6 +13712,202 @@ const resolveExhaustiveDepsSettings = (settings) => {
|
|
|
13097
13712
|
};
|
|
13098
13713
|
};
|
|
13099
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
|
|
13100
13911
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-suppression.ts
|
|
13101
13912
|
const DISABLE_COMMENT_RULE_NAME_PATTERN$1 = /(?:^|[\s,/])exhaustive-deps(?:$|[\s,:])/;
|
|
13102
13913
|
const DISABLE_NEXT_LINE_PATTERN$1 = /\b(?:eslint|oxlint)-disable-next-line\b([^\n]*)/;
|
|
@@ -13168,25 +13979,6 @@ const isExhaustiveDepsSuppressedAt = (filename, nodeStartOffset) => {
|
|
|
13168
13979
|
return index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf16NewlineOffsets)) || index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf8NewlineOffsets));
|
|
13169
13980
|
};
|
|
13170
13981
|
//#endregion
|
|
13171
|
-
//#region src/plugin/utils/is-ast-descendant.ts
|
|
13172
|
-
/**
|
|
13173
|
-
* True when `inner` is `outer` itself or any descendant in the AST
|
|
13174
|
-
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
13175
|
-
* match (`true`) or the chain's root (`false`).
|
|
13176
|
-
*
|
|
13177
|
-
* Was duplicated byte-identical in:
|
|
13178
|
-
* - exhaustive-deps-symbol-stability.ts
|
|
13179
|
-
* - semantic/closure-captures.ts
|
|
13180
|
-
*/
|
|
13181
|
-
const isAstDescendant = (inner, outer) => {
|
|
13182
|
-
let current = inner;
|
|
13183
|
-
while (current) {
|
|
13184
|
-
if (current === outer) return true;
|
|
13185
|
-
current = current.parent ?? null;
|
|
13186
|
-
}
|
|
13187
|
-
return false;
|
|
13188
|
-
};
|
|
13189
|
-
//#endregion
|
|
13190
13982
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
|
|
13191
13983
|
/**
|
|
13192
13984
|
* Symbol-stability helpers consumed by the `exhaustive-deps` rule.
|
|
@@ -13367,6 +14159,7 @@ const EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS = new Set([
|
|
|
13367
14159
|
"useLayoutEffect",
|
|
13368
14160
|
"useInsertionEffect"
|
|
13369
14161
|
]);
|
|
14162
|
+
const SOLE_WRITER_GUARD_HOOKS = new Set(["useEffect", "useLayoutEffect"]);
|
|
13370
14163
|
const buildAdditionalHooksRegex = (additional) => {
|
|
13371
14164
|
if (!additional) return null;
|
|
13372
14165
|
try {
|
|
@@ -13503,7 +14296,7 @@ const stringifyMemberChain = (node) => {
|
|
|
13503
14296
|
}
|
|
13504
14297
|
return null;
|
|
13505
14298
|
};
|
|
13506
|
-
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
14299
|
+
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allowSoleWriterEffectGuards = false) => {
|
|
13507
14300
|
const keys = /* @__PURE__ */ new Set();
|
|
13508
14301
|
const stableCapturedNames = /* @__PURE__ */ new Set();
|
|
13509
14302
|
const moduleScopeCapturedNames = /* @__PURE__ */ new Set();
|
|
@@ -13514,6 +14307,10 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
|
13514
14307
|
const symbol = reference.resolvedSymbol;
|
|
13515
14308
|
if (!symbol) continue;
|
|
13516
14309
|
if (isRecursiveInitializerCapture(symbol, callback)) continue;
|
|
14310
|
+
if (allowSoleWriterEffectGuards && isSoleWriterEffectGuardCapture(symbol, callback, scopes)) {
|
|
14311
|
+
stableCapturedNames.add(symbol.name);
|
|
14312
|
+
continue;
|
|
14313
|
+
}
|
|
13517
14314
|
if (symbolHasStableValue(symbol, scopes)) {
|
|
13518
14315
|
stableCapturedNames.add(symbol.name);
|
|
13519
14316
|
continue;
|
|
@@ -14186,7 +14983,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
14186
14983
|
if (isNodeOfType(stripped, "Identifier")) declaredExactBindingKeys.add(key);
|
|
14187
14984
|
declaredKeyToReportNode.set(key, elementNode);
|
|
14188
14985
|
}
|
|
14189
|
-
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));
|
|
14190
14987
|
for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
|
|
14191
14988
|
addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
|
|
14192
14989
|
const missingCaptureKeys = [];
|
|
@@ -14626,7 +15423,7 @@ const forbidElements = defineRule({
|
|
|
14626
15423
|
});
|
|
14627
15424
|
//#endregion
|
|
14628
15425
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
14629
|
-
const MESSAGE$
|
|
15426
|
+
const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
14630
15427
|
const forwardRefUsesRef = defineRule({
|
|
14631
15428
|
id: "forward-ref-uses-ref",
|
|
14632
15429
|
title: "forwardRef without ref parameter",
|
|
@@ -14649,7 +15446,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
14649
15446
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
14650
15447
|
context.report({
|
|
14651
15448
|
node: inner,
|
|
14652
|
-
message: MESSAGE$
|
|
15449
|
+
message: MESSAGE$54
|
|
14653
15450
|
});
|
|
14654
15451
|
} })
|
|
14655
15452
|
});
|
|
@@ -14690,7 +15487,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
14690
15487
|
});
|
|
14691
15488
|
//#endregion
|
|
14692
15489
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
14693
|
-
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`.";
|
|
14694
15491
|
const DEFAULT_HEADING_TAGS = [
|
|
14695
15492
|
"h1",
|
|
14696
15493
|
"h2",
|
|
@@ -14724,7 +15521,7 @@ const headingHasContent = defineRule({
|
|
|
14724
15521
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
14725
15522
|
context.report({
|
|
14726
15523
|
node,
|
|
14727
|
-
message: MESSAGE$
|
|
15524
|
+
message: MESSAGE$53
|
|
14728
15525
|
});
|
|
14729
15526
|
} };
|
|
14730
15527
|
}
|
|
@@ -14888,7 +15685,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
14888
15685
|
});
|
|
14889
15686
|
//#endregion
|
|
14890
15687
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
14891
|
-
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`.";
|
|
14892
15689
|
const resolveSettings$39 = (settings) => {
|
|
14893
15690
|
const reactDoctor = settings?.["react-doctor"];
|
|
14894
15691
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -14935,13 +15732,13 @@ const htmlHasLang = defineRule({
|
|
|
14935
15732
|
if (!lang) {
|
|
14936
15733
|
context.report({
|
|
14937
15734
|
node: node.name,
|
|
14938
|
-
message: MESSAGE$
|
|
15735
|
+
message: MESSAGE$52
|
|
14939
15736
|
});
|
|
14940
15737
|
return;
|
|
14941
15738
|
}
|
|
14942
15739
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
14943
15740
|
node: lang,
|
|
14944
|
-
message: MESSAGE$
|
|
15741
|
+
message: MESSAGE$52
|
|
14945
15742
|
});
|
|
14946
15743
|
} };
|
|
14947
15744
|
}
|
|
@@ -15181,7 +15978,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
15181
15978
|
});
|
|
15182
15979
|
//#endregion
|
|
15183
15980
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
15184
|
-
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.";
|
|
15185
15982
|
const evaluateTitleValue = (value) => {
|
|
15186
15983
|
if (!value) return "missing";
|
|
15187
15984
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -15221,14 +16018,14 @@ const iframeHasTitle = defineRule({
|
|
|
15221
16018
|
if (!titleAttr) {
|
|
15222
16019
|
if (hasSpread || tag === "iframe") context.report({
|
|
15223
16020
|
node: node.name,
|
|
15224
|
-
message: MESSAGE$
|
|
16021
|
+
message: MESSAGE$51
|
|
15225
16022
|
});
|
|
15226
16023
|
return;
|
|
15227
16024
|
}
|
|
15228
16025
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
15229
16026
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
15230
16027
|
node: titleAttr,
|
|
15231
|
-
message: MESSAGE$
|
|
16028
|
+
message: MESSAGE$51
|
|
15232
16029
|
});
|
|
15233
16030
|
} })
|
|
15234
16031
|
});
|
|
@@ -15353,7 +16150,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
15353
16150
|
});
|
|
15354
16151
|
//#endregion
|
|
15355
16152
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
15356
|
-
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.";
|
|
15357
16154
|
const DEFAULT_COMPONENTS = ["img"];
|
|
15358
16155
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
15359
16156
|
"image",
|
|
@@ -15418,7 +16215,7 @@ const imgRedundantAlt = defineRule({
|
|
|
15418
16215
|
if (!altAttribute) return;
|
|
15419
16216
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
15420
16217
|
node: altAttribute,
|
|
15421
|
-
message: MESSAGE$
|
|
16218
|
+
message: MESSAGE$50
|
|
15422
16219
|
});
|
|
15423
16220
|
} };
|
|
15424
16221
|
}
|
|
@@ -16000,14 +16797,14 @@ const isBindingInvokedOnRenderPath = (root, bindingName) => {
|
|
|
16000
16797
|
if (didFindRenderPathInvocation) return false;
|
|
16001
16798
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
16002
16799
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== bindingName) return;
|
|
16003
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
16800
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
16004
16801
|
while (enclosingFunction) {
|
|
16005
16802
|
if (isDeferredCallbackPosition$1(enclosingFunction) || getHandlerNamedBindingName(enclosingFunction)) return;
|
|
16006
16803
|
if (containingFunctionIsComponentOrHook(enclosingFunction)) {
|
|
16007
16804
|
didFindRenderPathInvocation = true;
|
|
16008
16805
|
return;
|
|
16009
16806
|
}
|
|
16010
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
16807
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
16011
16808
|
}
|
|
16012
16809
|
});
|
|
16013
16810
|
return didFindRenderPathInvocation;
|
|
@@ -16042,7 +16839,7 @@ const jotaiSelectAtomInRenderBody = defineRule({
|
|
|
16042
16839
|
};
|
|
16043
16840
|
return { CallExpression(node) {
|
|
16044
16841
|
if (!isImportedSelectAtom(node)) return;
|
|
16045
|
-
const nearestFunctionLike = findEnclosingFunction(node);
|
|
16842
|
+
const nearestFunctionLike = findEnclosingFunction$1(node);
|
|
16046
16843
|
if (!nearestFunctionLike) return;
|
|
16047
16844
|
if (isDeferredCallback(nearestFunctionLike)) return;
|
|
16048
16845
|
if (isBindingUsedAsHandler(nearestFunctionLike)) return;
|
|
@@ -17039,7 +17836,7 @@ const isPersistentCacheSetWrite = (callExpression, newExpression, enclosingFunct
|
|
|
17039
17836
|
return !isAstDescendant(receiverBinding.scopeOwner, enclosingFunction);
|
|
17040
17837
|
};
|
|
17041
17838
|
const isInsideCacheMemo = (node) => {
|
|
17042
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
17839
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
17043
17840
|
let child = node;
|
|
17044
17841
|
let cursor = node.parent ?? null;
|
|
17045
17842
|
while (cursor) {
|
|
@@ -17077,7 +17874,7 @@ const getFunctionName = (functionNode) => {
|
|
|
17077
17874
|
return null;
|
|
17078
17875
|
};
|
|
17079
17876
|
const isUncacheableOptionsMergeUtility = (node) => {
|
|
17080
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
17877
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
17081
17878
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction)) return false;
|
|
17082
17879
|
const functionName = getFunctionName(enclosingFunction);
|
|
17083
17880
|
if (!functionName || isComponentOrHookName(functionName)) return false;
|
|
@@ -17201,7 +17998,7 @@ const GLOBAL_BUILTIN_NAMES = new Set([
|
|
|
17201
17998
|
]);
|
|
17202
17999
|
const STRING_PROTOTYPE_PATH = "String.prototype";
|
|
17203
18000
|
const REGEXP_PROTOTYPE_PATH = "RegExp.prototype";
|
|
17204
|
-
const getStaticStringValue = (argument) => {
|
|
18001
|
+
const getStaticStringValue$1 = (argument) => {
|
|
17205
18002
|
if (!argument) return null;
|
|
17206
18003
|
const unwrappedArgument = stripParenExpression(argument);
|
|
17207
18004
|
if (isNodeOfType(unwrappedArgument, "Literal") && typeof unwrappedArgument.value === "string") return unwrappedArgument.value;
|
|
@@ -17209,7 +18006,7 @@ const getStaticStringValue = (argument) => {
|
|
|
17209
18006
|
return null;
|
|
17210
18007
|
};
|
|
17211
18008
|
const getEffectiveRegExpFlags = (patternArgument, flagsArgument) => {
|
|
17212
|
-
if (flagsArgument) return getStaticStringValue(flagsArgument);
|
|
18009
|
+
if (flagsArgument) return getStaticStringValue$1(flagsArgument);
|
|
17213
18010
|
if (!patternArgument) return "";
|
|
17214
18011
|
const unwrappedPattern = stripParenExpression(patternArgument);
|
|
17215
18012
|
if (isNodeOfType(unwrappedPattern, "Literal") && unwrappedPattern.value instanceof RegExp) return unwrappedPattern.value.flags;
|
|
@@ -17268,13 +18065,6 @@ const isSafeStatefulReplaceAllSearch = (node, flags, context) => {
|
|
|
17268
18065
|
const callee = stripParenExpression(replaceAllCall.callee);
|
|
17269
18066
|
return isNodeOfType(callee, "MemberExpression") && !callee.computed && !callee.optional && !replaceAllCall.optional && isNodeOfType(callee.property, "Identifier") && callee.property.name === "replaceAll" && isProvenNativeStringReceiver(callee.object, context);
|
|
17270
18067
|
};
|
|
17271
|
-
const getDestructuredBindingPropertyName = (bindingIdentifier) => {
|
|
17272
|
-
let bindingNode = bindingIdentifier;
|
|
17273
|
-
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
17274
|
-
const property = bindingNode.parent;
|
|
17275
|
-
if (!isNodeOfType(property, "Property") || property.value !== bindingNode || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
17276
|
-
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
17277
|
-
};
|
|
17278
18068
|
const extendGlobalPath = (basePath, propertyName) => {
|
|
17279
18069
|
if (basePath === "global") {
|
|
17280
18070
|
if (propertyName === null || GLOBAL_OBJECT_NAMES.has(propertyName)) return "global";
|
|
@@ -17359,7 +18149,7 @@ const getCallRegExpHazard = (node, context, symbolCache) => {
|
|
|
17359
18149
|
const mutationHazard = targetPath === "global" ? "globalRegExpReplaced" : "replaceAllIntegrityLost";
|
|
17360
18150
|
const guardedPropertyName = targetPath === "global" ? "RegExp" : "replaceAll";
|
|
17361
18151
|
if (isSinglePropertyMutation) {
|
|
17362
|
-
const propertyName = getStaticStringValue(node.arguments?.[1]);
|
|
18152
|
+
const propertyName = getStaticStringValue$1(node.arguments?.[1]);
|
|
17363
18153
|
return propertyName === null || propertyName === guardedPropertyName ? mutationHazard : "none";
|
|
17364
18154
|
}
|
|
17365
18155
|
if (!isPropertyCollectionMutation) return "none";
|
|
@@ -17388,7 +18178,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
17388
18178
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
17389
18179
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
17390
18180
|
};
|
|
17391
|
-
const MESSAGE$
|
|
18181
|
+
const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
17392
18182
|
const jsHoistRegexp = defineRule({
|
|
17393
18183
|
id: "js-hoist-regexp",
|
|
17394
18184
|
title: "RegExp built inside a loop",
|
|
@@ -17405,7 +18195,7 @@ const jsHoistRegexp = defineRule({
|
|
|
17405
18195
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
17406
18196
|
context.report({
|
|
17407
18197
|
node,
|
|
17408
|
-
message: MESSAGE$
|
|
18198
|
+
message: MESSAGE$49
|
|
17409
18199
|
});
|
|
17410
18200
|
};
|
|
17411
18201
|
return createLoopAwareVisitors({
|
|
@@ -18333,6 +19123,26 @@ const MEMBERSHIP_COMPARISON_OPERATORS = new Set([
|
|
|
18333
19123
|
]);
|
|
18334
19124
|
const isNegativeOneLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "UnaryExpression") && expression.operator === "-" && isNodeOfType(expression.argument, "Literal") && expression.argument.value === 1;
|
|
18335
19125
|
const isZeroLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "Literal") && expression.value === 0;
|
|
19126
|
+
const isToIntegerOrInfinityZero = (value) => Number.isNaN(value) || Math.trunc(value) === 0;
|
|
19127
|
+
const isZeroFromIndex = (expression) => {
|
|
19128
|
+
if (!expression) return false;
|
|
19129
|
+
const strippedExpression = stripParenExpression(expression);
|
|
19130
|
+
if (isNodeOfType(strippedExpression, "Literal")) {
|
|
19131
|
+
if (typeof strippedExpression.value === "number") return isToIntegerOrInfinityZero(strippedExpression.value);
|
|
19132
|
+
if (typeof strippedExpression.value === "string") return isToIntegerOrInfinityZero(Number(strippedExpression.value));
|
|
19133
|
+
return strippedExpression.value === null || strippedExpression.value === false;
|
|
19134
|
+
}
|
|
19135
|
+
if (isNodeOfType(strippedExpression, "UnaryExpression") && strippedExpression.operator === "void") return true;
|
|
19136
|
+
if (isNodeOfType(strippedExpression, "UnaryExpression") && (strippedExpression.operator === "-" || strippedExpression.operator === "+") && isNodeOfType(strippedExpression.argument, "Literal") && typeof strippedExpression.argument.value === "number") return isToIntegerOrInfinityZero(strippedExpression.operator === "-" ? -strippedExpression.argument.value : strippedExpression.argument.value);
|
|
19137
|
+
if (isNodeOfType(strippedExpression, "Identifier")) return (strippedExpression.name === "undefined" || strippedExpression.name === "NaN") && findVariableInitializer(strippedExpression, strippedExpression.name) === null;
|
|
19138
|
+
return isNodeOfType(strippedExpression, "MemberExpression") && !strippedExpression.computed && isNodeOfType(strippedExpression.object, "Identifier") && strippedExpression.object.name === "Number" && isNodeOfType(strippedExpression.property, "Identifier") && strippedExpression.property.name === "NaN" && findVariableInitializer(strippedExpression.object, strippedExpression.object.name) === null;
|
|
19139
|
+
};
|
|
19140
|
+
const hasSemanticsPreservingIncludesArguments = (node) => {
|
|
19141
|
+
if (node.arguments.length === 1) return !isNodeOfType(node.arguments[0], "SpreadElement");
|
|
19142
|
+
if (node.arguments.length !== 2) return false;
|
|
19143
|
+
if (isNodeOfType(node.arguments[0], "SpreadElement") || isNodeOfType(node.arguments[1], "SpreadElement")) return false;
|
|
19144
|
+
return isZeroFromIndex(node.arguments[1]);
|
|
19145
|
+
};
|
|
18336
19146
|
const PARENT_WRAPPER_TYPES = new Set([
|
|
18337
19147
|
"ParenthesizedExpression",
|
|
18338
19148
|
"ChainExpression",
|
|
@@ -18353,6 +19163,306 @@ const isIndexOfResultUsedAsMembershipTest = (node) => {
|
|
|
18353
19163
|
if (isNegativeOneLiteral(otherOperand)) return true;
|
|
18354
19164
|
return isZeroLiteral(otherOperand) && (parent.operator === ">=" || parent.operator === "<");
|
|
18355
19165
|
};
|
|
19166
|
+
const getTypeAnnotation = (node) => {
|
|
19167
|
+
if (!node || !("typeAnnotation" in node)) return null;
|
|
19168
|
+
const annotation = node.typeAnnotation;
|
|
19169
|
+
if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
|
|
19170
|
+
return annotation.typeAnnotation;
|
|
19171
|
+
};
|
|
19172
|
+
const getDeclaredPropertyType = (members, propertyName) => {
|
|
19173
|
+
for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation(member);
|
|
19174
|
+
return null;
|
|
19175
|
+
};
|
|
19176
|
+
const getArrayElementType = (typeNode) => {
|
|
19177
|
+
if (!typeNode) return null;
|
|
19178
|
+
if (isNodeOfType(typeNode, "TSArrayType")) return typeNode.elementType;
|
|
19179
|
+
if (isNodeOfType(typeNode, "TSTypeReference") && isNodeOfType(typeNode.typeName, "Identifier") && (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray")) return typeNode.typeArguments?.params?.[0] ?? null;
|
|
19180
|
+
if (isNodeOfType(typeNode, "TSUnionType")) {
|
|
19181
|
+
const arrayElementTypes = typeNode.types.map(getArrayElementType).filter(Boolean);
|
|
19182
|
+
return arrayElementTypes.length === 1 ? arrayElementTypes[0] : null;
|
|
19183
|
+
}
|
|
19184
|
+
return null;
|
|
19185
|
+
};
|
|
19186
|
+
const getDestructuredDeclaredType = (identifier) => {
|
|
19187
|
+
const binding = findVariableInitializer(identifier, identifier.name);
|
|
19188
|
+
if (!binding) return null;
|
|
19189
|
+
const property = binding.bindingIdentifier.parent;
|
|
19190
|
+
const objectPattern = property?.parent;
|
|
19191
|
+
if (!isNodeOfType(property, "Property") || !isNodeOfType(property.key, "Identifier") || !isNodeOfType(objectPattern, "ObjectPattern")) return null;
|
|
19192
|
+
const propsType = getTypeAnnotation(objectPattern);
|
|
19193
|
+
if (!propsType) return null;
|
|
19194
|
+
if (isNodeOfType(propsType, "TSTypeLiteral")) return getDeclaredPropertyType(propsType.members ?? [], property.key.name);
|
|
19195
|
+
if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return null;
|
|
19196
|
+
const program = findProgramRoot(identifier);
|
|
19197
|
+
if (!program) return null;
|
|
19198
|
+
for (const statement of program.body) {
|
|
19199
|
+
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
19200
|
+
if (isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name) return getDeclaredPropertyType(declaration.body.body, property.key.name);
|
|
19201
|
+
if (isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name && isNodeOfType(declaration.typeAnnotation, "TSTypeLiteral")) return getDeclaredPropertyType(declaration.typeAnnotation.members ?? [], property.key.name);
|
|
19202
|
+
}
|
|
19203
|
+
return null;
|
|
19204
|
+
};
|
|
19205
|
+
const getIdentifierDeclaredType = (identifier, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
19206
|
+
const binding = findVariableInitializer(identifier, identifier.name);
|
|
19207
|
+
if (!binding || visitedBindingIdentifiers.has(binding.bindingIdentifier)) return null;
|
|
19208
|
+
visitedBindingIdentifiers.add(binding.bindingIdentifier);
|
|
19209
|
+
const directType = getTypeAnnotation(binding.bindingIdentifier);
|
|
19210
|
+
if (directType) return directType;
|
|
19211
|
+
const initializer = binding.initializer;
|
|
19212
|
+
if (isNodeOfType(initializer, "TSAsExpression") || isNodeOfType(initializer, "TSTypeAssertion") || isNodeOfType(initializer, "TSSatisfiesExpression")) return initializer.typeAnnotation;
|
|
19213
|
+
const destructuredType = getDestructuredDeclaredType(identifier);
|
|
19214
|
+
if (destructuredType) return destructuredType;
|
|
19215
|
+
const declarator = binding.bindingIdentifier.parent;
|
|
19216
|
+
const declaration = declarator?.parent;
|
|
19217
|
+
const forOfStatement = declaration?.parent;
|
|
19218
|
+
if (isNodeOfType(declarator, "VariableDeclarator") && isNodeOfType(declaration, "VariableDeclaration") && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isNodeOfType(forOfStatement.right, "Identifier")) return getArrayElementType(getIdentifierDeclaredType(forOfStatement.right, visitedBindingIdentifiers));
|
|
19219
|
+
return null;
|
|
19220
|
+
};
|
|
19221
|
+
const isNativeIterationIndex = (identifier) => {
|
|
19222
|
+
const binding = findVariableInitializer(identifier, identifier.name);
|
|
19223
|
+
if (!binding) return false;
|
|
19224
|
+
const callback = binding.bindingIdentifier.parent;
|
|
19225
|
+
if (!isInlineFunctionExpression(callback)) return false;
|
|
19226
|
+
const callbackCall = callback.parent;
|
|
19227
|
+
if (!isNodeOfType(callbackCall, "CallExpression") || !isIterationCallbackCall(callbackCall) || !isNodeOfType(callbackCall.callee, "MemberExpression") || !isNodeOfType(callbackCall.callee.property, "Identifier")) return false;
|
|
19228
|
+
const indexParameterPosition = callbackCall.callee.property.name === "reduce" || callbackCall.callee.property.name === "reduceRight" ? 2 : 1;
|
|
19229
|
+
return callback.params?.[indexParameterPosition] === binding.bindingIdentifier;
|
|
19230
|
+
};
|
|
19231
|
+
const hasSameIdentifierBinding = (leftIdentifier, rightIdentifier) => {
|
|
19232
|
+
if (leftIdentifier.name !== rightIdentifier.name) return false;
|
|
19233
|
+
const leftBinding = findVariableInitializer(leftIdentifier, leftIdentifier.name);
|
|
19234
|
+
const rightBinding = findVariableInitializer(rightIdentifier, rightIdentifier.name);
|
|
19235
|
+
return Boolean(leftBinding && rightBinding && leftBinding.bindingIdentifier === rightBinding.bindingIdentifier);
|
|
19236
|
+
};
|
|
19237
|
+
const NON_NAN_RELATIONAL_OPERATORS = new Set([
|
|
19238
|
+
"<",
|
|
19239
|
+
"<=",
|
|
19240
|
+
">",
|
|
19241
|
+
">="
|
|
19242
|
+
]);
|
|
19243
|
+
const testProvesIdentifierIsNotNaN = (test, identifier) => {
|
|
19244
|
+
if (!test) return false;
|
|
19245
|
+
const strippedTest = stripParenExpression(test);
|
|
19246
|
+
if (isNodeOfType(strippedTest, "LogicalExpression") && strippedTest.operator === "&&") return testProvesIdentifierIsNotNaN(strippedTest.left, identifier) || testProvesIdentifierIsNotNaN(strippedTest.right, identifier);
|
|
19247
|
+
if (!isNodeOfType(strippedTest, "BinaryExpression") || !NON_NAN_RELATIONAL_OPERATORS.has(strippedTest.operator)) return false;
|
|
19248
|
+
return isNodeOfType(strippedTest.left, "Identifier") && hasSameIdentifierBinding(strippedTest.left, identifier) || isNodeOfType(strippedTest.right, "Identifier") && hasSameIdentifierBinding(strippedTest.right, identifier);
|
|
19249
|
+
};
|
|
19250
|
+
const writeTargetContainsIdentifierBinding = (writeTarget, identifier) => {
|
|
19251
|
+
let containsBinding = false;
|
|
19252
|
+
walkAst(writeTarget, (child) => {
|
|
19253
|
+
if (isNodeOfType(child, "Identifier") && hasSameIdentifierBinding(child, identifier)) {
|
|
19254
|
+
containsBinding = true;
|
|
19255
|
+
return false;
|
|
19256
|
+
}
|
|
19257
|
+
});
|
|
19258
|
+
return containsBinding;
|
|
19259
|
+
};
|
|
19260
|
+
const hasWriteBeforeQuery = (body, identifier) => {
|
|
19261
|
+
const queryStart = getRangeStart(identifier);
|
|
19262
|
+
if (queryStart === null) return true;
|
|
19263
|
+
let hasEarlierWrite = false;
|
|
19264
|
+
walkAst(body, (child) => {
|
|
19265
|
+
if (hasEarlierWrite) return false;
|
|
19266
|
+
const childStart = getRangeStart(child);
|
|
19267
|
+
if (childStart !== null && childStart >= queryStart) return false;
|
|
19268
|
+
if (child !== body && isFunctionLike$2(child)) return false;
|
|
19269
|
+
const writeTarget = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement") ? child.left : null;
|
|
19270
|
+
if (writeTarget && writeTargetContainsIdentifierBinding(writeTarget, identifier)) {
|
|
19271
|
+
hasEarlierWrite = true;
|
|
19272
|
+
return false;
|
|
19273
|
+
}
|
|
19274
|
+
});
|
|
19275
|
+
return hasEarlierWrite;
|
|
19276
|
+
};
|
|
19277
|
+
const isProtectedByRelationalLoopGuard = (identifier) => {
|
|
19278
|
+
let descendant = identifier;
|
|
19279
|
+
let ancestor = identifier.parent;
|
|
19280
|
+
while (ancestor) {
|
|
19281
|
+
if (isFunctionLike$2(ancestor)) return false;
|
|
19282
|
+
if ((isNodeOfType(ancestor, "ForStatement") || isNodeOfType(ancestor, "WhileStatement")) && ancestor.body === descendant && testProvesIdentifierIsNotNaN(ancestor.test, identifier)) return !hasWriteBeforeQuery(ancestor.body, identifier);
|
|
19283
|
+
descendant = ancestor;
|
|
19284
|
+
ancestor = ancestor.parent;
|
|
19285
|
+
}
|
|
19286
|
+
return false;
|
|
19287
|
+
};
|
|
19288
|
+
const isKnownSafeIndexOfQuery = (query) => {
|
|
19289
|
+
if (!query) return false;
|
|
19290
|
+
const strippedQuery = stripParenExpression(query);
|
|
19291
|
+
if (isNodeOfType(strippedQuery, "Literal")) return typeof strippedQuery.value !== "number" || Number.isFinite(strippedQuery.value);
|
|
19292
|
+
if (!isNodeOfType(strippedQuery, "Identifier")) return false;
|
|
19293
|
+
if (isNativeIterationIndex(strippedQuery)) return true;
|
|
19294
|
+
return isProtectedByRelationalLoopGuard(strippedQuery);
|
|
19295
|
+
};
|
|
19296
|
+
const findSameFileTypeAlias = (reference, typeName) => {
|
|
19297
|
+
const program = findProgramRoot(reference);
|
|
19298
|
+
if (!program) return null;
|
|
19299
|
+
for (const statement of program.body) {
|
|
19300
|
+
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
19301
|
+
if (declaration && isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === typeName) return declaration;
|
|
19302
|
+
}
|
|
19303
|
+
return null;
|
|
19304
|
+
};
|
|
19305
|
+
const hasDeclaredMembershipMethod = (members, methodName) => members.some((member) => (isNodeOfType(member, "TSMethodSignature") || isNodeOfType(member, "TSPropertySignature")) && isNodeOfType(member.key, "Identifier") && member.key.name === methodName);
|
|
19306
|
+
const isKnownUserlandMembershipReceiver = (receiver, methodName) => {
|
|
19307
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
19308
|
+
const declaredType = getIdentifierDeclaredType(receiver);
|
|
19309
|
+
if (!declaredType) return false;
|
|
19310
|
+
if (isNodeOfType(declaredType, "TSTypeLiteral")) return hasDeclaredMembershipMethod(declaredType.members ?? [], methodName);
|
|
19311
|
+
if (!isNodeOfType(declaredType, "TSTypeReference") || !isNodeOfType(declaredType.typeName, "Identifier")) return false;
|
|
19312
|
+
const typeAlias = findSameFileTypeAlias(receiver, declaredType.typeName.name);
|
|
19313
|
+
if (typeAlias && isNodeOfType(typeAlias.typeAnnotation, "TSTypeLiteral")) return hasDeclaredMembershipMethod(typeAlias.typeAnnotation.members ?? [], methodName);
|
|
19314
|
+
const program = findProgramRoot(receiver);
|
|
19315
|
+
if (!program) return false;
|
|
19316
|
+
for (const statement of program.body) {
|
|
19317
|
+
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
19318
|
+
if (declaration && isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === declaredType.typeName.name) return hasDeclaredMembershipMethod(declaration.body.body, methodName);
|
|
19319
|
+
}
|
|
19320
|
+
return false;
|
|
19321
|
+
};
|
|
19322
|
+
const findTypeParameter = (reference, typeName) => {
|
|
19323
|
+
let ancestor = reference.parent;
|
|
19324
|
+
while (ancestor) {
|
|
19325
|
+
if (isFunctionLike$2(ancestor) || isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) {
|
|
19326
|
+
const matchingTypeParameter = ancestor.typeParameters?.params?.find((typeParameter) => isNodeOfType(typeParameter, "TSTypeParameter") && isNodeOfType(typeParameter.name, "Identifier") && typeParameter.name.name === typeName);
|
|
19327
|
+
if (matchingTypeParameter && isNodeOfType(matchingTypeParameter, "TSTypeParameter")) return matchingTypeParameter;
|
|
19328
|
+
}
|
|
19329
|
+
ancestor = ancestor.parent;
|
|
19330
|
+
}
|
|
19331
|
+
return null;
|
|
19332
|
+
};
|
|
19333
|
+
const POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES = new Set(["NonNullable", "PropertyKey"]);
|
|
19334
|
+
const buildTypeAliasArguments = (typeAlias, typeReference, inheritedArguments) => {
|
|
19335
|
+
const typeArguments = new Map(inheritedArguments);
|
|
19336
|
+
for (const [index, typeParameter] of (typeAlias.typeParameters?.params ?? []).entries()) {
|
|
19337
|
+
if (!isNodeOfType(typeParameter, "TSTypeParameter")) continue;
|
|
19338
|
+
if (!isNodeOfType(typeParameter.name, "Identifier")) continue;
|
|
19339
|
+
const argument = typeReference.typeArguments?.params?.[index] ?? typeParameter.default;
|
|
19340
|
+
if (argument) typeArguments.set(typeParameter.name.name, argument);
|
|
19341
|
+
}
|
|
19342
|
+
return typeArguments;
|
|
19343
|
+
};
|
|
19344
|
+
const typeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
|
|
19345
|
+
if (!typeNode) return false;
|
|
19346
|
+
if (isNodeOfType(typeNode, "TSNumberKeyword") || isNodeOfType(typeNode, "TSAnyKeyword") || isNodeOfType(typeNode, "TSUnknownKeyword")) return true;
|
|
19347
|
+
if (isNodeOfType(typeNode, "TSStringKeyword") || isNodeOfType(typeNode, "TSBooleanKeyword") || isNodeOfType(typeNode, "TSBigIntKeyword") || isNodeOfType(typeNode, "TSSymbolKeyword") || isNodeOfType(typeNode, "TSNullKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword") || isNodeOfType(typeNode, "TSObjectKeyword") || isNodeOfType(typeNode, "TSLiteralType") || isNodeOfType(typeNode, "TSFunctionType")) return false;
|
|
19348
|
+
if (isNodeOfType(typeNode, "TSTypeLiteral")) return (typeNode.members?.length ?? 0) === 0;
|
|
19349
|
+
if (isNodeOfType(typeNode, "TSTypeOperator") && typeNode.operator === "keyof") return false;
|
|
19350
|
+
if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => typeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
|
|
19351
|
+
if (isNodeOfType(typeNode, "TSOptionalType") || isNodeOfType(typeNode, "TSRestType")) return typeCanHaveSameValueZeroDifference(typeNode.typeAnnotation, reference, activeTypeNodes, typeArguments);
|
|
19352
|
+
if (isNodeOfType(typeNode, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
|
|
19353
|
+
if (!isNodeOfType(typeNode, "TSTypeReference")) return true;
|
|
19354
|
+
if (!isNodeOfType(typeNode.typeName, "Identifier")) return false;
|
|
19355
|
+
const substitutedType = typeArguments.get(typeNode.typeName.name);
|
|
19356
|
+
if (substitutedType) {
|
|
19357
|
+
const remainingArguments = new Map(typeArguments);
|
|
19358
|
+
remainingArguments.delete(typeNode.typeName.name);
|
|
19359
|
+
return typeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
|
|
19360
|
+
}
|
|
19361
|
+
const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
|
|
19362
|
+
if (typeParameter) {
|
|
19363
|
+
if (!typeParameter.constraint || activeTypeNodes.has(typeParameter)) return true;
|
|
19364
|
+
activeTypeNodes.add(typeParameter);
|
|
19365
|
+
const constraintCanDiffer = typeCanHaveSameValueZeroDifference(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
|
|
19366
|
+
activeTypeNodes.delete(typeParameter);
|
|
19367
|
+
return constraintCanDiffer;
|
|
19368
|
+
}
|
|
19369
|
+
const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
|
|
19370
|
+
if (!typeAlias) return POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES.has(typeNode.typeName.name) || /(?:number|numeric)/i.test(typeNode.typeName.name);
|
|
19371
|
+
if (activeTypeNodes.has(typeAlias)) return true;
|
|
19372
|
+
activeTypeNodes.add(typeAlias);
|
|
19373
|
+
const canDiffer = typeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
|
|
19374
|
+
activeTypeNodes.delete(typeAlias);
|
|
19375
|
+
return canDiffer;
|
|
19376
|
+
};
|
|
19377
|
+
const arrayTypeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
|
|
19378
|
+
if (!typeNode) return false;
|
|
19379
|
+
if (isNodeOfType(typeNode, "TSArrayType")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
|
|
19380
|
+
if (isNodeOfType(typeNode, "TSTupleType")) return typeNode.elementTypes.some((elementType) => {
|
|
19381
|
+
if (isNodeOfType(elementType, "TSRestType")) return arrayTypeCanHaveSameValueZeroDifference(elementType.typeAnnotation, reference, activeTypeNodes, typeArguments);
|
|
19382
|
+
if (isNodeOfType(elementType, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(elementType.elementType, reference, activeTypeNodes, typeArguments);
|
|
19383
|
+
return typeCanHaveSameValueZeroDifference(elementType, reference, activeTypeNodes, typeArguments);
|
|
19384
|
+
});
|
|
19385
|
+
if (isNodeOfType(typeNode, "TSTypeOperator")) return arrayTypeCanHaveSameValueZeroDifference(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
|
|
19386
|
+
if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => arrayTypeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
|
|
19387
|
+
if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
|
|
19388
|
+
const substitutedType = typeArguments.get(typeNode.typeName.name);
|
|
19389
|
+
if (substitutedType) {
|
|
19390
|
+
const remainingArguments = new Map(typeArguments);
|
|
19391
|
+
remainingArguments.delete(typeNode.typeName.name);
|
|
19392
|
+
return arrayTypeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
|
|
19393
|
+
}
|
|
19394
|
+
if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return typeCanHaveSameValueZeroDifference(typeNode.typeArguments?.params?.[0] ?? null, reference, activeTypeNodes, typeArguments);
|
|
19395
|
+
const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
|
|
19396
|
+
if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
|
|
19397
|
+
activeTypeNodes.add(typeAlias);
|
|
19398
|
+
const canDiffer = arrayTypeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
|
|
19399
|
+
activeTypeNodes.delete(typeAlias);
|
|
19400
|
+
return canDiffer;
|
|
19401
|
+
};
|
|
19402
|
+
const isKnownArrayType = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
|
|
19403
|
+
if (!typeNode) return false;
|
|
19404
|
+
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return true;
|
|
19405
|
+
if (isNodeOfType(typeNode, "TSTypeOperator")) return isKnownArrayType(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
|
|
19406
|
+
if (isNodeOfType(typeNode, "TSUnionType")) {
|
|
19407
|
+
const nonNullishTypes = typeNode.types.filter((memberType) => !isNodeOfType(memberType, "TSNullKeyword") && !isNodeOfType(memberType, "TSUndefinedKeyword"));
|
|
19408
|
+
return nonNullishTypes.length > 0 && nonNullishTypes.every((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
|
|
19409
|
+
}
|
|
19410
|
+
if (isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
|
|
19411
|
+
if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
|
|
19412
|
+
const substitutedType = typeArguments.get(typeNode.typeName.name);
|
|
19413
|
+
if (substitutedType) {
|
|
19414
|
+
const remainingArguments = new Map(typeArguments);
|
|
19415
|
+
remainingArguments.delete(typeNode.typeName.name);
|
|
19416
|
+
return isKnownArrayType(substitutedType, reference, activeTypeNodes, remainingArguments);
|
|
19417
|
+
}
|
|
19418
|
+
if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return true;
|
|
19419
|
+
const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
|
|
19420
|
+
if (typeParameter?.constraint) {
|
|
19421
|
+
if (activeTypeNodes.has(typeParameter)) return false;
|
|
19422
|
+
activeTypeNodes.add(typeParameter);
|
|
19423
|
+
const isConstraintArray = isKnownArrayType(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
|
|
19424
|
+
activeTypeNodes.delete(typeParameter);
|
|
19425
|
+
return isConstraintArray;
|
|
19426
|
+
}
|
|
19427
|
+
const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
|
|
19428
|
+
if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
|
|
19429
|
+
activeTypeNodes.add(typeAlias);
|
|
19430
|
+
const isArray = isKnownArrayType(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
|
|
19431
|
+
activeTypeNodes.delete(typeAlias);
|
|
19432
|
+
return isArray;
|
|
19433
|
+
};
|
|
19434
|
+
const isKnownNativeArrayReceiver = (receiver) => {
|
|
19435
|
+
if (isNodeOfType(receiver, "ArrayExpression")) return true;
|
|
19436
|
+
if (isNodeOfType(receiver, "Identifier") && isKnownArrayType(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set())) return true;
|
|
19437
|
+
const initializer = getResolvedInitializer(receiver)?.initializer;
|
|
19438
|
+
if (!initializer) return false;
|
|
19439
|
+
const strippedInitializer = stripParenExpression(initializer);
|
|
19440
|
+
if (isNodeOfType(strippedInitializer, "ArrayExpression")) return true;
|
|
19441
|
+
if (isNodeOfType(strippedInitializer, "NewExpression") && isNodeOfType(strippedInitializer.callee, "Identifier") && strippedInitializer.callee.name === "Array" && findVariableInitializer(strippedInitializer.callee, strippedInitializer.callee.name) === null) return true;
|
|
19442
|
+
return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
|
|
19443
|
+
};
|
|
19444
|
+
const isKnownDenseArrayReceiver = (receiver) => {
|
|
19445
|
+
const initializer = isNodeOfType(receiver, "Identifier") ? getResolvedInitializer(receiver)?.initializer : receiver;
|
|
19446
|
+
if (!initializer) return false;
|
|
19447
|
+
const strippedInitializer = stripParenExpression(initializer);
|
|
19448
|
+
if (isNodeOfType(strippedInitializer, "ArrayExpression")) return (strippedInitializer.elements ?? []).every((element) => element !== null && !isNodeOfType(element, "SpreadElement"));
|
|
19449
|
+
return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
|
|
19450
|
+
};
|
|
19451
|
+
const isKnownUnsafeIndexOfQuery = (query, receiver) => {
|
|
19452
|
+
if (!query) return true;
|
|
19453
|
+
const strippedQuery = stripParenExpression(query);
|
|
19454
|
+
if (isNodeOfType(strippedQuery, "Identifier")) {
|
|
19455
|
+
if (strippedQuery.name === "undefined" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return !isKnownDenseArrayReceiver(receiver);
|
|
19456
|
+
if (strippedQuery.name === "NaN" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return true;
|
|
19457
|
+
return typeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(strippedQuery), strippedQuery, /* @__PURE__ */ new Set());
|
|
19458
|
+
}
|
|
19459
|
+
if (isNodeOfType(strippedQuery, "MemberExpression")) return !strippedQuery.computed && isNodeOfType(strippedQuery.object, "Identifier") && strippedQuery.object.name === "Number" && isNodeOfType(strippedQuery.property, "Identifier") && strippedQuery.property.name === "NaN" && findVariableInitializer(strippedQuery.object, strippedQuery.object.name) === null;
|
|
19460
|
+
return true;
|
|
19461
|
+
};
|
|
19462
|
+
const isKnownUnsafeIndexOfReceiver = (receiver) => {
|
|
19463
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
19464
|
+
return arrayTypeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set());
|
|
19465
|
+
};
|
|
18356
19466
|
const ITERATION_CALLBACK_METHOD_NAMES = new Set([
|
|
18357
19467
|
"forEach",
|
|
18358
19468
|
"map",
|
|
@@ -18481,16 +19591,22 @@ const jsSetMapLookups = defineRule({
|
|
|
18481
19591
|
if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
|
|
18482
19592
|
const methodName = node.callee.property.name;
|
|
18483
19593
|
if (methodName !== "includes" && methodName !== "indexOf") return;
|
|
18484
|
-
if (methodName === "
|
|
19594
|
+
if (methodName === "includes" && !hasSemanticsPreservingIncludesArguments(node)) return;
|
|
19595
|
+
if (methodName === "indexOf" && (node.arguments.length !== 1 || isNodeOfType(node.arguments[0], "SpreadElement") || !isIndexOfResultUsedAsMembershipTest(node))) return;
|
|
18485
19596
|
const rawReceiver = node.callee.object;
|
|
18486
19597
|
if (!rawReceiver) return;
|
|
18487
19598
|
const receiver = stripParenExpression(rawReceiver);
|
|
19599
|
+
const isKnownNativeArray = isKnownNativeArrayReceiver(receiver);
|
|
19600
|
+
if (isKnownUserlandMembershipReceiver(receiver, methodName)) return;
|
|
19601
|
+
if (methodName === "includes" && node.arguments.length === 2 && !isKnownNativeArray) return;
|
|
19602
|
+
const query = node.arguments[0];
|
|
19603
|
+
if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
|
|
18488
19604
|
if (isLikelyStringReceiver(receiver)) return;
|
|
18489
19605
|
if (isSmallInlineLiteralArray(receiver)) return;
|
|
18490
19606
|
if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
|
|
18491
19607
|
if (isSmallFixedListMember(receiver)) return;
|
|
18492
|
-
if (isSubstringSearchLiteral(
|
|
18493
|
-
if (isIndexedArrayElementWithStringArgument(receiver,
|
|
19608
|
+
if (isSubstringSearchLiteral(query)) return;
|
|
19609
|
+
if (isIndexedArrayElementWithStringArgument(receiver, query)) return;
|
|
18494
19610
|
const resolvedInitializer = getResolvedInitializer(receiver);
|
|
18495
19611
|
if (resolvedInitializer) {
|
|
18496
19612
|
if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
|
|
@@ -19699,7 +20815,7 @@ const jsxMaxDepth = defineRule({
|
|
|
19699
20815
|
});
|
|
19700
20816
|
//#endregion
|
|
19701
20817
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
19702
|
-
const MESSAGE$
|
|
20818
|
+
const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
19703
20819
|
const LITERAL_TEXT_TAGS = new Set([
|
|
19704
20820
|
"code",
|
|
19705
20821
|
"pre",
|
|
@@ -19769,7 +20885,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
19769
20885
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
19770
20886
|
context.report({
|
|
19771
20887
|
node,
|
|
19772
|
-
message: MESSAGE$
|
|
20888
|
+
message: MESSAGE$48
|
|
19773
20889
|
});
|
|
19774
20890
|
} })
|
|
19775
20891
|
});
|
|
@@ -19800,7 +20916,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
19800
20916
|
};
|
|
19801
20917
|
//#endregion
|
|
19802
20918
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
19803
|
-
const MESSAGE$
|
|
20919
|
+
const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
19804
20920
|
const CONTEXT_MODULES$1 = [
|
|
19805
20921
|
"react",
|
|
19806
20922
|
"use-context-selector",
|
|
@@ -19898,7 +21014,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
19898
21014
|
if (!isConstructedValue(innerExpression)) continue;
|
|
19899
21015
|
context.report({
|
|
19900
21016
|
node: attribute,
|
|
19901
|
-
message: MESSAGE$
|
|
21017
|
+
message: MESSAGE$47
|
|
19902
21018
|
});
|
|
19903
21019
|
}
|
|
19904
21020
|
}
|
|
@@ -20753,7 +21869,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
20753
21869
|
];
|
|
20754
21870
|
//#endregion
|
|
20755
21871
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
20756
|
-
const MESSAGE$
|
|
21872
|
+
const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
20757
21873
|
const isDataArrayPropName = (propName) => {
|
|
20758
21874
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
20759
21875
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -20840,7 +21956,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
20840
21956
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
20841
21957
|
context.report({
|
|
20842
21958
|
node,
|
|
20843
|
-
message: MESSAGE$
|
|
21959
|
+
message: MESSAGE$46
|
|
20844
21960
|
});
|
|
20845
21961
|
}
|
|
20846
21962
|
};
|
|
@@ -21098,7 +22214,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
21098
22214
|
]);
|
|
21099
22215
|
//#endregion
|
|
21100
22216
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
21101
|
-
const MESSAGE$
|
|
22217
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
21102
22218
|
const isAccessorPredicateName = (propName) => {
|
|
21103
22219
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
21104
22220
|
if (propName.length <= prefix.length) continue;
|
|
@@ -21304,7 +22420,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
21304
22420
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
21305
22421
|
context.report({
|
|
21306
22422
|
node,
|
|
21307
|
-
message: MESSAGE$
|
|
22423
|
+
message: MESSAGE$45
|
|
21308
22424
|
});
|
|
21309
22425
|
}
|
|
21310
22426
|
};
|
|
@@ -21524,7 +22640,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
21524
22640
|
];
|
|
21525
22641
|
//#endregion
|
|
21526
22642
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
21527
|
-
const MESSAGE$
|
|
22643
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
21528
22644
|
const isConfigObjectPropName = (propName) => {
|
|
21529
22645
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
21530
22646
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -21613,7 +22729,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
21613
22729
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
21614
22730
|
context.report({
|
|
21615
22731
|
node,
|
|
21616
|
-
message: MESSAGE$
|
|
22732
|
+
message: MESSAGE$44
|
|
21617
22733
|
});
|
|
21618
22734
|
}
|
|
21619
22735
|
};
|
|
@@ -21621,7 +22737,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
21621
22737
|
});
|
|
21622
22738
|
//#endregion
|
|
21623
22739
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
21624
|
-
const MESSAGE$
|
|
22740
|
+
const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
21625
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;
|
|
21626
22742
|
const resolveSettings$29 = (settings) => {
|
|
21627
22743
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -21659,7 +22775,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
21659
22775
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
21660
22776
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
21661
22777
|
node: attribute,
|
|
21662
|
-
message: MESSAGE$
|
|
22778
|
+
message: MESSAGE$43
|
|
21663
22779
|
});
|
|
21664
22780
|
}
|
|
21665
22781
|
} };
|
|
@@ -22279,7 +23395,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
22279
23395
|
});
|
|
22280
23396
|
//#endregion
|
|
22281
23397
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
22282
|
-
const MESSAGE$
|
|
23398
|
+
const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
|
|
22283
23399
|
const resolveSettings$25 = (settings) => {
|
|
22284
23400
|
const reactDoctor = settings?.["react-doctor"];
|
|
22285
23401
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -22322,7 +23438,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
22322
23438
|
}
|
|
22323
23439
|
context.report({
|
|
22324
23440
|
node: attribute,
|
|
22325
|
-
message: MESSAGE$
|
|
23441
|
+
message: MESSAGE$42
|
|
22326
23442
|
});
|
|
22327
23443
|
didReportInFile = true;
|
|
22328
23444
|
return;
|
|
@@ -22598,7 +23714,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
22598
23714
|
});
|
|
22599
23715
|
//#endregion
|
|
22600
23716
|
//#region src/plugin/rules/a11y/lang.ts
|
|
22601
|
-
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`.";
|
|
22602
23718
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
22603
23719
|
"aa",
|
|
22604
23720
|
"ab",
|
|
@@ -23078,7 +24194,7 @@ const lang = defineRule({
|
|
|
23078
24194
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
23079
24195
|
context.report({
|
|
23080
24196
|
node: langAttr,
|
|
23081
|
-
message: MESSAGE$
|
|
24197
|
+
message: MESSAGE$41
|
|
23082
24198
|
});
|
|
23083
24199
|
return;
|
|
23084
24200
|
}
|
|
@@ -23087,7 +24203,7 @@ const lang = defineRule({
|
|
|
23087
24203
|
if (value === null) return;
|
|
23088
24204
|
if (!isValidLangTag(value)) context.report({
|
|
23089
24205
|
node: langAttr,
|
|
23090
|
-
message: MESSAGE$
|
|
24206
|
+
message: MESSAGE$41
|
|
23091
24207
|
});
|
|
23092
24208
|
} })
|
|
23093
24209
|
});
|
|
@@ -23138,7 +24254,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
23138
24254
|
});
|
|
23139
24255
|
//#endregion
|
|
23140
24256
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
23141
|
-
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>`.";
|
|
23142
24258
|
const DEFAULT_AUDIO = ["audio"];
|
|
23143
24259
|
const DEFAULT_VIDEO = ["video"];
|
|
23144
24260
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -23227,7 +24343,7 @@ const mediaHasCaption = defineRule({
|
|
|
23227
24343
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
23228
24344
|
context.report({
|
|
23229
24345
|
node: node.name,
|
|
23230
|
-
message: MESSAGE$
|
|
24346
|
+
message: MESSAGE$40
|
|
23231
24347
|
});
|
|
23232
24348
|
return;
|
|
23233
24349
|
}
|
|
@@ -23246,7 +24362,7 @@ const mediaHasCaption = defineRule({
|
|
|
23246
24362
|
return kindValue.value.toLowerCase() === "captions";
|
|
23247
24363
|
})) context.report({
|
|
23248
24364
|
node: node.name,
|
|
23249
|
-
message: MESSAGE$
|
|
24365
|
+
message: MESSAGE$40
|
|
23250
24366
|
});
|
|
23251
24367
|
} };
|
|
23252
24368
|
}
|
|
@@ -24789,7 +25905,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
24789
25905
|
});
|
|
24790
25906
|
//#endregion
|
|
24791
25907
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
24792
|
-
const MESSAGE$
|
|
25908
|
+
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
24793
25909
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
24794
25910
|
const noAccessKey = defineRule({
|
|
24795
25911
|
id: "no-access-key",
|
|
@@ -24808,7 +25924,7 @@ const noAccessKey = defineRule({
|
|
|
24808
25924
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
24809
25925
|
context.report({
|
|
24810
25926
|
node: accessKey,
|
|
24811
|
-
message: MESSAGE$
|
|
25927
|
+
message: MESSAGE$39
|
|
24812
25928
|
});
|
|
24813
25929
|
return;
|
|
24814
25930
|
}
|
|
@@ -24818,7 +25934,7 @@ const noAccessKey = defineRule({
|
|
|
24818
25934
|
if (isUndefinedIdentifier(expression)) return;
|
|
24819
25935
|
context.report({
|
|
24820
25936
|
node: accessKey,
|
|
24821
|
-
message: MESSAGE$
|
|
25937
|
+
message: MESSAGE$39
|
|
24822
25938
|
});
|
|
24823
25939
|
}
|
|
24824
25940
|
} };
|
|
@@ -25123,8 +26239,11 @@ const unwrapUseCallback = (node) => {
|
|
|
25123
26239
|
const callee = node.callee;
|
|
25124
26240
|
return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
|
|
25125
26241
|
};
|
|
26242
|
+
const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
|
|
25126
26243
|
const resolveToFunction = (ref) => {
|
|
25127
|
-
const
|
|
26244
|
+
const definition = ref.resolved?.defs[0];
|
|
26245
|
+
if (!definition || hasParameterDefinition(ref)) return null;
|
|
26246
|
+
const definitionNode = definition.node;
|
|
25128
26247
|
if (!definitionNode) return null;
|
|
25129
26248
|
if (isFunctionLike$2(definitionNode)) return definitionNode;
|
|
25130
26249
|
if (isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
@@ -25534,7 +26653,7 @@ const isCleanupReturnArgument = (analysis, node) => {
|
|
|
25534
26653
|
if (isNodeOfType(node, "MemberExpression")) return true;
|
|
25535
26654
|
if (isNodeOfType(node, "Identifier")) {
|
|
25536
26655
|
const ref = getRef(analysis, node);
|
|
25537
|
-
if (ref && resolveToFunction(ref)) return true;
|
|
26656
|
+
if (ref && (hasParameterDefinition(ref) || resolveToFunction(ref))) return true;
|
|
25538
26657
|
}
|
|
25539
26658
|
if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
|
|
25540
26659
|
return false;
|
|
@@ -25692,7 +26811,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
25692
26811
|
};
|
|
25693
26812
|
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
25694
26813
|
if (!setterRef.resolved) return false;
|
|
25695
|
-
const componentFunction = findEnclosingFunction(effectNode);
|
|
26814
|
+
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
25696
26815
|
if (!componentFunction) return false;
|
|
25697
26816
|
for (const reference of setterRef.resolved.references) {
|
|
25698
26817
|
if (reference.init) continue;
|
|
@@ -26006,7 +27125,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
26006
27125
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
26007
27126
|
const reference = getRef(analysis, candidate);
|
|
26008
27127
|
if (!reference) return null;
|
|
26009
|
-
if (reference.resolved?.defs.some((definition) => definition.type === "
|
|
27128
|
+
if (reference.resolved?.defs.some((definition) => definition.type === "ImportBinding")) return null;
|
|
26010
27129
|
const resolved = resolveToFunction(reference);
|
|
26011
27130
|
if (resolved) return resolved;
|
|
26012
27131
|
const definitionNode = reference.resolved?.defs[0]?.node;
|
|
@@ -26670,7 +27789,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
26670
27789
|
});
|
|
26671
27790
|
//#endregion
|
|
26672
27791
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
26673
|
-
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.";
|
|
26674
27793
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
26675
27794
|
"button",
|
|
26676
27795
|
"embed",
|
|
@@ -26782,7 +27901,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
26782
27901
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
26783
27902
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
26784
27903
|
node: ariaHidden,
|
|
26785
|
-
message: MESSAGE$
|
|
27904
|
+
message: MESSAGE$38
|
|
26786
27905
|
});
|
|
26787
27906
|
} })
|
|
26788
27907
|
});
|
|
@@ -27704,7 +28823,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
27704
28823
|
};
|
|
27705
28824
|
//#endregion
|
|
27706
28825
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
27707
|
-
const MESSAGE$
|
|
28826
|
+
const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
|
|
27708
28827
|
const SECOND_INDEX_METHODS = new Set([
|
|
27709
28828
|
"every",
|
|
27710
28829
|
"filter",
|
|
@@ -27823,14 +28942,14 @@ const noArrayIndexKey = defineRule({
|
|
|
27823
28942
|
if (propName !== "key") continue;
|
|
27824
28943
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
27825
28944
|
node: property,
|
|
27826
|
-
message: MESSAGE$
|
|
28945
|
+
message: MESSAGE$37
|
|
27827
28946
|
});
|
|
27828
28947
|
}
|
|
27829
28948
|
} })
|
|
27830
28949
|
});
|
|
27831
28950
|
//#endregion
|
|
27832
28951
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
27833
|
-
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.";
|
|
27834
28953
|
const noAsyncEffectCallback = defineRule({
|
|
27835
28954
|
id: "no-async-effect-callback",
|
|
27836
28955
|
title: "Async effect callback",
|
|
@@ -27848,13 +28967,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
27848
28967
|
if (!callback.async) return;
|
|
27849
28968
|
context.report({
|
|
27850
28969
|
node: callback,
|
|
27851
|
-
message: MESSAGE$
|
|
28970
|
+
message: MESSAGE$36
|
|
27852
28971
|
});
|
|
27853
28972
|
} })
|
|
27854
28973
|
});
|
|
27855
28974
|
//#endregion
|
|
27856
28975
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
27857
|
-
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.";
|
|
27858
28977
|
const resolveSettings$21 = (settings) => {
|
|
27859
28978
|
const reactDoctor = settings?.["react-doctor"];
|
|
27860
28979
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -27951,7 +29070,7 @@ const noAutofocus = defineRule({
|
|
|
27951
29070
|
if (isConditionallyRendered(node)) return;
|
|
27952
29071
|
context.report({
|
|
27953
29072
|
node: autoFocusAttribute,
|
|
27954
|
-
message: MESSAGE$
|
|
29073
|
+
message: MESSAGE$35
|
|
27955
29074
|
});
|
|
27956
29075
|
} };
|
|
27957
29076
|
}
|
|
@@ -28465,138 +29584,6 @@ const noBarrelImport = defineRule({
|
|
|
28465
29584
|
}
|
|
28466
29585
|
});
|
|
28467
29586
|
//#endregion
|
|
28468
|
-
//#region src/plugin/utils/has-static-property-write-before.ts
|
|
28469
|
-
const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
28470
|
-
const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
|
|
28471
|
-
"CatchClause",
|
|
28472
|
-
"ConditionalExpression",
|
|
28473
|
-
"DoWhileStatement",
|
|
28474
|
-
"ForInStatement",
|
|
28475
|
-
"ForOfStatement",
|
|
28476
|
-
"ForStatement",
|
|
28477
|
-
"IfStatement",
|
|
28478
|
-
"LogicalExpression",
|
|
28479
|
-
"SwitchCase",
|
|
28480
|
-
"SwitchStatement",
|
|
28481
|
-
"TryStatement",
|
|
28482
|
-
"WhileStatement"
|
|
28483
|
-
]);
|
|
28484
|
-
const getResolvedStaticPropertyName = (memberExpression, scopes) => {
|
|
28485
|
-
if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
|
|
28486
|
-
const directPropertyName = getStaticPropertyName(memberExpression);
|
|
28487
|
-
if (directPropertyName || !memberExpression.computed) return directPropertyName;
|
|
28488
|
-
const property = stripParenExpression(memberExpression.property);
|
|
28489
|
-
if (!isNodeOfType(property, "Identifier")) return null;
|
|
28490
|
-
const propertySymbol = resolveConstIdentifierAlias(property, scopes);
|
|
28491
|
-
const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
|
|
28492
|
-
return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
|
|
28493
|
-
};
|
|
28494
|
-
const collectScopeSymbols = (scope, symbols) => {
|
|
28495
|
-
symbols.push(...scope.symbols);
|
|
28496
|
-
for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
|
|
28497
|
-
};
|
|
28498
|
-
const getEquivalentSymbols = (identifier, scopes) => {
|
|
28499
|
-
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
28500
|
-
if (!rootSymbol) return [];
|
|
28501
|
-
let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
|
|
28502
|
-
if (!symbolsByRootId) {
|
|
28503
|
-
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
28504
|
-
equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
28505
|
-
}
|
|
28506
|
-
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
28507
|
-
if (cachedSymbols) return cachedSymbols;
|
|
28508
|
-
const allSymbols = [];
|
|
28509
|
-
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
28510
|
-
const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
|
|
28511
|
-
symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
|
|
28512
|
-
return equivalentSymbols;
|
|
28513
|
-
};
|
|
28514
|
-
const findExecutionBoundary = (node) => {
|
|
28515
|
-
let current = node;
|
|
28516
|
-
while (current) {
|
|
28517
|
-
if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
|
|
28518
|
-
current = current.parent ?? null;
|
|
28519
|
-
}
|
|
28520
|
-
return null;
|
|
28521
|
-
};
|
|
28522
|
-
const isOnUnconditionalPath = (node, boundary) => {
|
|
28523
|
-
let current = node.parent ?? null;
|
|
28524
|
-
while (current && current !== boundary) {
|
|
28525
|
-
if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
|
|
28526
|
-
current = current.parent ?? null;
|
|
28527
|
-
}
|
|
28528
|
-
return current === boundary;
|
|
28529
|
-
};
|
|
28530
|
-
const findFunctionBindingIdentifier = (functionNode) => {
|
|
28531
|
-
if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
|
|
28532
|
-
const parent = functionNode.parent;
|
|
28533
|
-
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
28534
|
-
return null;
|
|
28535
|
-
};
|
|
28536
|
-
const findDirectCall = (identifier) => {
|
|
28537
|
-
let callee = identifier;
|
|
28538
|
-
let parent = callee.parent;
|
|
28539
|
-
while (parent && stripParenExpression(parent) === identifier) {
|
|
28540
|
-
callee = parent;
|
|
28541
|
-
parent = callee.parent;
|
|
28542
|
-
}
|
|
28543
|
-
return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
28544
|
-
};
|
|
28545
|
-
const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
28546
|
-
if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
|
|
28547
|
-
visitedFunctionNodes.add(functionNode);
|
|
28548
|
-
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
28549
|
-
if (!referenceBoundary) return false;
|
|
28550
|
-
const invocationCalls = [];
|
|
28551
|
-
const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
|
|
28552
|
-
if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
|
|
28553
|
-
const call = findDirectCall(reference.identifier);
|
|
28554
|
-
if (call) invocationCalls.push(call);
|
|
28555
|
-
}
|
|
28556
|
-
else {
|
|
28557
|
-
const call = findDirectCall(functionNode);
|
|
28558
|
-
if (call) invocationCalls.push(call);
|
|
28559
|
-
}
|
|
28560
|
-
return invocationCalls.some((call) => {
|
|
28561
|
-
if (call.range[0] >= referenceNode.range[0]) return false;
|
|
28562
|
-
const callBoundary = findExecutionBoundary(call);
|
|
28563
|
-
if (!callBoundary) return false;
|
|
28564
|
-
if (callBoundary === referenceBoundary) return true;
|
|
28565
|
-
if (!isFunctionLike$2(callBoundary)) return false;
|
|
28566
|
-
return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
|
|
28567
|
-
});
|
|
28568
|
-
};
|
|
28569
|
-
const isMemberWriteTarget = (memberExpression) => {
|
|
28570
|
-
const parent = memberExpression.parent;
|
|
28571
|
-
if (!parent) return false;
|
|
28572
|
-
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
|
|
28573
|
-
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
|
|
28574
|
-
return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
|
|
28575
|
-
};
|
|
28576
|
-
const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
28577
|
-
let parent = reference.identifier.parent;
|
|
28578
|
-
while (parent && stripParenExpression(parent) === reference.identifier) parent = parent.parent;
|
|
28579
|
-
if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== reference.identifier || getResolvedStaticPropertyName(parent, scopes) !== propertyName || !isMemberWriteTarget(parent)) return false;
|
|
28580
|
-
const writeBoundary = findExecutionBoundary(parent);
|
|
28581
|
-
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
28582
|
-
if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(parent, writeBoundary)) return false;
|
|
28583
|
-
if (writeBoundary === referenceBoundary) return parent.range[0] < referenceNode.range[0];
|
|
28584
|
-
return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
|
|
28585
|
-
});
|
|
28586
|
-
const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
28587
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
28588
|
-
return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
|
|
28589
|
-
};
|
|
28590
|
-
//#endregion
|
|
28591
|
-
//#region src/plugin/utils/has-symbol-write-before.ts
|
|
28592
|
-
const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
28593
|
-
if (reference.flag === "read") return false;
|
|
28594
|
-
const writeFunction = findEnclosingFunction(reference.identifier);
|
|
28595
|
-
if (writeFunction === findEnclosingFunction(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
|
|
28596
|
-
if (!writeFunction) return true;
|
|
28597
|
-
return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
|
|
28598
|
-
});
|
|
28599
|
-
//#endregion
|
|
28600
29587
|
//#region src/plugin/utils/has-stable-call-target.ts
|
|
28601
29588
|
const hasStableCallTarget = (callExpression, scopes) => {
|
|
28602
29589
|
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -28924,7 +29911,7 @@ const noChainStateUpdates = defineRule({
|
|
|
28924
29911
|
});
|
|
28925
29912
|
//#endregion
|
|
28926
29913
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
28927
|
-
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.";
|
|
28928
29915
|
const noChildrenProp = defineRule({
|
|
28929
29916
|
id: "no-children-prop",
|
|
28930
29917
|
title: "Children passed as a prop",
|
|
@@ -28936,7 +29923,7 @@ const noChildrenProp = defineRule({
|
|
|
28936
29923
|
if (node.name.name !== "children") return;
|
|
28937
29924
|
context.report({
|
|
28938
29925
|
node: node.name,
|
|
28939
|
-
message: MESSAGE$
|
|
29926
|
+
message: MESSAGE$34
|
|
28940
29927
|
});
|
|
28941
29928
|
},
|
|
28942
29929
|
CallExpression(node) {
|
|
@@ -28949,7 +29936,7 @@ const noChildrenProp = defineRule({
|
|
|
28949
29936
|
const propertyKey = property.key;
|
|
28950
29937
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
28951
29938
|
node: propertyKey,
|
|
28952
|
-
message: MESSAGE$
|
|
29939
|
+
message: MESSAGE$34
|
|
28953
29940
|
});
|
|
28954
29941
|
}
|
|
28955
29942
|
}
|
|
@@ -28957,7 +29944,7 @@ const noChildrenProp = defineRule({
|
|
|
28957
29944
|
});
|
|
28958
29945
|
//#endregion
|
|
28959
29946
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
28960
|
-
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.";
|
|
28961
29948
|
const noCloneElement = defineRule({
|
|
28962
29949
|
id: "no-clone-element",
|
|
28963
29950
|
title: "cloneElement makes child props fragile",
|
|
@@ -28970,7 +29957,7 @@ const noCloneElement = defineRule({
|
|
|
28970
29957
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
28971
29958
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
28972
29959
|
node: callee,
|
|
28973
|
-
message: MESSAGE$
|
|
29960
|
+
message: MESSAGE$33
|
|
28974
29961
|
});
|
|
28975
29962
|
return;
|
|
28976
29963
|
}
|
|
@@ -28983,7 +29970,7 @@ const noCloneElement = defineRule({
|
|
|
28983
29970
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
28984
29971
|
context.report({
|
|
28985
29972
|
node: callee,
|
|
28986
|
-
message: MESSAGE$
|
|
29973
|
+
message: MESSAGE$33
|
|
28987
29974
|
});
|
|
28988
29975
|
}
|
|
28989
29976
|
} })
|
|
@@ -29003,7 +29990,7 @@ const getCallMethodName = (callee) => {
|
|
|
29003
29990
|
};
|
|
29004
29991
|
//#endregion
|
|
29005
29992
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
29006
|
-
const MESSAGE$
|
|
29993
|
+
const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
29007
29994
|
const CONTEXT_MODULES = [
|
|
29008
29995
|
"react",
|
|
29009
29996
|
"use-context-selector",
|
|
@@ -29051,13 +30038,13 @@ const noCreateContextInRender = defineRule({
|
|
|
29051
30038
|
if (!componentOrHookName) return;
|
|
29052
30039
|
context.report({
|
|
29053
30040
|
node,
|
|
29054
|
-
message: `${MESSAGE$
|
|
30041
|
+
message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
|
|
29055
30042
|
});
|
|
29056
30043
|
} })
|
|
29057
30044
|
});
|
|
29058
30045
|
//#endregion
|
|
29059
30046
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
29060
|
-
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.";
|
|
29061
30048
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
29062
30049
|
const parent = functionNode.parent;
|
|
29063
30050
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -29065,8 +30052,8 @@ const isUseMemoCallbackArgument = (functionNode) => {
|
|
|
29065
30052
|
return isReactFunctionCall(parent, "useMemo");
|
|
29066
30053
|
};
|
|
29067
30054
|
const findEnclosingRenderFunction = (node) => {
|
|
29068
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
29069
|
-
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
30055
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
30056
|
+
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
29070
30057
|
return enclosingFunction;
|
|
29071
30058
|
};
|
|
29072
30059
|
const noCreateRefInFunctionComponent = defineRule({
|
|
@@ -29087,7 +30074,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
29087
30074
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
|
|
29088
30075
|
context.report({
|
|
29089
30076
|
node,
|
|
29090
|
-
message: MESSAGE$
|
|
30077
|
+
message: MESSAGE$31
|
|
29091
30078
|
});
|
|
29092
30079
|
} })
|
|
29093
30080
|
});
|
|
@@ -29227,7 +30214,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
29227
30214
|
});
|
|
29228
30215
|
//#endregion
|
|
29229
30216
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
29230
|
-
const MESSAGE$
|
|
30217
|
+
const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
29231
30218
|
const noDanger = defineRule({
|
|
29232
30219
|
id: "no-danger",
|
|
29233
30220
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -29241,7 +30228,7 @@ const noDanger = defineRule({
|
|
|
29241
30228
|
if (!propAttribute) return;
|
|
29242
30229
|
context.report({
|
|
29243
30230
|
node: propAttribute.name,
|
|
29244
|
-
message: MESSAGE$
|
|
30231
|
+
message: MESSAGE$30
|
|
29245
30232
|
});
|
|
29246
30233
|
},
|
|
29247
30234
|
CallExpression(node) {
|
|
@@ -29253,7 +30240,7 @@ const noDanger = defineRule({
|
|
|
29253
30240
|
const propertyKey = property.key;
|
|
29254
30241
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
29255
30242
|
node: propertyKey,
|
|
29256
|
-
message: MESSAGE$
|
|
30243
|
+
message: MESSAGE$30
|
|
29257
30244
|
});
|
|
29258
30245
|
}
|
|
29259
30246
|
}
|
|
@@ -29276,7 +30263,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
29276
30263
|
};
|
|
29277
30264
|
//#endregion
|
|
29278
30265
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
29279
|
-
const MESSAGE$
|
|
30266
|
+
const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
29280
30267
|
const mergePropsShape = (target, source) => {
|
|
29281
30268
|
target.hasDangerously ||= source.hasDangerously;
|
|
29282
30269
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -29351,7 +30338,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29351
30338
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
29352
30339
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
29353
30340
|
node: opening,
|
|
29354
|
-
message: MESSAGE$
|
|
30341
|
+
message: MESSAGE$29
|
|
29355
30342
|
});
|
|
29356
30343
|
},
|
|
29357
30344
|
CallExpression(node) {
|
|
@@ -29364,7 +30351,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29364
30351
|
const positionalChildren = node.arguments.slice(2);
|
|
29365
30352
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
29366
30353
|
node,
|
|
29367
|
-
message: MESSAGE$
|
|
30354
|
+
message: MESSAGE$29
|
|
29368
30355
|
});
|
|
29369
30356
|
}
|
|
29370
30357
|
})
|
|
@@ -30117,7 +31104,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
30117
31104
|
const isEffectDrivenResync = (useStateCall) => {
|
|
30118
31105
|
const setterName = getStateSetterName(useStateCall);
|
|
30119
31106
|
if (!setterName) return false;
|
|
30120
|
-
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31107
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
30121
31108
|
if (!componentFunction) return false;
|
|
30122
31109
|
let isExempt = false;
|
|
30123
31110
|
walkAst(componentFunction, (child) => {
|
|
@@ -30221,7 +31208,7 @@ const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
|
|
|
30221
31208
|
const setterName = getStateSetterName(useStateCall);
|
|
30222
31209
|
if (!setterName) return false;
|
|
30223
31210
|
const stateValueName = getStateValueName(useStateCall);
|
|
30224
|
-
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31211
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
30225
31212
|
if (!componentFunction) return false;
|
|
30226
31213
|
let isExempt = false;
|
|
30227
31214
|
walkAst(componentFunction, (child) => {
|
|
@@ -30267,7 +31254,7 @@ const noDerivedUseState = defineRule({
|
|
|
30267
31254
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
30268
31255
|
if (isEffectDrivenResync(node)) return;
|
|
30269
31256
|
if (isNextjsDataFetchingPage(node)) return;
|
|
30270
|
-
const componentFunction = findEnclosingFunction(node);
|
|
31257
|
+
const componentFunction = findEnclosingFunction$1(node);
|
|
30271
31258
|
if (componentFunction) {
|
|
30272
31259
|
if (isDefaultedDestructuredProp(componentFunction, propName)) return;
|
|
30273
31260
|
if (isDraftCommittedToParent(componentFunction, getStateValueName(node), propStackTracker.isPropName)) return;
|
|
@@ -30329,7 +31316,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
30329
31316
|
//#endregion
|
|
30330
31317
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
30331
31318
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
30332
|
-
const MESSAGE$
|
|
31319
|
+
const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
30333
31320
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
30334
31321
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
30335
31322
|
let ancestor = setStateCall.parent;
|
|
@@ -30457,7 +31444,7 @@ const noDidMountSetState = defineRule({
|
|
|
30457
31444
|
}
|
|
30458
31445
|
context.report({
|
|
30459
31446
|
node: node.callee,
|
|
30460
|
-
message: MESSAGE$
|
|
31447
|
+
message: MESSAGE$28
|
|
30461
31448
|
});
|
|
30462
31449
|
} };
|
|
30463
31450
|
}
|
|
@@ -30465,7 +31452,7 @@ const noDidMountSetState = defineRule({
|
|
|
30465
31452
|
//#endregion
|
|
30466
31453
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
30467
31454
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
30468
|
-
const MESSAGE$
|
|
31455
|
+
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
30469
31456
|
const EQUALITY_OPERATORS = new Set([
|
|
30470
31457
|
"==",
|
|
30471
31458
|
"===",
|
|
@@ -30639,7 +31626,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
30639
31626
|
if (isInsideDiffGuard(node)) return;
|
|
30640
31627
|
context.report({
|
|
30641
31628
|
node: node.callee,
|
|
30642
|
-
message: MESSAGE$
|
|
31629
|
+
message: MESSAGE$27
|
|
30643
31630
|
});
|
|
30644
31631
|
} };
|
|
30645
31632
|
}
|
|
@@ -30662,7 +31649,7 @@ const isStateMemberExpression = (node) => {
|
|
|
30662
31649
|
};
|
|
30663
31650
|
//#endregion
|
|
30664
31651
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
30665
|
-
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.";
|
|
30666
31653
|
const shouldIgnoreMutation = (node) => {
|
|
30667
31654
|
let isConstructor = false;
|
|
30668
31655
|
let isInsideCallExpression = false;
|
|
@@ -30684,7 +31671,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
30684
31671
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
30685
31672
|
context.report({
|
|
30686
31673
|
node: reportNode,
|
|
30687
|
-
message: MESSAGE$
|
|
31674
|
+
message: MESSAGE$26
|
|
30688
31675
|
});
|
|
30689
31676
|
};
|
|
30690
31677
|
const noDirectMutationState = defineRule({
|
|
@@ -31035,7 +32022,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
31035
32022
|
});
|
|
31036
32023
|
//#endregion
|
|
31037
32024
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
31038
|
-
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.";
|
|
31039
32026
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
31040
32027
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
31041
32028
|
const noDocumentWrite = defineRule({
|
|
@@ -31052,7 +32039,7 @@ const noDocumentWrite = defineRule({
|
|
|
31052
32039
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
31053
32040
|
context.report({
|
|
31054
32041
|
node,
|
|
31055
|
-
message: MESSAGE$
|
|
32042
|
+
message: MESSAGE$25
|
|
31056
32043
|
});
|
|
31057
32044
|
} })
|
|
31058
32045
|
});
|
|
@@ -32105,7 +33092,7 @@ const isFunctionUsedOutsideHandlers = (analysis, functionNode, componentFunction
|
|
|
32105
33092
|
if (isInsideInlineEventHandler(identifier, componentFunction)) return false;
|
|
32106
33093
|
const parent = identifier.parent;
|
|
32107
33094
|
if (parent && isNodeOfType(parent, "CallExpression") && parent.callee === identifier) {
|
|
32108
|
-
if (findEnclosingFunction(parent) === functionNode) return false;
|
|
33095
|
+
if (findEnclosingFunction$1(parent) === functionNode) return false;
|
|
32109
33096
|
if (isInsideProvenEventHandler(analysis, parent, componentFunction, false)) return false;
|
|
32110
33097
|
}
|
|
32111
33098
|
return true;
|
|
@@ -32151,7 +33138,7 @@ const isStateWrittenOnlyFromEventHandlers = (analysis, stateReference) => {
|
|
|
32151
33138
|
if (!setterVariable) return false;
|
|
32152
33139
|
const stateDeclarator = getUseStateDecl(analysis, stateReference);
|
|
32153
33140
|
if (!stateDeclarator) return false;
|
|
32154
|
-
const componentFunction = findEnclosingFunction(stateDeclarator);
|
|
33141
|
+
const componentFunction = findEnclosingFunction$1(stateDeclarator);
|
|
32155
33142
|
if (!componentFunction) return false;
|
|
32156
33143
|
let hasWriter = false;
|
|
32157
33144
|
for (const reference of setterVariable.references) {
|
|
@@ -33213,8 +34200,8 @@ const isAwaitedInFunction = (request, functionNode) => {
|
|
|
33213
34200
|
return false;
|
|
33214
34201
|
};
|
|
33215
34202
|
const isCompletionSinkForRequest = (completionSink, request) => {
|
|
33216
|
-
const requestFunction = findEnclosingFunction(request);
|
|
33217
|
-
const completionSinkFunction = findEnclosingFunction(completionSink);
|
|
34203
|
+
const requestFunction = findEnclosingFunction$1(request);
|
|
34204
|
+
const completionSinkFunction = findEnclosingFunction$1(completionSink);
|
|
33218
34205
|
if (!requestFunction || !completionSinkFunction) return false;
|
|
33219
34206
|
if (requestFunction === completionSinkFunction) {
|
|
33220
34207
|
if (!isAwaitedInFunction(request, requestFunction)) return false;
|
|
@@ -33270,7 +34257,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
33270
34257
|
"ReactDOM",
|
|
33271
34258
|
"ReactDom"
|
|
33272
34259
|
]);
|
|
33273
|
-
const MESSAGE$
|
|
34260
|
+
const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
33274
34261
|
const noFindDomNode = defineRule({
|
|
33275
34262
|
id: "no-find-dom-node",
|
|
33276
34263
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -33281,7 +34268,7 @@ const noFindDomNode = defineRule({
|
|
|
33281
34268
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
33282
34269
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
33283
34270
|
node: callee,
|
|
33284
|
-
message: MESSAGE$
|
|
34271
|
+
message: MESSAGE$24
|
|
33285
34272
|
});
|
|
33286
34273
|
return;
|
|
33287
34274
|
}
|
|
@@ -33292,7 +34279,7 @@ const noFindDomNode = defineRule({
|
|
|
33292
34279
|
if (callee.property.name !== "findDOMNode") return;
|
|
33293
34280
|
context.report({
|
|
33294
34281
|
node: callee.property,
|
|
33295
|
-
message: MESSAGE$
|
|
34282
|
+
message: MESSAGE$24
|
|
33296
34283
|
});
|
|
33297
34284
|
}
|
|
33298
34285
|
} })
|
|
@@ -33501,13 +34488,6 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
33501
34488
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
33502
34489
|
};
|
|
33503
34490
|
//#endregion
|
|
33504
|
-
//#region src/plugin/utils/is-type-only-import.ts
|
|
33505
|
-
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
33506
|
-
if (!specifiers || specifiers.length === 0) return false;
|
|
33507
|
-
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
33508
|
-
};
|
|
33509
|
-
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
33510
|
-
//#endregion
|
|
33511
34491
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
33512
34492
|
const isLibraryDevPage = (filename) => {
|
|
33513
34493
|
if (!filename) return false;
|
|
@@ -34071,7 +35051,7 @@ const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
|
|
|
34071
35051
|
return attribute ? !isEventHandlerAttribute(attribute) : true;
|
|
34072
35052
|
}
|
|
34073
35053
|
if (isNodeOfType(parentNode, "ReturnStatement")) {
|
|
34074
|
-
if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
|
|
35054
|
+
if (findEnclosingFunction$1(parentNode) === componentOrHookNode) return true;
|
|
34075
35055
|
}
|
|
34076
35056
|
if (parentNode === componentOrHookNode) return isFunctionLike$2(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
|
|
34077
35057
|
if (isFunctionLike$2(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
|
|
@@ -34194,7 +35174,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34194
35174
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
34195
35175
|
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
34196
35176
|
if (!componentOrHookNode) return;
|
|
34197
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
35177
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
34198
35178
|
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
|
|
34199
35179
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
34200
35180
|
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
@@ -34206,7 +35186,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34206
35186
|
});
|
|
34207
35187
|
//#endregion
|
|
34208
35188
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
34209
|
-
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.";
|
|
34210
35190
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
34211
35191
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
34212
35192
|
title: "Lazy image with high fetchPriority",
|
|
@@ -34220,7 +35200,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
34220
35200
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
34221
35201
|
context.report({
|
|
34222
35202
|
node: node.name,
|
|
34223
|
-
message: MESSAGE$
|
|
35203
|
+
message: MESSAGE$23
|
|
34224
35204
|
});
|
|
34225
35205
|
} })
|
|
34226
35206
|
});
|
|
@@ -34409,7 +35389,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
34409
35389
|
});
|
|
34410
35390
|
//#endregion
|
|
34411
35391
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
34412
|
-
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.";
|
|
34413
35393
|
const REACT_USE_REF_OPTIONS = {
|
|
34414
35394
|
allowGlobalReactNamespace: false,
|
|
34415
35395
|
allowUnboundBareCalls: false
|
|
@@ -34510,7 +35490,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34510
35490
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
34511
35491
|
context.report({
|
|
34512
35492
|
node: indeterminateAttribute,
|
|
34513
|
-
message: MESSAGE$
|
|
35493
|
+
message: MESSAGE$22
|
|
34514
35494
|
});
|
|
34515
35495
|
},
|
|
34516
35496
|
CallExpression(node) {
|
|
@@ -34519,7 +35499,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34519
35499
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
34520
35500
|
context.report({
|
|
34521
35501
|
node,
|
|
34522
|
-
message: MESSAGE$
|
|
35502
|
+
message: MESSAGE$22
|
|
34523
35503
|
});
|
|
34524
35504
|
}
|
|
34525
35505
|
};
|
|
@@ -34642,10 +35622,10 @@ const isInsideInstanceField = (node) => {
|
|
|
34642
35622
|
return false;
|
|
34643
35623
|
};
|
|
34644
35624
|
const isOneShotModuleInitialization = (node, context) => {
|
|
34645
|
-
let functionNode = findEnclosingFunction(node);
|
|
35625
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
34646
35626
|
while (functionNode) {
|
|
34647
35627
|
if (!executesDuringRender(functionNode, context.scopes)) return false;
|
|
34648
|
-
functionNode = findEnclosingFunction(functionNode);
|
|
35628
|
+
functionNode = findEnclosingFunction$1(functionNode);
|
|
34649
35629
|
}
|
|
34650
35630
|
return !isInsideInstanceField(node);
|
|
34651
35631
|
};
|
|
@@ -34807,7 +35787,7 @@ const noIsMounted = defineRule({
|
|
|
34807
35787
|
});
|
|
34808
35788
|
//#endregion
|
|
34809
35789
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
34810
|
-
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)`.";
|
|
34811
35791
|
const isJsonMethodCall = (node, method) => {
|
|
34812
35792
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
34813
35793
|
const callee = node.callee;
|
|
@@ -34871,13 +35851,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
34871
35851
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
34872
35852
|
context.report({
|
|
34873
35853
|
node,
|
|
34874
|
-
message: MESSAGE$
|
|
35854
|
+
message: MESSAGE$21
|
|
34875
35855
|
});
|
|
34876
35856
|
} })
|
|
34877
35857
|
});
|
|
34878
35858
|
//#endregion
|
|
34879
35859
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
34880
|
-
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.";
|
|
34881
35861
|
const isJsxElementTypeReference = (node) => {
|
|
34882
35862
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
34883
35863
|
const typeName = node.typeName;
|
|
@@ -34931,7 +35911,7 @@ const noJsxElementType = defineRule({
|
|
|
34931
35911
|
if (isJsxImported) return;
|
|
34932
35912
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
34933
35913
|
node: typeAnnotation,
|
|
34934
|
-
message: MESSAGE$
|
|
35914
|
+
message: MESSAGE$20
|
|
34935
35915
|
});
|
|
34936
35916
|
}
|
|
34937
35917
|
};
|
|
@@ -35163,6 +36143,221 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
35163
36143
|
}
|
|
35164
36144
|
});
|
|
35165
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
|
|
35166
36361
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
35167
36362
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
35168
36363
|
"childContextTypes",
|
|
@@ -35173,15 +36368,6 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
35173
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.`;
|
|
35174
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.";
|
|
35175
36370
|
};
|
|
35176
|
-
const isInsideClassBody = (node) => {
|
|
35177
|
-
let current = node.parent;
|
|
35178
|
-
while (current) {
|
|
35179
|
-
if (isNodeOfType(current, "ClassBody")) return true;
|
|
35180
|
-
if (isFunctionLike$2(current)) return false;
|
|
35181
|
-
current = current.parent;
|
|
35182
|
-
}
|
|
35183
|
-
return false;
|
|
35184
|
-
};
|
|
35185
36371
|
const noLegacyContextApi = defineRule({
|
|
35186
36372
|
id: "no-legacy-context-api",
|
|
35187
36373
|
title: "Legacy context API",
|
|
@@ -35196,6 +36382,7 @@ const noLegacyContextApi = defineRule({
|
|
|
35196
36382
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
35197
36383
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
35198
36384
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
36385
|
+
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
35199
36386
|
context.report({
|
|
35200
36387
|
node: memberNode.key,
|
|
35201
36388
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -35203,6 +36390,8 @@ const noLegacyContextApi = defineRule({
|
|
|
35203
36390
|
};
|
|
35204
36391
|
return {
|
|
35205
36392
|
ClassBody(node) {
|
|
36393
|
+
const classNode = node.parent;
|
|
36394
|
+
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
35206
36395
|
for (const member of node.body ?? []) checkMember(member);
|
|
35207
36396
|
},
|
|
35208
36397
|
AssignmentExpression(node) {
|
|
@@ -35212,9 +36401,11 @@ const noLegacyContextApi = defineRule({
|
|
|
35212
36401
|
if (left.computed) return;
|
|
35213
36402
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
35214
36403
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
35215
|
-
if (
|
|
35216
|
-
|
|
35217
|
-
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;
|
|
35218
36409
|
context.report({
|
|
35219
36410
|
node: left,
|
|
35220
36411
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -35418,10 +36609,10 @@ const getMethodCalls = (functionNode, scopes) => {
|
|
|
35418
36609
|
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
35419
36610
|
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
35420
36611
|
visitedFunctionNodes.add(functionNode);
|
|
35421
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36612
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
35422
36613
|
const immediateCall = getDirectCallForExpression(functionNode);
|
|
35423
36614
|
if (immediateCall) {
|
|
35424
|
-
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
36615
|
+
const immediateCallFunction = findEnclosingFunction$1(immediateCall);
|
|
35425
36616
|
if (immediateCallFunction === usageFunction) {
|
|
35426
36617
|
const immediateCallStart = getRangeStart(immediateCall);
|
|
35427
36618
|
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
@@ -35430,7 +36621,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
35430
36621
|
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
35431
36622
|
}
|
|
35432
36623
|
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
35433
|
-
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
36624
|
+
const methodCallFunction = findEnclosingFunction$1(methodCall);
|
|
35434
36625
|
if (methodCallFunction === usageFunction) {
|
|
35435
36626
|
const methodCallStart = getRangeStart(methodCall);
|
|
35436
36627
|
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
@@ -35453,7 +36644,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
35453
36644
|
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
35454
36645
|
const call = getDirectCallForExpression(child);
|
|
35455
36646
|
if (!call) return false;
|
|
35456
|
-
const callFunction = findEnclosingFunction(call);
|
|
36647
|
+
const callFunction = findEnclosingFunction$1(call);
|
|
35457
36648
|
if (callFunction === usageFunction) {
|
|
35458
36649
|
const callStart = getRangeStart(call);
|
|
35459
36650
|
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
@@ -35484,14 +36675,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
|
|
|
35484
36675
|
visitedMutationSymbolIds.add(symbol.id);
|
|
35485
36676
|
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
35486
36677
|
if (typeof usageBoundary !== "number") return true;
|
|
35487
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36678
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
35488
36679
|
return symbol.references.some((reference) => {
|
|
35489
36680
|
const referenceStart = getRangeStart(reference.identifier);
|
|
35490
36681
|
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
35491
36682
|
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
35492
36683
|
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
35493
36684
|
if (referenceStart === null) return true;
|
|
35494
|
-
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
36685
|
+
const mutationFunction = findEnclosingFunction$1(reference.identifier);
|
|
35495
36686
|
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
35496
36687
|
if (!mutationFunction) return usageFunction !== null;
|
|
35497
36688
|
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
@@ -36036,7 +37227,7 @@ const noMoment = defineRule({
|
|
|
36036
37227
|
});
|
|
36037
37228
|
//#endregion
|
|
36038
37229
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
36039
|
-
const MESSAGE$
|
|
37230
|
+
const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
36040
37231
|
const resolveSettings$16 = (settings) => {
|
|
36041
37232
|
const reactDoctor = settings?.["react-doctor"];
|
|
36042
37233
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -36073,13 +37264,6 @@ const isReactNamespaceExpression = (node, scopes) => {
|
|
|
36073
37264
|
return isReactNamespaceImport(node, scopes);
|
|
36074
37265
|
};
|
|
36075
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));
|
|
36076
|
-
const getDestructuredPropertyName$1 = (symbol) => {
|
|
36077
|
-
const property = symbol.bindingIdentifier.parent;
|
|
36078
|
-
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
36079
|
-
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
36080
|
-
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
36081
|
-
return null;
|
|
36082
|
-
};
|
|
36083
37267
|
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
36084
37268
|
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
36085
37269
|
visitedSymbolIds.add(symbol.id);
|
|
@@ -36089,7 +37273,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
|
36089
37273
|
}
|
|
36090
37274
|
const init = symbol.initializer;
|
|
36091
37275
|
if (!init) return false;
|
|
36092
|
-
const destructuredPropertyName =
|
|
37276
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
36093
37277
|
if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
|
|
36094
37278
|
if (isReactHocMemberReference(init, scopes)) return true;
|
|
36095
37279
|
if (isNodeOfType(init, "Identifier")) {
|
|
@@ -36395,7 +37579,7 @@ const noMultiComp = defineRule({
|
|
|
36395
37579
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
36396
37580
|
for (const component of flagged.slice(1)) context.report({
|
|
36397
37581
|
node: component.reportNode,
|
|
36398
|
-
message: MESSAGE$
|
|
37582
|
+
message: MESSAGE$19
|
|
36399
37583
|
});
|
|
36400
37584
|
} };
|
|
36401
37585
|
}
|
|
@@ -36608,7 +37792,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
36608
37792
|
};
|
|
36609
37793
|
//#endregion
|
|
36610
37794
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
36611
|
-
const MESSAGE$
|
|
37795
|
+
const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
|
|
36612
37796
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
36613
37797
|
"copyWithin",
|
|
36614
37798
|
"fill",
|
|
@@ -36817,7 +38001,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
36817
38001
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
36818
38002
|
context.report({
|
|
36819
38003
|
node: options.crossFileConsumerCallSite,
|
|
36820
|
-
message: `${MESSAGE$
|
|
38004
|
+
message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
36821
38005
|
});
|
|
36822
38006
|
return;
|
|
36823
38007
|
}
|
|
@@ -36826,7 +38010,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
36826
38010
|
reportedNodes.add(mutation.node);
|
|
36827
38011
|
context.report({
|
|
36828
38012
|
node: mutation.node,
|
|
36829
|
-
message: MESSAGE$
|
|
38013
|
+
message: MESSAGE$18
|
|
36830
38014
|
});
|
|
36831
38015
|
}
|
|
36832
38016
|
};
|
|
@@ -37233,7 +38417,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
37233
38417
|
});
|
|
37234
38418
|
//#endregion
|
|
37235
38419
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
37236
|
-
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.";
|
|
37237
38421
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
37238
38422
|
"onKeyDown",
|
|
37239
38423
|
"onKeyUp",
|
|
@@ -37352,7 +38536,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37352
38536
|
if (numeric === null) {
|
|
37353
38537
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
37354
38538
|
node: tabIndex,
|
|
37355
|
-
message: MESSAGE$
|
|
38539
|
+
message: MESSAGE$17
|
|
37356
38540
|
});
|
|
37357
38541
|
return;
|
|
37358
38542
|
}
|
|
@@ -37374,7 +38558,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37374
38558
|
if (!roleAttribute) {
|
|
37375
38559
|
context.report({
|
|
37376
38560
|
node: tabIndex,
|
|
37377
|
-
message: MESSAGE$
|
|
38561
|
+
message: MESSAGE$17
|
|
37378
38562
|
});
|
|
37379
38563
|
return;
|
|
37380
38564
|
}
|
|
@@ -37388,7 +38572,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37388
38572
|
}
|
|
37389
38573
|
context.report({
|
|
37390
38574
|
node: tabIndex,
|
|
37391
|
-
message: MESSAGE$
|
|
38575
|
+
message: MESSAGE$17
|
|
37392
38576
|
});
|
|
37393
38577
|
} };
|
|
37394
38578
|
}
|
|
@@ -37815,8 +38999,6 @@ const getWrapperHookWrappedFunction = (initializer) => {
|
|
|
37815
38999
|
if (!wrapped || !isFunctionLike$2(wrapped)) return null;
|
|
37816
39000
|
return wrapped;
|
|
37817
39001
|
};
|
|
37818
|
-
const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
|
|
37819
|
-
const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
|
|
37820
39002
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
37821
39003
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
37822
39004
|
if (!isProp(analysis, innerRef)) return false;
|
|
@@ -37845,12 +39027,6 @@ const getDeclarationKind = (declarator) => {
|
|
|
37845
39027
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
37846
39028
|
};
|
|
37847
39029
|
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
37848
|
-
const getDestructuredPropertyName = (bindingIdentifier) => {
|
|
37849
|
-
const property = bindingIdentifier.parent;
|
|
37850
|
-
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
37851
|
-
if (property.computed) return isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? String(property.key.value) : null;
|
|
37852
|
-
return isNodeOfType(property.key, "Identifier") ? property.key.name : null;
|
|
37853
|
-
};
|
|
37854
39030
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
37855
39031
|
const unwrappedExpression = stripParenExpression(expression);
|
|
37856
39032
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -37860,7 +39036,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
37860
39036
|
if (isProp(analysis, callbackReference)) {
|
|
37861
39037
|
if (isWholePropsObjectReference(analysis, callbackReference)) return null;
|
|
37862
39038
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
37863
|
-
return (bindingIdentifier &&
|
|
39039
|
+
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
37864
39040
|
}
|
|
37865
39041
|
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
37866
39042
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -37870,7 +39046,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
37870
39046
|
visitedVariables.add(callbackVariable);
|
|
37871
39047
|
if (isNodeOfType(declarator.id, "ObjectPattern")) {
|
|
37872
39048
|
const bindingIdentifier = callbackVariable.defs[0]?.name;
|
|
37873
|
-
const propertyName = bindingIdentifier ?
|
|
39049
|
+
const propertyName = bindingIdentifier ? getDestructuredBindingPropertyName(bindingIdentifier) : null;
|
|
37874
39050
|
const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
|
|
37875
39051
|
return propertyName && propsReference ? propertyName : null;
|
|
37876
39052
|
}
|
|
@@ -37973,7 +39149,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
37973
39149
|
const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
|
|
37974
39150
|
if (!bindingProvenance) return null;
|
|
37975
39151
|
const { refCall, variables } = bindingProvenance;
|
|
37976
|
-
const componentFunction = findEnclosingFunction(effectCall);
|
|
39152
|
+
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
37977
39153
|
if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
37978
39154
|
if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
|
|
37979
39155
|
const callbackPropNames = /* @__PURE__ */ new Set();
|
|
@@ -38137,7 +39313,7 @@ const noPassDataToParent = defineRule({
|
|
|
38137
39313
|
if (isConstant(argRef)) return false;
|
|
38138
39314
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
38139
39315
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
38140
|
-
if (
|
|
39316
|
+
if (resolveToFunction(argRef)) return false;
|
|
38141
39317
|
const argIdentifier = argRef.identifier;
|
|
38142
39318
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
38143
39319
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
@@ -38809,174 +39985,6 @@ const noPropCallbackInRender = defineRule({
|
|
|
38809
39985
|
} })
|
|
38810
39986
|
});
|
|
38811
39987
|
//#endregion
|
|
38812
|
-
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
38813
|
-
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
38814
|
-
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
38815
|
-
const expression = stripParenExpression(node);
|
|
38816
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
38817
|
-
const propertyName = getStaticPropertyName(expression);
|
|
38818
|
-
const receiver = stripParenExpression(expression.object);
|
|
38819
|
-
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
38820
|
-
}
|
|
38821
|
-
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
38822
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
38823
|
-
const symbol = scopes.symbolFor(expression);
|
|
38824
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
38825
|
-
visitedSymbolIds.add(symbol.id);
|
|
38826
|
-
if (isImportedFromReact(symbol)) {
|
|
38827
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
38828
|
-
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
38829
|
-
}
|
|
38830
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
38831
|
-
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
38832
|
-
};
|
|
38833
|
-
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
38834
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
38835
|
-
visitedClassNodes.add(classNode);
|
|
38836
|
-
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
38837
|
-
};
|
|
38838
|
-
//#endregion
|
|
38839
|
-
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
38840
|
-
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
38841
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
38842
|
-
let containsReactHookCall = false;
|
|
38843
|
-
walkAst(functionNode.body, (node) => {
|
|
38844
|
-
if (containsReactHookCall) return false;
|
|
38845
|
-
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
38846
|
-
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
38847
|
-
containsReactHookCall = true;
|
|
38848
|
-
return false;
|
|
38849
|
-
}
|
|
38850
|
-
});
|
|
38851
|
-
return containsReactHookCall;
|
|
38852
|
-
};
|
|
38853
|
-
//#endregion
|
|
38854
|
-
//#region src/plugin/utils/function-returns-props-children.ts
|
|
38855
|
-
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
38856
|
-
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
38857
|
-
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
38858
|
-
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
38859
|
-
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
38860
|
-
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
38861
|
-
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
38862
|
-
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
38863
|
-
const propertyValue = stripParenExpression(property.value);
|
|
38864
|
-
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
38865
|
-
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
38866
|
-
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
38867
|
-
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
38868
|
-
}
|
|
38869
|
-
}
|
|
38870
|
-
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
38871
|
-
const candidate = stripParenExpression(expression);
|
|
38872
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
38873
|
-
const symbol = scopes.symbolFor(candidate);
|
|
38874
|
-
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
38875
|
-
}
|
|
38876
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
38877
|
-
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
38878
|
-
const receiver = stripParenExpression(candidate.object);
|
|
38879
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
38880
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
38881
|
-
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
38882
|
-
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
38883
|
-
}, controlFlow);
|
|
38884
|
-
};
|
|
38885
|
-
//#endregion
|
|
38886
|
-
//#region src/plugin/utils/function-returns-only-null.ts
|
|
38887
|
-
const isNullExpression = (expression) => {
|
|
38888
|
-
const candidate = stripParenExpression(expression);
|
|
38889
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
38890
|
-
};
|
|
38891
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
38892
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
38893
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
38894
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
38895
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
38896
|
-
};
|
|
38897
|
-
//#endregion
|
|
38898
|
-
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
38899
|
-
const findFactoryRoot = (node) => {
|
|
38900
|
-
const candidate = stripParenExpression(node);
|
|
38901
|
-
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
38902
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
38903
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
38904
|
-
return null;
|
|
38905
|
-
};
|
|
38906
|
-
const findFactoryPropertyName = (node) => {
|
|
38907
|
-
const candidate = stripParenExpression(node);
|
|
38908
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
38909
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
38910
|
-
return null;
|
|
38911
|
-
};
|
|
38912
|
-
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
38913
|
-
const candidate = stripParenExpression(expression);
|
|
38914
|
-
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
38915
|
-
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
38916
|
-
if (!factoryRoot) return false;
|
|
38917
|
-
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
38918
|
-
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
38919
|
-
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
38920
|
-
if (!symbol || symbol.kind !== "import") return false;
|
|
38921
|
-
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
38922
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
38923
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
38924
|
-
};
|
|
38925
|
-
//#endregion
|
|
38926
|
-
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
38927
|
-
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
38928
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
38929
|
-
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
38930
|
-
const candidate = stripParenExpression(expression);
|
|
38931
|
-
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
38932
|
-
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
38933
|
-
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
38934
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
38935
|
-
const symbol = scopes.symbolFor(candidate);
|
|
38936
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
38937
|
-
visitedSymbolIds.add(symbol.id);
|
|
38938
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
38939
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
38940
|
-
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
38941
|
-
}
|
|
38942
|
-
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
38943
|
-
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
38944
|
-
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
38945
|
-
const wrappedComponent = candidate.arguments[0];
|
|
38946
|
-
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
38947
|
-
}
|
|
38948
|
-
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
38949
|
-
const factory = candidate.arguments[0];
|
|
38950
|
-
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
38951
|
-
const unwrappedFactory = stripParenExpression(factory);
|
|
38952
|
-
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
38953
|
-
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
38954
|
-
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
38955
|
-
const returnedExpression = returnStatements[0]?.argument;
|
|
38956
|
-
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
38957
|
-
};
|
|
38958
|
-
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
38959
|
-
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
38960
|
-
for (const candidateSymbol of candidateSymbols) {
|
|
38961
|
-
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
38962
|
-
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
38963
|
-
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
38964
|
-
continue;
|
|
38965
|
-
}
|
|
38966
|
-
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
38967
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
38968
|
-
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
38969
|
-
continue;
|
|
38970
|
-
}
|
|
38971
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
38972
|
-
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
38973
|
-
continue;
|
|
38974
|
-
}
|
|
38975
|
-
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
38976
|
-
}
|
|
38977
|
-
return false;
|
|
38978
|
-
};
|
|
38979
|
-
//#endregion
|
|
38980
39988
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
38981
39989
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
38982
39990
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -39129,7 +40137,7 @@ const isModuleScopedBinding = (identifier) => {
|
|
|
39129
40137
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
39130
40138
|
if (!binding) return false;
|
|
39131
40139
|
if (isNodeOfType(binding.scopeOwner, "Program")) return true;
|
|
39132
|
-
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
|
|
40140
|
+
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction$1(binding.scopeOwner) === null;
|
|
39133
40141
|
return false;
|
|
39134
40142
|
};
|
|
39135
40143
|
const looksLikeFreshUpdateExpression = (expression) => {
|
|
@@ -39184,7 +40192,7 @@ const noRandomKey = defineRule({
|
|
|
39184
40192
|
});
|
|
39185
40193
|
//#endregion
|
|
39186
40194
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
39187
|
-
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.";
|
|
39188
40196
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
39189
40197
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
39190
40198
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -39210,13 +40218,13 @@ const noReactChildren = defineRule({
|
|
|
39210
40218
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
39211
40219
|
context.report({
|
|
39212
40220
|
node: calleeOuter,
|
|
39213
|
-
message: MESSAGE$
|
|
40221
|
+
message: MESSAGE$16
|
|
39214
40222
|
});
|
|
39215
40223
|
return;
|
|
39216
40224
|
}
|
|
39217
40225
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
39218
40226
|
node: calleeOuter,
|
|
39219
|
-
message: MESSAGE$
|
|
40227
|
+
message: MESSAGE$16
|
|
39220
40228
|
});
|
|
39221
40229
|
} })
|
|
39222
40230
|
});
|
|
@@ -39897,7 +40905,7 @@ const noRenderPropChildren = defineRule({
|
|
|
39897
40905
|
});
|
|
39898
40906
|
//#endregion
|
|
39899
40907
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
39900
|
-
const MESSAGE$
|
|
40908
|
+
const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
39901
40909
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
39902
40910
|
const isUsedAsReturnValue = (parent) => {
|
|
39903
40911
|
if (!parent) return false;
|
|
@@ -39915,7 +40923,7 @@ const noRenderReturnValue = defineRule({
|
|
|
39915
40923
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
39916
40924
|
context.report({
|
|
39917
40925
|
node: node.callee,
|
|
39918
|
-
message: MESSAGE$
|
|
40926
|
+
message: MESSAGE$15
|
|
39919
40927
|
});
|
|
39920
40928
|
} })
|
|
39921
40929
|
});
|
|
@@ -40724,7 +41732,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
40724
41732
|
});
|
|
40725
41733
|
//#endregion
|
|
40726
41734
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
40727
|
-
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.";
|
|
40728
41736
|
const noSetState = defineRule({
|
|
40729
41737
|
id: "no-set-state",
|
|
40730
41738
|
title: "Local class state forbidden",
|
|
@@ -40739,7 +41747,7 @@ const noSetState = defineRule({
|
|
|
40739
41747
|
if (!getParentComponent(node)) return;
|
|
40740
41748
|
context.report({
|
|
40741
41749
|
node: node.callee,
|
|
40742
|
-
message: MESSAGE$
|
|
41750
|
+
message: MESSAGE$14
|
|
40743
41751
|
});
|
|
40744
41752
|
} })
|
|
40745
41753
|
});
|
|
@@ -41046,9 +42054,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
|
41046
42054
|
return isReturnedFromEffect;
|
|
41047
42055
|
};
|
|
41048
42056
|
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
41049
|
-
let functionNode = findEnclosingFunction(node);
|
|
42057
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
41050
42058
|
while (functionNode) {
|
|
41051
|
-
const outerFunction = findEnclosingFunction(functionNode);
|
|
42059
|
+
const outerFunction = findEnclosingFunction$1(functionNode);
|
|
41052
42060
|
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
41053
42061
|
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
41054
42062
|
functionNode = outerFunction;
|
|
@@ -41056,7 +42064,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
|
41056
42064
|
return false;
|
|
41057
42065
|
};
|
|
41058
42066
|
const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
|
|
41059
|
-
const enclosingFunction = findEnclosingFunction(clearCall);
|
|
42067
|
+
const enclosingFunction = findEnclosingFunction$1(clearCall);
|
|
41060
42068
|
if (!isFunctionLike$2(enclosingFunction)) return false;
|
|
41061
42069
|
if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
41062
42070
|
const clearStart = getRangeStart(clearCall);
|
|
@@ -41109,7 +42117,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
41109
42117
|
};
|
|
41110
42118
|
//#endregion
|
|
41111
42119
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
41112
|
-
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.";
|
|
41113
42121
|
const DEFAULT_HANDLERS = [
|
|
41114
42122
|
"onClick",
|
|
41115
42123
|
"onMouseDown",
|
|
@@ -41224,7 +42232,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41224
42232
|
if (!roleAttribute || !roleAttribute.value) {
|
|
41225
42233
|
context.report({
|
|
41226
42234
|
node: node.name,
|
|
41227
|
-
message: MESSAGE$
|
|
42235
|
+
message: MESSAGE$13
|
|
41228
42236
|
});
|
|
41229
42237
|
return;
|
|
41230
42238
|
}
|
|
@@ -41234,7 +42242,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41234
42242
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
41235
42243
|
context.report({
|
|
41236
42244
|
node: node.name,
|
|
41237
|
-
message: MESSAGE$
|
|
42245
|
+
message: MESSAGE$13
|
|
41238
42246
|
});
|
|
41239
42247
|
return;
|
|
41240
42248
|
}
|
|
@@ -41242,7 +42250,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41242
42250
|
if (!settings.allowExpressionValues) {
|
|
41243
42251
|
context.report({
|
|
41244
42252
|
node: node.name,
|
|
41245
|
-
message: MESSAGE$
|
|
42253
|
+
message: MESSAGE$13
|
|
41246
42254
|
});
|
|
41247
42255
|
return;
|
|
41248
42256
|
}
|
|
@@ -41250,7 +42258,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41250
42258
|
if (isStaticNullishExpression(expression)) {
|
|
41251
42259
|
context.report({
|
|
41252
42260
|
node: node.name,
|
|
41253
|
-
message: MESSAGE$
|
|
42261
|
+
message: MESSAGE$13
|
|
41254
42262
|
});
|
|
41255
42263
|
return;
|
|
41256
42264
|
}
|
|
@@ -41260,7 +42268,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41260
42268
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
41261
42269
|
context.report({
|
|
41262
42270
|
node: node.name,
|
|
41263
|
-
message: MESSAGE$
|
|
42271
|
+
message: MESSAGE$13
|
|
41264
42272
|
});
|
|
41265
42273
|
return;
|
|
41266
42274
|
}
|
|
@@ -41268,7 +42276,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41268
42276
|
}
|
|
41269
42277
|
context.report({
|
|
41270
42278
|
node: node.name,
|
|
41271
|
-
message: MESSAGE$
|
|
42279
|
+
message: MESSAGE$13
|
|
41272
42280
|
});
|
|
41273
42281
|
} };
|
|
41274
42282
|
}
|
|
@@ -41374,7 +42382,7 @@ const noStringRefs = defineRule({
|
|
|
41374
42382
|
});
|
|
41375
42383
|
//#endregion
|
|
41376
42384
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
41377
|
-
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)`).";
|
|
41378
42386
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
41379
42387
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
41380
42388
|
const noSyncXhr = defineRule({
|
|
@@ -41395,7 +42403,7 @@ const noSyncXhr = defineRule({
|
|
|
41395
42403
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
41396
42404
|
context.report({
|
|
41397
42405
|
node,
|
|
41398
|
-
message: MESSAGE$
|
|
42406
|
+
message: MESSAGE$12
|
|
41399
42407
|
});
|
|
41400
42408
|
};
|
|
41401
42409
|
return {
|
|
@@ -41420,7 +42428,7 @@ const noSyncXhr = defineRule({
|
|
|
41420
42428
|
});
|
|
41421
42429
|
//#endregion
|
|
41422
42430
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
41423
|
-
const MESSAGE$
|
|
42431
|
+
const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
|
|
41424
42432
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
41425
42433
|
let ancestor = node.parent;
|
|
41426
42434
|
while (ancestor) {
|
|
@@ -41508,7 +42516,7 @@ const noThisInSfc = defineRule({
|
|
|
41508
42516
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
41509
42517
|
context.report({
|
|
41510
42518
|
node,
|
|
41511
|
-
message: MESSAGE$
|
|
42519
|
+
message: MESSAGE$11
|
|
41512
42520
|
});
|
|
41513
42521
|
} };
|
|
41514
42522
|
}
|
|
@@ -41886,7 +42894,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
|
|
|
41886
42894
|
return false;
|
|
41887
42895
|
};
|
|
41888
42896
|
const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
|
|
41889
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
42897
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
41890
42898
|
if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
41891
42899
|
let currentNode = node;
|
|
41892
42900
|
while (currentNode !== enclosingFunction) {
|
|
@@ -43153,7 +44161,11 @@ const resolveSettings$8 = (settings) => {
|
|
|
43153
44161
|
propNamePattern: ruleSettings.propNamePattern ?? "render*"
|
|
43154
44162
|
};
|
|
43155
44163
|
};
|
|
43156
|
-
const
|
|
44164
|
+
const isReactCreateElementCall = (node, scopes) => isReactApiCall(node, "createElement", scopes, {
|
|
44165
|
+
allowGlobalReactNamespace: true,
|
|
44166
|
+
resolveNamedAliases: true
|
|
44167
|
+
});
|
|
44168
|
+
const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
43157
44169
|
let found = false;
|
|
43158
44170
|
walkAst(root, (node) => {
|
|
43159
44171
|
if (found) return false;
|
|
@@ -43162,17 +44174,17 @@ const expressionContainsJsxOrCreateElement = (root) => {
|
|
|
43162
44174
|
found = true;
|
|
43163
44175
|
return false;
|
|
43164
44176
|
}
|
|
43165
|
-
if (isNodeOfType(node, "CallExpression") &&
|
|
44177
|
+
if (isNodeOfType(node, "CallExpression") && isReactCreateElementCall(node, scopes)) {
|
|
43166
44178
|
found = true;
|
|
43167
44179
|
return false;
|
|
43168
44180
|
}
|
|
43169
44181
|
});
|
|
43170
44182
|
return found;
|
|
43171
44183
|
};
|
|
43172
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
|
|
43173
|
-
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) => {
|
|
43174
44186
|
if (isEs6Component(classNode)) return true;
|
|
43175
|
-
return expressionContainsJsxOrCreateElement(classNode);
|
|
44187
|
+
return expressionContainsJsxOrCreateElement(classNode, scopes);
|
|
43176
44188
|
};
|
|
43177
44189
|
const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
43178
44190
|
let walker = node.parent;
|
|
@@ -43189,7 +44201,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
|
43189
44201
|
};
|
|
43190
44202
|
}
|
|
43191
44203
|
if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
|
|
43192
|
-
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
|
|
44204
|
+
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker, scopes)) return {
|
|
43193
44205
|
component: walker,
|
|
43194
44206
|
name: walker.id.name
|
|
43195
44207
|
};
|
|
@@ -43278,12 +44290,12 @@ const isObjectCallbackCandidate = (node) => {
|
|
|
43278
44290
|
}
|
|
43279
44291
|
return false;
|
|
43280
44292
|
};
|
|
43281
|
-
const hocCallContainsComponent = (call) => {
|
|
44293
|
+
const hocCallContainsComponent = (call, scopes) => {
|
|
43282
44294
|
const firstArgument = call.arguments[0];
|
|
43283
44295
|
if (!firstArgument) return false;
|
|
43284
|
-
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument);
|
|
43285
|
-
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument);
|
|
43286
|
-
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);
|
|
43287
44299
|
};
|
|
43288
44300
|
const isFirstArgumentOfHocCall = (node) => {
|
|
43289
44301
|
const parent = node.parent;
|
|
@@ -43316,19 +44328,35 @@ const isReturnOfMapCallback = (node) => {
|
|
|
43316
44328
|
}
|
|
43317
44329
|
return false;
|
|
43318
44330
|
};
|
|
43319
|
-
const isCloneElementCall = (node) => {
|
|
43320
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
43321
|
-
const callee = node.callee;
|
|
43322
|
-
if (isNodeOfType(callee, "Identifier")) return callee.name === "cloneElement";
|
|
43323
|
-
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "cloneElement";
|
|
43324
|
-
};
|
|
43325
44331
|
const TS_VALUE_PASSTHROUGH_TYPES = new Set([
|
|
43326
44332
|
"TSAsExpression",
|
|
43327
44333
|
"TSNonNullExpression",
|
|
43328
44334
|
"TSSatisfiesExpression",
|
|
43329
44335
|
"TSTypeAssertion"
|
|
43330
44336
|
]);
|
|
43331
|
-
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()) => {
|
|
43332
44360
|
let valueNode = identifier;
|
|
43333
44361
|
let parent = valueNode.parent;
|
|
43334
44362
|
while (parent) {
|
|
@@ -43338,20 +44366,26 @@ const isRenderFlowingReadReference = (identifier) => {
|
|
|
43338
44366
|
continue;
|
|
43339
44367
|
}
|
|
43340
44368
|
switch (parent.type) {
|
|
43341
|
-
case "
|
|
43342
|
-
case "
|
|
43343
|
-
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;
|
|
43344
44373
|
case "CallExpression":
|
|
43345
44374
|
if (parent.callee === valueNode) return false;
|
|
43346
|
-
if (
|
|
44375
|
+
if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
|
|
44376
|
+
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
43347
44377
|
valueNode = parent;
|
|
43348
44378
|
parent = parent.parent;
|
|
43349
44379
|
continue;
|
|
43350
|
-
case "VariableDeclarator":
|
|
43351
|
-
|
|
43352
|
-
const
|
|
43353
|
-
|
|
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));
|
|
43354
44387
|
}
|
|
44388
|
+
case "AssignmentExpression": return false;
|
|
43355
44389
|
case "Property":
|
|
43356
44390
|
if (parent.value !== valueNode) return false;
|
|
43357
44391
|
valueNode = parent;
|
|
@@ -43389,6 +44423,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43389
44423
|
severity: "warn",
|
|
43390
44424
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
43391
44425
|
category: "Performance",
|
|
44426
|
+
tags: ["react-jsx-only"],
|
|
43392
44427
|
create: (context) => {
|
|
43393
44428
|
const settings = resolveSettings$8(context.settings);
|
|
43394
44429
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -43451,7 +44486,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43451
44486
|
},
|
|
43452
44487
|
Identifier(node) {
|
|
43453
44488
|
if (!isReactComponentName(node.name)) return;
|
|
43454
|
-
if (!isRenderFlowingReadReference(node)) return;
|
|
44489
|
+
if (!isRenderFlowingReadReference(node, context.scopes)) return;
|
|
43455
44490
|
recordInstantiation(node, node.name);
|
|
43456
44491
|
},
|
|
43457
44492
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -43460,28 +44495,33 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43460
44495
|
ClassDeclaration(node) {
|
|
43461
44496
|
if (!node.id) return;
|
|
43462
44497
|
if (!isReactComponentName(node.id.name)) return;
|
|
43463
|
-
if (!isReactClassComponent(node)) return;
|
|
44498
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43464
44499
|
enqueueCandidate(node, null);
|
|
43465
44500
|
},
|
|
43466
44501
|
ClassExpression(node) {
|
|
43467
44502
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
43468
44503
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
43469
|
-
if (!isReactClassComponent(node)) return;
|
|
44504
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43470
44505
|
enqueueCandidate(node, null);
|
|
43471
44506
|
},
|
|
43472
44507
|
CallExpression(node) {
|
|
43473
|
-
if (
|
|
44508
|
+
if (isReactCreateElementCall(node, context.scopes)) {
|
|
43474
44509
|
const firstArgument = node.arguments[0];
|
|
43475
44510
|
if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
|
|
43476
44511
|
else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
|
|
43477
44512
|
}
|
|
43478
|
-
|
|
43479
|
-
if (!
|
|
43480
|
-
|
|
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);
|
|
43481
44520
|
},
|
|
43482
44521
|
"Program:exit"() {
|
|
43483
44522
|
for (const report of queuedReports) {
|
|
43484
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;
|
|
43485
44525
|
if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
|
|
43486
44526
|
}
|
|
43487
44527
|
context.report({
|
|
@@ -43655,7 +44695,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
43655
44695
|
//#endregion
|
|
43656
44696
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
43657
44697
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
43658
|
-
const MESSAGE$
|
|
44698
|
+
const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
43659
44699
|
const resolveSettings$7 = (settings) => {
|
|
43660
44700
|
const reactDoctor = settings?.["react-doctor"];
|
|
43661
44701
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -43689,7 +44729,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
43689
44729
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
43690
44730
|
context.report({
|
|
43691
44731
|
node: node.callee,
|
|
43692
|
-
message: MESSAGE$
|
|
44732
|
+
message: MESSAGE$10
|
|
43693
44733
|
});
|
|
43694
44734
|
} };
|
|
43695
44735
|
}
|
|
@@ -44514,7 +45554,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
|
|
|
44514
45554
|
const symbol = scopes.symbolFor(identifier);
|
|
44515
45555
|
if (symbol) {
|
|
44516
45556
|
if (symbol.kind !== "parameter") return null;
|
|
44517
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45557
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
44518
45558
|
if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
|
|
44519
45559
|
return null;
|
|
44520
45560
|
}
|
|
@@ -44560,7 +45600,7 @@ const isChildrenMemberExpression = (node, scopes) => {
|
|
|
44560
45600
|
const symbol = scopes.symbolFor(propsObject);
|
|
44561
45601
|
if (symbol) {
|
|
44562
45602
|
if (symbol.kind !== "parameter") return false;
|
|
44563
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45603
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
44564
45604
|
return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
|
|
44565
45605
|
}
|
|
44566
45606
|
return hasComponentLikeAncestorFunction(node);
|
|
@@ -44685,7 +45725,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
44685
45725
|
});
|
|
44686
45726
|
//#endregion
|
|
44687
45727
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
44688
|
-
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.";
|
|
44689
45729
|
const preactPreferOndblclick = defineRule({
|
|
44690
45730
|
id: "preact-prefer-ondblclick",
|
|
44691
45731
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -44700,7 +45740,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
44700
45740
|
if (!onDoubleClickAttribute) return;
|
|
44701
45741
|
context.report({
|
|
44702
45742
|
node: onDoubleClickAttribute,
|
|
44703
|
-
message: MESSAGE$
|
|
45743
|
+
message: MESSAGE$9
|
|
44704
45744
|
});
|
|
44705
45745
|
} })
|
|
44706
45746
|
});
|
|
@@ -44951,7 +45991,7 @@ const preferExplicitVariants = defineRule({
|
|
|
44951
45991
|
});
|
|
44952
45992
|
//#endregion
|
|
44953
45993
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
44954
|
-
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.";
|
|
44955
45995
|
const resolveSettings$4 = (settings) => {
|
|
44956
45996
|
const reactDoctor = settings?.["react-doctor"];
|
|
44957
45997
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -44990,7 +46030,7 @@ const preferFunctionComponent = defineRule({
|
|
|
44990
46030
|
const reportNode = node.id ?? node;
|
|
44991
46031
|
context.report({
|
|
44992
46032
|
node: reportNode,
|
|
44993
|
-
message: MESSAGE$
|
|
46033
|
+
message: MESSAGE$8
|
|
44994
46034
|
});
|
|
44995
46035
|
};
|
|
44996
46036
|
return {
|
|
@@ -45212,7 +46252,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
|
|
|
45212
46252
|
//#endregion
|
|
45213
46253
|
//#region src/plugin/utils/enclosing-component-or-hook-scope.ts
|
|
45214
46254
|
const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
|
|
45215
|
-
const functionNode = findEnclosingFunction(startNode);
|
|
46255
|
+
const functionNode = findEnclosingFunction$1(startNode);
|
|
45216
46256
|
if (!functionNode) return null;
|
|
45217
46257
|
const displayName = componentOrHookDisplayNameForFunction(functionNode);
|
|
45218
46258
|
if (!displayName) return null;
|
|
@@ -46274,7 +47314,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
|
|
|
46274
47314
|
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
46275
47315
|
const isHookReturnForwardingSpread = (objectExpression) => {
|
|
46276
47316
|
const objectParent = objectExpression.parent;
|
|
46277
|
-
const enclosingFunction = findEnclosingFunction(objectExpression);
|
|
47317
|
+
const enclosingFunction = findEnclosingFunction$1(objectExpression);
|
|
46278
47318
|
if (!enclosingFunction) return false;
|
|
46279
47319
|
if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
|
|
46280
47320
|
const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
|
|
@@ -46568,7 +47608,258 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
46568
47608
|
}
|
|
46569
47609
|
});
|
|
46570
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
|
|
46571
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
|
+
};
|
|
46572
47863
|
const queryNoQueryInEffect = defineRule({
|
|
46573
47864
|
id: "query-no-query-in-effect",
|
|
46574
47865
|
title: "Query refetch inside useEffect",
|
|
@@ -46584,8 +47875,7 @@ const queryNoQueryInEffect = defineRule({
|
|
|
46584
47875
|
walkAst(callback, (child) => {
|
|
46585
47876
|
if (child !== callback && isFunctionLike$2(child) && !effectInvokedFunctions.has(child)) return false;
|
|
46586
47877
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
46587
|
-
|
|
46588
|
-
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({
|
|
46589
47879
|
node: child,
|
|
46590
47880
|
message: "refetch() inside useEffect duplicates work React Query already does, causing extra fetches."
|
|
46591
47881
|
});
|
|
@@ -46594,28 +47884,6 @@ const queryNoQueryInEffect = defineRule({
|
|
|
46594
47884
|
});
|
|
46595
47885
|
//#endregion
|
|
46596
47886
|
//#region src/plugin/rules/tanstack-query/query-no-rest-destructuring.ts
|
|
46597
|
-
const resolveTanstackQueryHookName = (callExpression) => {
|
|
46598
|
-
const callee = callExpression.callee;
|
|
46599
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
46600
|
-
const importBinding = getImportBindingForName(callExpression, callee.name);
|
|
46601
|
-
if (importBinding === null) return TANSTACK_QUERY_HOOKS.has(callee.name) ? callee.name : null;
|
|
46602
|
-
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
46603
|
-
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
46604
|
-
}
|
|
46605
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && TANSTACK_QUERY_HOOKS.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) {
|
|
46606
|
-
const namespaceBinding = getImportBindingForName(callExpression, callee.object.name);
|
|
46607
|
-
if (namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source)) return callee.property.name;
|
|
46608
|
-
}
|
|
46609
|
-
return null;
|
|
46610
|
-
};
|
|
46611
|
-
const resolveHookNameFromInitializer = (initializer, scopes) => {
|
|
46612
|
-
if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
|
|
46613
|
-
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
46614
|
-
const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
|
|
46615
|
-
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
46616
|
-
if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
|
|
46617
|
-
return resolveTanstackQueryHookName(resolvedSymbol.initializer);
|
|
46618
|
-
};
|
|
46619
47887
|
const queryNoRestDestructuring = defineRule({
|
|
46620
47888
|
id: "query-no-rest-destructuring",
|
|
46621
47889
|
title: "Rest destructuring on query result",
|
|
@@ -46627,7 +47895,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
46627
47895
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
46628
47896
|
if (!node.init) return;
|
|
46629
47897
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
46630
|
-
const hookName =
|
|
47898
|
+
const hookName = resolveTanstackQueryHookNameFromInitializer(node.init, context.scopes);
|
|
46631
47899
|
if (!hookName) return;
|
|
46632
47900
|
context.report({
|
|
46633
47901
|
node: node.id,
|
|
@@ -46762,8 +48030,8 @@ const queryStableQueryClient = defineRule({
|
|
|
46762
48030
|
create: (context) => ({ NewExpression(node) {
|
|
46763
48031
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
|
|
46764
48032
|
if (isStableHookWrapperArgument(node)) return;
|
|
46765
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
46766
|
-
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
48033
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
48034
|
+
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
46767
48035
|
if (!enclosingFunction) return;
|
|
46768
48036
|
if (!isComponentFunction(enclosingFunction)) return;
|
|
46769
48037
|
context.report({
|
|
@@ -46858,7 +48126,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
46858
48126
|
const comparatorArgument = node.arguments?.[1];
|
|
46859
48127
|
if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
|
|
46860
48128
|
} else {
|
|
46861
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
48129
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
46862
48130
|
if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
|
|
46863
48131
|
}
|
|
46864
48132
|
const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
|
|
@@ -46871,7 +48139,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
46871
48139
|
});
|
|
46872
48140
|
//#endregion
|
|
46873
48141
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
46874
|
-
const MESSAGE$
|
|
48142
|
+
const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
|
|
46875
48143
|
const reactInJsxScope = defineRule({
|
|
46876
48144
|
id: "react-in-jsx-scope",
|
|
46877
48145
|
title: "React not in scope for JSX",
|
|
@@ -46883,19 +48151,190 @@ const reactInJsxScope = defineRule({
|
|
|
46883
48151
|
if (findVariableInitializer(node, "React")) return;
|
|
46884
48152
|
context.report({
|
|
46885
48153
|
node: node.name,
|
|
46886
|
-
message: MESSAGE$
|
|
48154
|
+
message: MESSAGE$7
|
|
46887
48155
|
});
|
|
46888
48156
|
},
|
|
46889
48157
|
JSXFragment(node) {
|
|
46890
48158
|
if (findVariableInitializer(node, "React")) return;
|
|
46891
48159
|
context.report({
|
|
46892
48160
|
node: node.openingFragment,
|
|
46893
|
-
message: MESSAGE$
|
|
48161
|
+
message: MESSAGE$7
|
|
46894
48162
|
});
|
|
46895
48163
|
}
|
|
46896
48164
|
})
|
|
46897
48165
|
});
|
|
46898
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
|
|
46899
48338
|
//#region src/plugin/utils/collect-react-redux-selector-aliases.ts
|
|
46900
48339
|
const REACT_REDUX_MODULE = "react-redux";
|
|
46901
48340
|
const collectReactReduxSelectorAliases = (programRoot) => {
|
|
@@ -49792,7 +51231,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
|
|
|
49792
51231
|
};
|
|
49793
51232
|
const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
|
|
49794
51233
|
const isInsideStyleFactoryCallback = (node) => {
|
|
49795
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
51234
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
49796
51235
|
if (!enclosingFunction) return false;
|
|
49797
51236
|
const callExpression = enclosingFunction.parent;
|
|
49798
51237
|
if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -55323,22 +56762,6 @@ const isInsideClassComponent = (node) => {
|
|
|
55323
56762
|
}
|
|
55324
56763
|
return false;
|
|
55325
56764
|
};
|
|
55326
|
-
const hasShortCircuitAncestor = (descendant, ancestor) => {
|
|
55327
|
-
let current = descendant.parent;
|
|
55328
|
-
while (current && current !== ancestor) {
|
|
55329
|
-
if (isNodeOfType(current, "ConditionalExpression") && (isWithinRange(descendant, current.consequent) || isWithinRange(descendant, current.alternate))) return true;
|
|
55330
|
-
if (isNodeOfType(current, "LogicalExpression") && (current.operator === "&&" || current.operator === "||" || current.operator === "??") && isWithinRange(descendant, current.right)) return true;
|
|
55331
|
-
if (isNodeOfType(current, "AssignmentPattern") && isWithinRange(descendant, current.right)) return true;
|
|
55332
|
-
current = current.parent ?? null;
|
|
55333
|
-
}
|
|
55334
|
-
return false;
|
|
55335
|
-
};
|
|
55336
|
-
const isWithinRange = (descendant, sibling) => {
|
|
55337
|
-
const descendantSpan = descendant;
|
|
55338
|
-
const siblingSpan = sibling;
|
|
55339
|
-
if (typeof descendantSpan.start !== "number" || typeof siblingSpan.start !== "number" || typeof siblingSpan.end !== "number") return false;
|
|
55340
|
-
return descendantSpan.start >= siblingSpan.start && (descendantSpan.end ?? siblingSpan.end) <= siblingSpan.end;
|
|
55341
|
-
};
|
|
55342
56765
|
const isInsideTry = (descendant, ancestor) => {
|
|
55343
56766
|
let current = descendant.parent;
|
|
55344
56767
|
while (current && current !== ancestor) {
|
|
@@ -55530,7 +56953,7 @@ const rulesOfHooks = defineRule({
|
|
|
55530
56953
|
});
|
|
55531
56954
|
return;
|
|
55532
56955
|
}
|
|
55533
|
-
if (
|
|
56956
|
+
if (isNodeConditionallyExecuted(node, enclosing.node)) {
|
|
55534
56957
|
context.report({
|
|
55535
56958
|
node: node.callee,
|
|
55536
56959
|
message: buildConditionalMessage(hookName)
|
|
@@ -56664,12 +58087,14 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
|
|
|
56664
58087
|
}
|
|
56665
58088
|
return false;
|
|
56666
58089
|
};
|
|
56667
|
-
const declarationAwaitsGate = (declaration) => {
|
|
58090
|
+
const declarationAwaitsGate = (declaration, context) => {
|
|
56668
58091
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
56669
58092
|
for (const declarator of declaration.declarations ?? []) {
|
|
56670
58093
|
if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
|
|
56671
58094
|
const argument = declarator.init.argument;
|
|
56672
58095
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
58096
|
+
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
58097
|
+
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
56673
58098
|
const calleeName = getCalleeName$2(argument);
|
|
56674
58099
|
if (!calleeName) continue;
|
|
56675
58100
|
if (isAuthGuardName(calleeName)) return true;
|
|
@@ -56697,7 +58122,7 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
56697
58122
|
if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
|
|
56698
58123
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
56699
58124
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
56700
|
-
if (declarationAwaitsGate(currentStatement)) continue;
|
|
58125
|
+
if (declarationAwaitsGate(currentStatement, context)) continue;
|
|
56701
58126
|
context.report({
|
|
56702
58127
|
node: nextStatement,
|
|
56703
58128
|
message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
|
|
@@ -57683,7 +59108,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
57683
59108
|
const isReturnedFromCustomHook = (functionNode) => {
|
|
57684
59109
|
const parent = functionNode.parent;
|
|
57685
59110
|
if (isNodeOfType(parent, "ReturnStatement")) {
|
|
57686
|
-
const outerFunction = findEnclosingFunction(parent);
|
|
59111
|
+
const outerFunction = findEnclosingFunction$1(parent);
|
|
57687
59112
|
const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
|
|
57688
59113
|
return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
|
|
57689
59114
|
}
|
|
@@ -57700,13 +59125,13 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
57700
59125
|
return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
|
|
57701
59126
|
};
|
|
57702
59127
|
const isDeferredNavigateCall = (callNode) => {
|
|
57703
|
-
let enclosingFunction = findEnclosingFunction(callNode);
|
|
59128
|
+
let enclosingFunction = findEnclosingFunction$1(callNode);
|
|
57704
59129
|
while (enclosingFunction) {
|
|
57705
59130
|
if (isDeferredCallbackPosition(enclosingFunction)) return true;
|
|
57706
59131
|
if (isWiredAsEventHandler(enclosingFunction)) return true;
|
|
57707
59132
|
if (isReturnedFromCustomHook(enclosingFunction)) return true;
|
|
57708
59133
|
if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
|
|
57709
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
59134
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
57710
59135
|
}
|
|
57711
59136
|
return false;
|
|
57712
59137
|
};
|
|
@@ -62145,6 +63570,17 @@ const reactDoctorRules = [
|
|
|
62145
63570
|
requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
|
|
62146
63571
|
}
|
|
62147
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
|
+
},
|
|
62148
63584
|
{
|
|
62149
63585
|
key: "react-doctor/redux-useselector-inline-derivation",
|
|
62150
63586
|
id: "redux-useselector-inline-derivation",
|