oxlint-plugin-react-doctor 0.7.7-dev.a468589 → 0.7.7-dev.afed801
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +0 -46
- package/dist/index.js +754 -2065
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,13 +7,6 @@ import { analyze } from "eslint-scope";
|
|
|
7
7
|
//#region src/plugin/utils/is-node-of-type.ts
|
|
8
8
|
const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
|
|
9
9
|
//#endregion
|
|
10
|
-
//#region src/plugin/utils/is-type-only-import.ts
|
|
11
|
-
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
12
|
-
if (!specifiers || specifiers.length === 0) return false;
|
|
13
|
-
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
14
|
-
};
|
|
15
|
-
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
16
|
-
//#endregion
|
|
17
10
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
18
11
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
19
12
|
"solid-js",
|
|
@@ -28,33 +21,17 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
|
28
21
|
"vidode"
|
|
29
22
|
]);
|
|
30
23
|
const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
|
|
31
|
-
const REACT_JSX_DIALECT_PACKAGE_PREFIXES = [
|
|
32
|
-
"react",
|
|
33
|
-
"react-dom",
|
|
34
|
-
"preact"
|
|
35
|
-
];
|
|
36
24
|
const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
|
|
37
|
-
const
|
|
38
|
-
let hasNonReactRuntime = false;
|
|
39
|
-
let hasReactRuntime = false;
|
|
25
|
+
const fileImportsNonReactJsxDialect = (program) => {
|
|
40
26
|
for (const statement of program.body) {
|
|
41
27
|
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
42
|
-
const
|
|
43
|
-
if (isTypeOnlyImport(importDeclaration)) continue;
|
|
44
|
-
const source = importDeclaration.source;
|
|
28
|
+
const source = statement.source;
|
|
45
29
|
const value = source && typeof source.value === "string" ? source.value : null;
|
|
46
30
|
if (!value) continue;
|
|
47
|
-
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)
|
|
48
|
-
if (startsWithAny(value,
|
|
31
|
+
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)) return true;
|
|
32
|
+
if (startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) return true;
|
|
49
33
|
}
|
|
50
|
-
return
|
|
51
|
-
hasNonReactRuntime,
|
|
52
|
-
hasReactRuntime
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
const fileImportsNonReactJsxDialect = (program) => {
|
|
56
|
-
const runtimeImports = collectJsxRuntimeImports$1(program);
|
|
57
|
-
return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
34
|
+
return false;
|
|
58
35
|
};
|
|
59
36
|
const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
|
|
60
37
|
for (const attribute of openingNode.attributes) {
|
|
@@ -267,7 +244,6 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
|
|
|
267
244
|
const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
268
245
|
const innerVisitors = create(context);
|
|
269
246
|
let fileIsNonReactJsx = false;
|
|
270
|
-
let fileImportsReactRuntime = false;
|
|
271
247
|
const wrappedVisitors = {};
|
|
272
248
|
for (const [key, visitor] of Object.entries(innerVisitors)) {
|
|
273
249
|
if (typeof visitor !== "function") {
|
|
@@ -280,16 +256,14 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
280
256
|
}
|
|
281
257
|
if (key === "Program") {
|
|
282
258
|
wrappedVisitors.Program = (node) => {
|
|
283
|
-
|
|
284
|
-
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
285
|
-
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
259
|
+
fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
|
|
286
260
|
visitor(node);
|
|
287
261
|
};
|
|
288
262
|
continue;
|
|
289
263
|
}
|
|
290
264
|
if (key === "JSXOpeningElement") {
|
|
291
265
|
wrappedVisitors.JSXOpeningElement = (node) => {
|
|
292
|
-
if (!
|
|
266
|
+
if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
293
267
|
if (fileIsNonReactJsx) return;
|
|
294
268
|
visitor(node);
|
|
295
269
|
};
|
|
@@ -301,9 +275,7 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
301
275
|
};
|
|
302
276
|
}
|
|
303
277
|
if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
|
|
304
|
-
|
|
305
|
-
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
306
|
-
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
278
|
+
fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
|
|
307
279
|
};
|
|
308
280
|
return wrappedVisitors;
|
|
309
281
|
});
|
|
@@ -2340,7 +2312,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2340
2312
|
});
|
|
2341
2313
|
//#endregion
|
|
2342
2314
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
2343
|
-
const MESSAGE$
|
|
2315
|
+
const MESSAGE$61 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
2344
2316
|
const isTransComponentsTemplate = (node) => {
|
|
2345
2317
|
let current = node.parent;
|
|
2346
2318
|
while (current) {
|
|
@@ -2376,7 +2348,7 @@ const anchorHasContent = defineRule({
|
|
|
2376
2348
|
if (isTransComponentsTemplate(node)) return;
|
|
2377
2349
|
context.report({
|
|
2378
2350
|
node: opening.name,
|
|
2379
|
-
message: MESSAGE$
|
|
2351
|
+
message: MESSAGE$61
|
|
2380
2352
|
});
|
|
2381
2353
|
} };
|
|
2382
2354
|
}
|
|
@@ -2808,7 +2780,7 @@ const parseJsxValue = (value) => {
|
|
|
2808
2780
|
};
|
|
2809
2781
|
//#endregion
|
|
2810
2782
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2811
|
-
const MESSAGE$
|
|
2783
|
+
const MESSAGE$60 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2812
2784
|
const mayBeContentEditable = (node) => {
|
|
2813
2785
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
|
|
2814
2786
|
if (!attribute) return false;
|
|
@@ -2839,7 +2811,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2839
2811
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2840
2812
|
context.report({
|
|
2841
2813
|
node: node.name,
|
|
2842
|
-
message: MESSAGE$
|
|
2814
|
+
message: MESSAGE$60
|
|
2843
2815
|
});
|
|
2844
2816
|
return;
|
|
2845
2817
|
}
|
|
@@ -2847,7 +2819,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2847
2819
|
if (mayBeContentEditable(node)) return;
|
|
2848
2820
|
context.report({
|
|
2849
2821
|
node: node.name,
|
|
2850
|
-
message: MESSAGE$
|
|
2822
|
+
message: MESSAGE$60
|
|
2851
2823
|
});
|
|
2852
2824
|
} })
|
|
2853
2825
|
});
|
|
@@ -4342,397 +4314,6 @@ const findTransparentExpressionRoot = (node) => {
|
|
|
4342
4314
|
return current;
|
|
4343
4315
|
};
|
|
4344
4316
|
//#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
|
|
4736
4317
|
//#region src/plugin/utils/is-inline-function-expression.ts
|
|
4737
4318
|
/**
|
|
4738
4319
|
* Type-guard for the two "inline function expression" ESTree forms:
|
|
@@ -4766,15 +4347,13 @@ const isIntentionalSequencingCallee = (callee) => {
|
|
|
4766
4347
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(callee.property.name);
|
|
4767
4348
|
return false;
|
|
4768
4349
|
};
|
|
4769
|
-
const isAwaitingSleepLikeCall = (awaitNode
|
|
4350
|
+
const isAwaitingSleepLikeCall = (awaitNode) => {
|
|
4770
4351
|
if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
|
|
4771
4352
|
const argument = awaitNode.argument;
|
|
4772
4353
|
if (!argument) return false;
|
|
4773
4354
|
if (!isNodeOfType(argument, "CallExpression")) return false;
|
|
4774
|
-
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) return false;
|
|
4775
4355
|
return isIntentionalSequencingCallee(argument.callee);
|
|
4776
4356
|
};
|
|
4777
|
-
const isAwaitingPossiblyMutatedMemberCall = (awaitNode, context) => isNodeOfType(awaitNode, "AwaitExpression") && Boolean(awaitNode.argument) && hasPossibleStaticMemberCallWrite(awaitNode.argument, context.scopes);
|
|
4778
4357
|
const PROMISE_CONCURRENCY_METHODS = new Set([
|
|
4779
4358
|
"all",
|
|
4780
4359
|
"allSettled",
|
|
@@ -4817,7 +4396,7 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
|
|
|
4817
4396
|
});
|
|
4818
4397
|
return isWaitLike;
|
|
4819
4398
|
};
|
|
4820
|
-
const isIntentionallySequentialAwait = (awaitNode
|
|
4399
|
+
const isIntentionallySequentialAwait = (awaitNode) => isAwaitingSleepLikeCall(awaitNode) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
|
|
4821
4400
|
const collectPatternIdentifiers = (pattern, target) => {
|
|
4822
4401
|
if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
|
|
4823
4402
|
else if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
@@ -5008,12 +4587,12 @@ const getLoopLabelName = (loopNode) => {
|
|
|
5008
4587
|
if (isNodeOfType(parent, "LabeledStatement") && isNodeOfType(parent.label, "Identifier")) return parent.label.name;
|
|
5009
4588
|
return null;
|
|
5010
4589
|
};
|
|
5011
|
-
const loopBodyHasIntentionallySequentialAwait = (block
|
|
4590
|
+
const loopBodyHasIntentionallySequentialAwait = (block) => {
|
|
5012
4591
|
let foundIntentional = false;
|
|
5013
4592
|
walkAst(block, (child) => {
|
|
5014
4593
|
if (foundIntentional) return false;
|
|
5015
4594
|
if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
|
|
5016
|
-
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child
|
|
4595
|
+
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child)) {
|
|
5017
4596
|
foundIntentional = true;
|
|
5018
4597
|
return false;
|
|
5019
4598
|
}
|
|
@@ -5118,7 +4697,7 @@ const asyncAwaitInLoop = defineRule({
|
|
|
5118
4697
|
const inspectLoop = (loopNode, label) => {
|
|
5119
4698
|
const loopBody = loopNode.body;
|
|
5120
4699
|
if (!loopBody) return;
|
|
5121
|
-
if (loopBodyHasIntentionallySequentialAwait(loopBody
|
|
4700
|
+
if (loopBodyHasIntentionallySequentialAwait(loopBody)) return;
|
|
5122
4701
|
if ((isNodeOfType(loopNode, "WhileStatement") || isNodeOfType(loopNode, "DoWhileStatement")) && isLoopTestDependentOnBodyState(loopNode.test, loopBody)) return;
|
|
5123
4702
|
if (hasLoopCarriedDependency(loopBody)) return;
|
|
5124
4703
|
if (loopBodyHasAwaitDependentEarlyExit(loopBody, getLoopLabelName(loopNode))) return;
|
|
@@ -5378,7 +4957,7 @@ const guardConsequentPerformsSideEffects = (consequent) => {
|
|
|
5378
4957
|
});
|
|
5379
4958
|
return performsSideEffects;
|
|
5380
4959
|
};
|
|
5381
|
-
const findEnclosingFunction = (node) => {
|
|
4960
|
+
const findEnclosingFunction$1 = (node) => {
|
|
5382
4961
|
let ancestor = node.parent;
|
|
5383
4962
|
while (ancestor) {
|
|
5384
4963
|
if (isFunctionLike$2(ancestor)) return ancestor;
|
|
@@ -5391,7 +4970,7 @@ const guardTestReadsReassignedLocal = (test, guardStatement) => {
|
|
|
5391
4970
|
const testIdentifierNames = /* @__PURE__ */ new Set();
|
|
5392
4971
|
collectReferenceIdentifierNames(test, testIdentifierNames);
|
|
5393
4972
|
if (testIdentifierNames.size === 0) return false;
|
|
5394
|
-
const enclosingFunction = findEnclosingFunction(guardStatement);
|
|
4973
|
+
const enclosingFunction = findEnclosingFunction$1(guardStatement);
|
|
5395
4974
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction) || !enclosingFunction.body) return false;
|
|
5396
4975
|
let readsReassignedLocal = false;
|
|
5397
4976
|
walkAst(enclosingFunction.body, (child) => {
|
|
@@ -5562,36 +5141,19 @@ const isIntentionalSequencingAwait = (awaitedCall) => {
|
|
|
5562
5141
|
return getCalleeIdentifierTrail(awaitedCall).some((name) => INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(name));
|
|
5563
5142
|
};
|
|
5564
5143
|
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
|
-
};
|
|
5574
5144
|
const isNonCallAwait = (statement) => {
|
|
5575
5145
|
const awaitedExpression = getAwaitedCall(statement);
|
|
5576
5146
|
if (!awaitedExpression) return false;
|
|
5577
5147
|
const stripped = stripParenExpression(awaitedExpression);
|
|
5578
5148
|
return !isNodeOfType(stripped, "CallExpression") && !isNodeOfType(stripped, "NewExpression") && !isNodeOfType(stripped, "ImportExpression");
|
|
5579
5149
|
};
|
|
5580
|
-
const sequenceContainsSerializationSignal = (statements
|
|
5581
|
-
let bareAwaitFunction = null;
|
|
5150
|
+
const sequenceContainsSerializationSignal = (statements) => {
|
|
5582
5151
|
for (const statement of statements) {
|
|
5152
|
+
if (isBareExpressionAwait(statement)) return true;
|
|
5583
5153
|
if (isNonCallAwait(statement)) return true;
|
|
5584
5154
|
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
|
-
}
|
|
5593
5155
|
if (isOrderedUiFlowAwait(awaitedCall)) return true;
|
|
5594
|
-
if (isIntentionalSequencingAwait(awaitedCall)
|
|
5156
|
+
if (isIntentionalSequencingAwait(awaitedCall)) return true;
|
|
5595
5157
|
}
|
|
5596
5158
|
return false;
|
|
5597
5159
|
};
|
|
@@ -5649,7 +5211,7 @@ const asyncParallel = defineRule({
|
|
|
5649
5211
|
const consecutiveAwaitStatements = [];
|
|
5650
5212
|
const flushConsecutiveAwaits = () => {
|
|
5651
5213
|
if (consecutiveAwaitStatements.length >= 3) {
|
|
5652
|
-
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements
|
|
5214
|
+
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements)) reportIfIndependent(consecutiveAwaitStatements, context);
|
|
5653
5215
|
}
|
|
5654
5216
|
consecutiveAwaitStatements.length = 0;
|
|
5655
5217
|
};
|
|
@@ -5662,7 +5224,7 @@ const asyncParallel = defineRule({
|
|
|
5662
5224
|
});
|
|
5663
5225
|
//#endregion
|
|
5664
5226
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
5665
|
-
const MESSAGE$
|
|
5227
|
+
const MESSAGE$59 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
|
|
5666
5228
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
5667
5229
|
const STORAGE_GLOBALS = new Set([
|
|
5668
5230
|
"window",
|
|
@@ -5727,7 +5289,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5727
5289
|
if (keyString === null || !isAuthCredentialKey(keyString)) return;
|
|
5728
5290
|
context.report({
|
|
5729
5291
|
node,
|
|
5730
|
-
message: MESSAGE$
|
|
5292
|
+
message: MESSAGE$59
|
|
5731
5293
|
});
|
|
5732
5294
|
},
|
|
5733
5295
|
AssignmentExpression(node) {
|
|
@@ -5738,7 +5300,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5738
5300
|
if (!propertyName || !isAuthCredentialKey(propertyName)) return;
|
|
5739
5301
|
context.report({
|
|
5740
5302
|
node: target,
|
|
5741
|
-
message: MESSAGE$
|
|
5303
|
+
message: MESSAGE$59
|
|
5742
5304
|
});
|
|
5743
5305
|
}
|
|
5744
5306
|
}))
|
|
@@ -5877,7 +5439,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
|
|
|
5877
5439
|
const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
|
|
5878
5440
|
const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
|
|
5879
5441
|
const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
|
|
5880
|
-
const MESSAGE$
|
|
5442
|
+
const MESSAGE$58 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
|
|
5881
5443
|
const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
|
|
5882
5444
|
const scanWorkflowContent = (content) => {
|
|
5883
5445
|
const lines = content.split("\n");
|
|
@@ -5922,7 +5484,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5922
5484
|
const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
|
|
5923
5485
|
const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
|
|
5924
5486
|
return [{
|
|
5925
|
-
message: MESSAGE$
|
|
5487
|
+
message: MESSAGE$58,
|
|
5926
5488
|
line: step.startLineIndex + installLineOffset + 1,
|
|
5927
5489
|
column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
|
|
5928
5490
|
}];
|
|
@@ -5932,7 +5494,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5932
5494
|
const scanNonWorkflowConfig = scanByPattern({
|
|
5933
5495
|
shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
|
|
5934
5496
|
pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
|
|
5935
|
-
message: MESSAGE$
|
|
5497
|
+
message: MESSAGE$58
|
|
5936
5498
|
});
|
|
5937
5499
|
const scan = (file) => {
|
|
5938
5500
|
if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
|
|
@@ -6427,6 +5989,21 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
6427
5989
|
}
|
|
6428
5990
|
});
|
|
6429
5991
|
//#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
|
|
6430
6007
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
6431
6008
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
6432
6009
|
if (!a || !b) return a === b;
|
|
@@ -6684,7 +6261,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
6684
6261
|
};
|
|
6685
6262
|
//#endregion
|
|
6686
6263
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
6687
|
-
const MESSAGE$
|
|
6264
|
+
const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
6688
6265
|
const KEY_HANDLERS = [
|
|
6689
6266
|
"onKeyUp",
|
|
6690
6267
|
"onKeyDown",
|
|
@@ -6895,7 +6472,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6895
6472
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
|
|
6896
6473
|
context.report({
|
|
6897
6474
|
node: node.name,
|
|
6898
|
-
message: MESSAGE$
|
|
6475
|
+
message: MESSAGE$57
|
|
6899
6476
|
});
|
|
6900
6477
|
} };
|
|
6901
6478
|
}
|
|
@@ -7110,6 +6687,15 @@ const getDirectConstInitializer = (symbol) => {
|
|
|
7110
6687
|
return symbol.initializer;
|
|
7111
6688
|
};
|
|
7112
6689
|
//#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
|
|
7113
6699
|
//#region src/plugin/utils/get-range-start.ts
|
|
7114
6700
|
const getRangeStart = (node) => {
|
|
7115
6701
|
const rangeStart = node.range?.[0];
|
|
@@ -7505,6 +7091,16 @@ const collectFunctionReturnStatements = (functionNode) => {
|
|
|
7505
7091
|
return returnStatements;
|
|
7506
7092
|
};
|
|
7507
7093
|
//#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
|
|
7508
7104
|
//#region src/plugin/utils/statement-always-exits.ts
|
|
7509
7105
|
const statementAlwaysExits = (statement) => {
|
|
7510
7106
|
if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
|
|
@@ -7550,8 +7146,8 @@ const applyDefinitions = (incomingDefinitions, definitions) => {
|
|
|
7550
7146
|
const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow) => {
|
|
7551
7147
|
if (!REASSIGNABLE_BINDING_KINDS.has(symbol.kind)) return symbol.initializer ? [symbol.initializer] : [];
|
|
7552
7148
|
if (!controlFlow) return [];
|
|
7553
|
-
const referenceFunction = findEnclosingFunction
|
|
7554
|
-
if (findEnclosingFunction
|
|
7149
|
+
const referenceFunction = findEnclosingFunction(referenceNode);
|
|
7150
|
+
if (findEnclosingFunction(symbol.bindingIdentifier) !== referenceFunction) return [];
|
|
7555
7151
|
if (!referenceFunction) return [];
|
|
7556
7152
|
const functionControlFlow = controlFlow.cfgFor(referenceFunction);
|
|
7557
7153
|
if (!functionControlFlow) return [];
|
|
@@ -7575,7 +7171,7 @@ const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow)
|
|
|
7575
7171
|
if (symbol.initializer) addDefinition(symbol.initializer, symbol.bindingIdentifier, bindingPosition);
|
|
7576
7172
|
for (const reference of symbol.references) {
|
|
7577
7173
|
const writePosition = getRangeStart(reference.identifier);
|
|
7578
|
-
if (reference.flag === "read" || findEnclosingFunction
|
|
7174
|
+
if (reference.flag === "read" || findEnclosingFunction(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
|
|
7579
7175
|
const assignedExpression = getAssignedExpressionForWrite(reference.identifier);
|
|
7580
7176
|
if (!assignedExpression) continue;
|
|
7581
7177
|
addDefinition(assignedExpression, reference.identifier, writePosition);
|
|
@@ -9129,7 +8725,7 @@ const getClassNameLiteral = (classAttribute) => {
|
|
|
9129
8725
|
};
|
|
9130
8726
|
//#endregion
|
|
9131
8727
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
9132
|
-
const MESSAGE$
|
|
8728
|
+
const MESSAGE$56 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
9133
8729
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
9134
8730
|
"td",
|
|
9135
8731
|
"th",
|
|
@@ -9247,7 +8843,6 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
|
|
|
9247
8843
|
const LABEL_ELEMENT = "label";
|
|
9248
8844
|
const LABEL_COMPONENT_NAME = "Label";
|
|
9249
8845
|
const POLYMORPHIC_COMPONENT_PROP = "component";
|
|
9250
|
-
const TITLE_ATTRIBUTE = "title";
|
|
9251
8846
|
const WRAPPER_LABEL_PROP = "label";
|
|
9252
8847
|
const SELECT_ELEMENT = "select";
|
|
9253
8848
|
const DEFAULT_DEPTH = 5;
|
|
@@ -9293,96 +8888,6 @@ const hasNonEmptyPropValue = (attribute) => {
|
|
|
9293
8888
|
}
|
|
9294
8889
|
return true;
|
|
9295
8890
|
};
|
|
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
|
-
};
|
|
9386
8891
|
const toAttributeMatchKey = (kind, value) => {
|
|
9387
8892
|
const trimmedValue = value.trim();
|
|
9388
8893
|
return trimmedValue.length > 0 ? `${kind}:${trimmedValue}` : null;
|
|
@@ -9639,7 +9144,6 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9639
9144
|
if (typeValue === "submit" || typeValue === "reset") return;
|
|
9640
9145
|
if (typeValue === "button" && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "value"))) return;
|
|
9641
9146
|
}
|
|
9642
|
-
if (isDomElement && hasNonEmptyNativeTitle(getLastJsxPropIgnoreCase(opening.attributes, TITLE_ATTRIBUTE), context.scopes)) return;
|
|
9643
9147
|
if (supportsPlaceholderNameFallback(tagName, opening) && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "placeholder"))) return;
|
|
9644
9148
|
if (hasLabellingProp(opening.attributes, settings.labelAttributes)) return;
|
|
9645
9149
|
if (isInsideJsxAttribute(node)) return;
|
|
@@ -9659,7 +9163,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9659
9163
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
9660
9164
|
context.report({
|
|
9661
9165
|
node: candidate.opening,
|
|
9662
|
-
message: MESSAGE$
|
|
9166
|
+
message: MESSAGE$56
|
|
9663
9167
|
});
|
|
9664
9168
|
}
|
|
9665
9169
|
}
|
|
@@ -10420,7 +9924,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
10420
9924
|
});
|
|
10421
9925
|
//#endregion
|
|
10422
9926
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
10423
|
-
const MESSAGE$
|
|
9927
|
+
const MESSAGE$55 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
10424
9928
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
10425
9929
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
10426
9930
|
"aria-label",
|
|
@@ -10445,7 +9949,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
10445
9949
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
10446
9950
|
context.report({
|
|
10447
9951
|
node: node.name,
|
|
10448
|
-
message: MESSAGE$
|
|
9952
|
+
message: MESSAGE$55
|
|
10449
9953
|
});
|
|
10450
9954
|
} };
|
|
10451
9955
|
}
|
|
@@ -10485,7 +9989,7 @@ const isEs6Component = (node) => {
|
|
|
10485
9989
|
};
|
|
10486
9990
|
//#endregion
|
|
10487
9991
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
10488
|
-
const MESSAGE$
|
|
9992
|
+
const MESSAGE$54 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
10489
9993
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
10490
9994
|
"observer",
|
|
10491
9995
|
"lazy",
|
|
@@ -10678,7 +10182,7 @@ const displayName = defineRule({
|
|
|
10678
10182
|
const reportAt = (node) => {
|
|
10679
10183
|
context.report({
|
|
10680
10184
|
node,
|
|
10681
|
-
message: MESSAGE$
|
|
10185
|
+
message: MESSAGE$54
|
|
10682
10186
|
});
|
|
10683
10187
|
};
|
|
10684
10188
|
return {
|
|
@@ -11499,14 +11003,6 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
|
|
|
11499
11003
|
"catch",
|
|
11500
11004
|
"finally"
|
|
11501
11005
|
]);
|
|
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
|
-
};
|
|
11510
11006
|
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
11511
11007
|
const invokedFunctions = new Set([effectCallback]);
|
|
11512
11008
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
@@ -11518,6 +11014,7 @@ const collectEffectInvokedFunctions = (effectCallback) => {
|
|
|
11518
11014
|
invokedFunctions.add(strippedCandidate);
|
|
11519
11015
|
pendingFunctions.push(strippedCandidate);
|
|
11520
11016
|
};
|
|
11017
|
+
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11521
11018
|
while (pendingFunctions.length > 0) {
|
|
11522
11019
|
const currentFunction = pendingFunctions.pop();
|
|
11523
11020
|
if (!currentFunction) break;
|
|
@@ -11589,7 +11086,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
|
|
|
11589
11086
|
//#endregion
|
|
11590
11087
|
//#region src/plugin/utils/enclosing-component-or-hook-name.ts
|
|
11591
11088
|
const enclosingComponentOrHookName = (node) => {
|
|
11592
|
-
const functionNode = findEnclosingFunction
|
|
11089
|
+
const functionNode = findEnclosingFunction(node);
|
|
11593
11090
|
return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
|
|
11594
11091
|
};
|
|
11595
11092
|
//#endregion
|
|
@@ -11646,11 +11143,11 @@ const executesDuringRender = (functionNode, scopes) => {
|
|
|
11646
11143
|
//#endregion
|
|
11647
11144
|
//#region src/plugin/utils/find-render-phase-component-or-hook.ts
|
|
11648
11145
|
const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
11649
|
-
let functionNode = findEnclosingFunction
|
|
11146
|
+
let functionNode = findEnclosingFunction(node);
|
|
11650
11147
|
while (functionNode) {
|
|
11651
11148
|
if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
|
|
11652
11149
|
if (!executesDuringRender(functionNode, scopes)) return null;
|
|
11653
|
-
functionNode = findEnclosingFunction
|
|
11150
|
+
functionNode = findEnclosingFunction(functionNode);
|
|
11654
11151
|
}
|
|
11655
11152
|
return null;
|
|
11656
11153
|
};
|
|
@@ -11725,50 +11222,6 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
|
|
|
11725
11222
|
return true;
|
|
11726
11223
|
};
|
|
11727
11224
|
//#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
|
|
11772
11225
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
11773
11226
|
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
11774
11227
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
@@ -11897,15 +11350,36 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
11897
11350
|
});
|
|
11898
11351
|
return usages.filter((usage) => isNodeReachableWithinFunction(usage.node, context));
|
|
11899
11352
|
};
|
|
11353
|
+
const isNodeReachableWithinFunction = (node, context) => {
|
|
11354
|
+
const owner = context.cfg.enclosingFunction(node);
|
|
11355
|
+
if (!owner) return true;
|
|
11356
|
+
const functionCfg = context.cfg.cfgFor(owner);
|
|
11357
|
+
if (!functionCfg) return true;
|
|
11358
|
+
const targetBlock = functionCfg.blockOf(node);
|
|
11359
|
+
if (!targetBlock) return true;
|
|
11360
|
+
const visitedBlocks = new Set([functionCfg.entry]);
|
|
11361
|
+
const pendingBlocks = [functionCfg.entry];
|
|
11362
|
+
while (pendingBlocks.length > 0) {
|
|
11363
|
+
const currentBlock = pendingBlocks.pop();
|
|
11364
|
+
if (!currentBlock) break;
|
|
11365
|
+
if (currentBlock === targetBlock) return true;
|
|
11366
|
+
for (const edge of currentBlock.successors) {
|
|
11367
|
+
if (visitedBlocks.has(edge.to)) continue;
|
|
11368
|
+
visitedBlocks.add(edge.to);
|
|
11369
|
+
pendingBlocks.push(edge.to);
|
|
11370
|
+
}
|
|
11371
|
+
}
|
|
11372
|
+
return false;
|
|
11373
|
+
};
|
|
11900
11374
|
const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
|
|
11901
11375
|
let pathAnchor = usageNode;
|
|
11902
|
-
let pathOwner = findEnclosingFunction
|
|
11376
|
+
let pathOwner = findEnclosingFunction(pathAnchor);
|
|
11903
11377
|
while (pathOwner && isSynchronousIteratorCallback(pathOwner)) {
|
|
11904
11378
|
if (matchingNodes.length > 0 && matchingNodes.every((matchingNode) => context.cfg.enclosingFunction(matchingNode) === pathOwner)) break;
|
|
11905
11379
|
const iteratorCall = pathOwner.parent;
|
|
11906
11380
|
if (!isNodeOfType(iteratorCall, "CallExpression")) break;
|
|
11907
11381
|
pathAnchor = iteratorCall;
|
|
11908
|
-
pathOwner = findEnclosingFunction
|
|
11382
|
+
pathOwner = findEnclosingFunction(pathAnchor);
|
|
11909
11383
|
}
|
|
11910
11384
|
const owner = context.cfg.enclosingFunction(pathAnchor);
|
|
11911
11385
|
if (!owner) return false;
|
|
@@ -12134,7 +11608,7 @@ const findCollectionMappingCall = (callbackNode) => {
|
|
|
12134
11608
|
return callee.property.name === "map" && callNode.arguments?.[0] === callbackNode ? callNode : null;
|
|
12135
11609
|
};
|
|
12136
11610
|
const findMappedResourceCollectionKey = (resourceNode, context) => {
|
|
12137
|
-
const callbackNode = findEnclosingFunction
|
|
11611
|
+
const callbackNode = findEnclosingFunction(resourceNode);
|
|
12138
11612
|
if (!callbackNode || !isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression")) return null;
|
|
12139
11613
|
const mappingCall = findCollectionMappingCall(callbackNode);
|
|
12140
11614
|
if (!mappingCall) return null;
|
|
@@ -12245,10 +11719,10 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
12245
11719
|
});
|
|
12246
11720
|
if (invocationCalls.length !== 1) return null;
|
|
12247
11721
|
const invocationCall = invocationCalls[0];
|
|
12248
|
-
return findEnclosingFunction
|
|
11722
|
+
return findEnclosingFunction(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
12249
11723
|
};
|
|
12250
11724
|
const resolveCleanupPathAnchor = (usageNode, effectCallback, context) => {
|
|
12251
|
-
const usageFunction = findEnclosingFunction
|
|
11725
|
+
const usageFunction = findEnclosingFunction(usageNode);
|
|
12252
11726
|
if (!usageFunction || usageFunction === effectCallback) return usageNode;
|
|
12253
11727
|
return findSingleDirectInvocation(usageFunction, effectCallback, context) ?? usageNode;
|
|
12254
11728
|
};
|
|
@@ -12263,7 +11737,7 @@ const resolveSingleAssignedCleanupFunction = (expression, usage, context) => {
|
|
|
12263
11737
|
const assignmentReference = assignmentReferences[0];
|
|
12264
11738
|
const assignmentTarget = findTransparentExpressionRoot(assignmentReference.identifier);
|
|
12265
11739
|
const assignmentNode = assignmentTarget.parent;
|
|
12266
|
-
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction
|
|
11740
|
+
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction(assignmentNode) !== findEnclosingFunction(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
|
|
12267
11741
|
const assignedValue = stripParenExpression(assignmentNode.right);
|
|
12268
11742
|
return isFunctionLike$2(assignedValue) ? assignedValue : null;
|
|
12269
11743
|
};
|
|
@@ -12352,12 +11826,12 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
12352
11826
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
12353
11827
|
};
|
|
12354
11828
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
12355
|
-
const componentFunction = findEnclosingFunction
|
|
11829
|
+
const componentFunction = findEnclosingFunction(callback);
|
|
12356
11830
|
if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
|
|
12357
11831
|
let didFindUnmountCleanup = false;
|
|
12358
11832
|
walkAst(componentFunction.body, (child) => {
|
|
12359
11833
|
if (didFindUnmountCleanup) return false;
|
|
12360
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction
|
|
11834
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
|
|
12361
11835
|
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
12362
11836
|
const dependencyList = child.arguments?.[1];
|
|
12363
11837
|
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
@@ -12370,98 +11844,10 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
12370
11844
|
return didFindUnmountCleanup;
|
|
12371
11845
|
};
|
|
12372
11846
|
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
|
-
};
|
|
12461
11847
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
12462
11848
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
12463
11849
|
if (callback.async) return false;
|
|
12464
|
-
if (usage.kind === "subscribe" && findEnclosingFunction
|
|
11850
|
+
if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
12465
11851
|
if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
|
|
12466
11852
|
const matchingCleanupReturns = [];
|
|
12467
11853
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
@@ -12497,7 +11883,6 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
12497
11883
|
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
|
|
12498
11884
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
12499
11885
|
});
|
|
12500
|
-
if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
|
|
12501
11886
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
12502
11887
|
};
|
|
12503
11888
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
@@ -12630,7 +12015,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
|
12630
12015
|
parentNode = currentNode.parent;
|
|
12631
12016
|
}
|
|
12632
12017
|
if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
|
|
12633
|
-
const effectCallback = findEnclosingFunction
|
|
12018
|
+
const effectCallback = findEnclosingFunction(parentNode);
|
|
12634
12019
|
const effectCall = effectCallback?.parent;
|
|
12635
12020
|
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
12636
12021
|
};
|
|
@@ -12640,13 +12025,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
12640
12025
|
if (!bindingIdentifier) return false;
|
|
12641
12026
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
12642
12027
|
if (!symbol) return false;
|
|
12643
|
-
return symbol.references.some((reference) => findEnclosingFunction
|
|
12028
|
+
return symbol.references.some((reference) => findEnclosingFunction(reference.identifier) !== functionNode);
|
|
12644
12029
|
};
|
|
12645
12030
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
12646
12031
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
12647
|
-
const releaseFunction = findEnclosingFunction
|
|
12032
|
+
const releaseFunction = findEnclosingFunction(releaseNode);
|
|
12648
12033
|
if (!releaseFunction) return true;
|
|
12649
|
-
if (releaseFunction === findEnclosingFunction
|
|
12034
|
+
if (releaseFunction === findEnclosingFunction(usage.node)) return true;
|
|
12650
12035
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
12651
12036
|
};
|
|
12652
12037
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -12829,10 +12214,10 @@ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction
|
|
|
12829
12214
|
}
|
|
12830
12215
|
if (assignedKey !== storage.refCurrentKey) return;
|
|
12831
12216
|
const assignedValue = stripParenExpression(child.right);
|
|
12832
|
-
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction
|
|
12217
|
+
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction(child) === cleanupFunction) return;
|
|
12833
12218
|
const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
|
|
12834
12219
|
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);
|
|
12835
|
-
if (findEnclosingFunction
|
|
12220
|
+
if (findEnclosingFunction(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12836
12221
|
hasUnsafeRefWrite = true;
|
|
12837
12222
|
return false;
|
|
12838
12223
|
}
|
|
@@ -12853,12 +12238,12 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
12853
12238
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
12854
12239
|
};
|
|
12855
12240
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
12856
|
-
const componentFunction = findEnclosingFunction
|
|
12241
|
+
const componentFunction = findEnclosingFunction(retainedFunction);
|
|
12857
12242
|
if (!componentFunction || !isFunctionLike$2(componentFunction)) return false;
|
|
12858
12243
|
let didFindCleanupEffect = false;
|
|
12859
12244
|
walkAst(componentFunction.body, (child) => {
|
|
12860
12245
|
if (didFindCleanupEffect) return false;
|
|
12861
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction
|
|
12246
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
12862
12247
|
const effectStatement = findTransparentExpressionRoot(child).parent;
|
|
12863
12248
|
if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
|
|
12864
12249
|
const effectCallback = getEffectCallback(child);
|
|
@@ -13712,202 +13097,6 @@ const resolveExhaustiveDepsSettings = (settings) => {
|
|
|
13712
13097
|
};
|
|
13713
13098
|
};
|
|
13714
13099
|
//#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
|
|
13911
13100
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-suppression.ts
|
|
13912
13101
|
const DISABLE_COMMENT_RULE_NAME_PATTERN$1 = /(?:^|[\s,/])exhaustive-deps(?:$|[\s,:])/;
|
|
13913
13102
|
const DISABLE_NEXT_LINE_PATTERN$1 = /\b(?:eslint|oxlint)-disable-next-line\b([^\n]*)/;
|
|
@@ -13979,6 +13168,25 @@ const isExhaustiveDepsSuppressedAt = (filename, nodeStartOffset) => {
|
|
|
13979
13168
|
return index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf16NewlineOffsets)) || index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf8NewlineOffsets));
|
|
13980
13169
|
};
|
|
13981
13170
|
//#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
|
|
13982
13190
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
|
|
13983
13191
|
/**
|
|
13984
13192
|
* Symbol-stability helpers consumed by the `exhaustive-deps` rule.
|
|
@@ -14159,7 +13367,6 @@ const EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS = new Set([
|
|
|
14159
13367
|
"useLayoutEffect",
|
|
14160
13368
|
"useInsertionEffect"
|
|
14161
13369
|
]);
|
|
14162
|
-
const SOLE_WRITER_GUARD_HOOKS = new Set(["useEffect", "useLayoutEffect"]);
|
|
14163
13370
|
const buildAdditionalHooksRegex = (additional) => {
|
|
14164
13371
|
if (!additional) return null;
|
|
14165
13372
|
try {
|
|
@@ -14296,7 +13503,7 @@ const stringifyMemberChain = (node) => {
|
|
|
14296
13503
|
}
|
|
14297
13504
|
return null;
|
|
14298
13505
|
};
|
|
14299
|
-
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys
|
|
13506
|
+
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
14300
13507
|
const keys = /* @__PURE__ */ new Set();
|
|
14301
13508
|
const stableCapturedNames = /* @__PURE__ */ new Set();
|
|
14302
13509
|
const moduleScopeCapturedNames = /* @__PURE__ */ new Set();
|
|
@@ -14307,10 +13514,6 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
|
|
|
14307
13514
|
const symbol = reference.resolvedSymbol;
|
|
14308
13515
|
if (!symbol) continue;
|
|
14309
13516
|
if (isRecursiveInitializerCapture(symbol, callback)) continue;
|
|
14310
|
-
if (allowSoleWriterEffectGuards && isSoleWriterEffectGuardCapture(symbol, callback, scopes)) {
|
|
14311
|
-
stableCapturedNames.add(symbol.name);
|
|
14312
|
-
continue;
|
|
14313
|
-
}
|
|
14314
13517
|
if (symbolHasStableValue(symbol, scopes)) {
|
|
14315
13518
|
stableCapturedNames.add(symbol.name);
|
|
14316
13519
|
continue;
|
|
@@ -14983,7 +14186,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
14983
14186
|
if (isNodeOfType(stripped, "Identifier")) declaredExactBindingKeys.add(key);
|
|
14984
14187
|
declaredKeyToReportNode.set(key, elementNode);
|
|
14985
14188
|
}
|
|
14986
|
-
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys
|
|
14189
|
+
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys);
|
|
14987
14190
|
for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
|
|
14988
14191
|
addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
|
|
14989
14192
|
const missingCaptureKeys = [];
|
|
@@ -15423,7 +14626,7 @@ const forbidElements = defineRule({
|
|
|
15423
14626
|
});
|
|
15424
14627
|
//#endregion
|
|
15425
14628
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
15426
|
-
const MESSAGE$
|
|
14629
|
+
const MESSAGE$53 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
15427
14630
|
const forwardRefUsesRef = defineRule({
|
|
15428
14631
|
id: "forward-ref-uses-ref",
|
|
15429
14632
|
title: "forwardRef without ref parameter",
|
|
@@ -15446,7 +14649,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
15446
14649
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
15447
14650
|
context.report({
|
|
15448
14651
|
node: inner,
|
|
15449
|
-
message: MESSAGE$
|
|
14652
|
+
message: MESSAGE$53
|
|
15450
14653
|
});
|
|
15451
14654
|
} })
|
|
15452
14655
|
});
|
|
@@ -15487,7 +14690,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
15487
14690
|
});
|
|
15488
14691
|
//#endregion
|
|
15489
14692
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
15490
|
-
const MESSAGE$
|
|
14693
|
+
const MESSAGE$52 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
15491
14694
|
const DEFAULT_HEADING_TAGS = [
|
|
15492
14695
|
"h1",
|
|
15493
14696
|
"h2",
|
|
@@ -15521,7 +14724,7 @@ const headingHasContent = defineRule({
|
|
|
15521
14724
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
15522
14725
|
context.report({
|
|
15523
14726
|
node,
|
|
15524
|
-
message: MESSAGE$
|
|
14727
|
+
message: MESSAGE$52
|
|
15525
14728
|
});
|
|
15526
14729
|
} };
|
|
15527
14730
|
}
|
|
@@ -15685,7 +14888,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
15685
14888
|
});
|
|
15686
14889
|
//#endregion
|
|
15687
14890
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
15688
|
-
const MESSAGE$
|
|
14891
|
+
const MESSAGE$51 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
15689
14892
|
const resolveSettings$39 = (settings) => {
|
|
15690
14893
|
const reactDoctor = settings?.["react-doctor"];
|
|
15691
14894
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -15732,13 +14935,13 @@ const htmlHasLang = defineRule({
|
|
|
15732
14935
|
if (!lang) {
|
|
15733
14936
|
context.report({
|
|
15734
14937
|
node: node.name,
|
|
15735
|
-
message: MESSAGE$
|
|
14938
|
+
message: MESSAGE$51
|
|
15736
14939
|
});
|
|
15737
14940
|
return;
|
|
15738
14941
|
}
|
|
15739
14942
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
15740
14943
|
node: lang,
|
|
15741
|
-
message: MESSAGE$
|
|
14944
|
+
message: MESSAGE$51
|
|
15742
14945
|
});
|
|
15743
14946
|
} };
|
|
15744
14947
|
}
|
|
@@ -15978,7 +15181,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
15978
15181
|
});
|
|
15979
15182
|
//#endregion
|
|
15980
15183
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
15981
|
-
const MESSAGE$
|
|
15184
|
+
const MESSAGE$50 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
15982
15185
|
const evaluateTitleValue = (value) => {
|
|
15983
15186
|
if (!value) return "missing";
|
|
15984
15187
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -16018,14 +15221,14 @@ const iframeHasTitle = defineRule({
|
|
|
16018
15221
|
if (!titleAttr) {
|
|
16019
15222
|
if (hasSpread || tag === "iframe") context.report({
|
|
16020
15223
|
node: node.name,
|
|
16021
|
-
message: MESSAGE$
|
|
15224
|
+
message: MESSAGE$50
|
|
16022
15225
|
});
|
|
16023
15226
|
return;
|
|
16024
15227
|
}
|
|
16025
15228
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
16026
15229
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
16027
15230
|
node: titleAttr,
|
|
16028
|
-
message: MESSAGE$
|
|
15231
|
+
message: MESSAGE$50
|
|
16029
15232
|
});
|
|
16030
15233
|
} })
|
|
16031
15234
|
});
|
|
@@ -16150,7 +15353,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
16150
15353
|
});
|
|
16151
15354
|
//#endregion
|
|
16152
15355
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
16153
|
-
const MESSAGE$
|
|
15356
|
+
const MESSAGE$49 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
16154
15357
|
const DEFAULT_COMPONENTS = ["img"];
|
|
16155
15358
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
16156
15359
|
"image",
|
|
@@ -16215,7 +15418,7 @@ const imgRedundantAlt = defineRule({
|
|
|
16215
15418
|
if (!altAttribute) return;
|
|
16216
15419
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
16217
15420
|
node: altAttribute,
|
|
16218
|
-
message: MESSAGE$
|
|
15421
|
+
message: MESSAGE$49
|
|
16219
15422
|
});
|
|
16220
15423
|
} };
|
|
16221
15424
|
}
|
|
@@ -16797,14 +16000,14 @@ const isBindingInvokedOnRenderPath = (root, bindingName) => {
|
|
|
16797
16000
|
if (didFindRenderPathInvocation) return false;
|
|
16798
16001
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
16799
16002
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== bindingName) return;
|
|
16800
|
-
let enclosingFunction = findEnclosingFunction
|
|
16003
|
+
let enclosingFunction = findEnclosingFunction(node);
|
|
16801
16004
|
while (enclosingFunction) {
|
|
16802
16005
|
if (isDeferredCallbackPosition$1(enclosingFunction) || getHandlerNamedBindingName(enclosingFunction)) return;
|
|
16803
16006
|
if (containingFunctionIsComponentOrHook(enclosingFunction)) {
|
|
16804
16007
|
didFindRenderPathInvocation = true;
|
|
16805
16008
|
return;
|
|
16806
16009
|
}
|
|
16807
|
-
enclosingFunction = findEnclosingFunction
|
|
16010
|
+
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
16808
16011
|
}
|
|
16809
16012
|
});
|
|
16810
16013
|
return didFindRenderPathInvocation;
|
|
@@ -16839,7 +16042,7 @@ const jotaiSelectAtomInRenderBody = defineRule({
|
|
|
16839
16042
|
};
|
|
16840
16043
|
return { CallExpression(node) {
|
|
16841
16044
|
if (!isImportedSelectAtom(node)) return;
|
|
16842
|
-
const nearestFunctionLike = findEnclosingFunction
|
|
16045
|
+
const nearestFunctionLike = findEnclosingFunction(node);
|
|
16843
16046
|
if (!nearestFunctionLike) return;
|
|
16844
16047
|
if (isDeferredCallback(nearestFunctionLike)) return;
|
|
16845
16048
|
if (isBindingUsedAsHandler(nearestFunctionLike)) return;
|
|
@@ -17836,7 +17039,7 @@ const isPersistentCacheSetWrite = (callExpression, newExpression, enclosingFunct
|
|
|
17836
17039
|
return !isAstDescendant(receiverBinding.scopeOwner, enclosingFunction);
|
|
17837
17040
|
};
|
|
17838
17041
|
const isInsideCacheMemo = (node) => {
|
|
17839
|
-
const enclosingFunction = findEnclosingFunction
|
|
17042
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
17840
17043
|
let child = node;
|
|
17841
17044
|
let cursor = node.parent ?? null;
|
|
17842
17045
|
while (cursor) {
|
|
@@ -17874,7 +17077,7 @@ const getFunctionName = (functionNode) => {
|
|
|
17874
17077
|
return null;
|
|
17875
17078
|
};
|
|
17876
17079
|
const isUncacheableOptionsMergeUtility = (node) => {
|
|
17877
|
-
const enclosingFunction = findEnclosingFunction
|
|
17080
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
17878
17081
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction)) return false;
|
|
17879
17082
|
const functionName = getFunctionName(enclosingFunction);
|
|
17880
17083
|
if (!functionName || isComponentOrHookName(functionName)) return false;
|
|
@@ -17998,7 +17201,7 @@ const GLOBAL_BUILTIN_NAMES = new Set([
|
|
|
17998
17201
|
]);
|
|
17999
17202
|
const STRING_PROTOTYPE_PATH = "String.prototype";
|
|
18000
17203
|
const REGEXP_PROTOTYPE_PATH = "RegExp.prototype";
|
|
18001
|
-
const getStaticStringValue
|
|
17204
|
+
const getStaticStringValue = (argument) => {
|
|
18002
17205
|
if (!argument) return null;
|
|
18003
17206
|
const unwrappedArgument = stripParenExpression(argument);
|
|
18004
17207
|
if (isNodeOfType(unwrappedArgument, "Literal") && typeof unwrappedArgument.value === "string") return unwrappedArgument.value;
|
|
@@ -18006,7 +17209,7 @@ const getStaticStringValue$1 = (argument) => {
|
|
|
18006
17209
|
return null;
|
|
18007
17210
|
};
|
|
18008
17211
|
const getEffectiveRegExpFlags = (patternArgument, flagsArgument) => {
|
|
18009
|
-
if (flagsArgument) return getStaticStringValue
|
|
17212
|
+
if (flagsArgument) return getStaticStringValue(flagsArgument);
|
|
18010
17213
|
if (!patternArgument) return "";
|
|
18011
17214
|
const unwrappedPattern = stripParenExpression(patternArgument);
|
|
18012
17215
|
if (isNodeOfType(unwrappedPattern, "Literal") && unwrappedPattern.value instanceof RegExp) return unwrappedPattern.value.flags;
|
|
@@ -18065,6 +17268,13 @@ const isSafeStatefulReplaceAllSearch = (node, flags, context) => {
|
|
|
18065
17268
|
const callee = stripParenExpression(replaceAllCall.callee);
|
|
18066
17269
|
return isNodeOfType(callee, "MemberExpression") && !callee.computed && !callee.optional && !replaceAllCall.optional && isNodeOfType(callee.property, "Identifier") && callee.property.name === "replaceAll" && isProvenNativeStringReceiver(callee.object, context);
|
|
18067
17270
|
};
|
|
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
|
+
};
|
|
18068
17278
|
const extendGlobalPath = (basePath, propertyName) => {
|
|
18069
17279
|
if (basePath === "global") {
|
|
18070
17280
|
if (propertyName === null || GLOBAL_OBJECT_NAMES.has(propertyName)) return "global";
|
|
@@ -18149,7 +17359,7 @@ const getCallRegExpHazard = (node, context, symbolCache) => {
|
|
|
18149
17359
|
const mutationHazard = targetPath === "global" ? "globalRegExpReplaced" : "replaceAllIntegrityLost";
|
|
18150
17360
|
const guardedPropertyName = targetPath === "global" ? "RegExp" : "replaceAll";
|
|
18151
17361
|
if (isSinglePropertyMutation) {
|
|
18152
|
-
const propertyName = getStaticStringValue
|
|
17362
|
+
const propertyName = getStaticStringValue(node.arguments?.[1]);
|
|
18153
17363
|
return propertyName === null || propertyName === guardedPropertyName ? mutationHazard : "none";
|
|
18154
17364
|
}
|
|
18155
17365
|
if (!isPropertyCollectionMutation) return "none";
|
|
@@ -18178,7 +17388,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
18178
17388
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
18179
17389
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
18180
17390
|
};
|
|
18181
|
-
const MESSAGE$
|
|
17391
|
+
const MESSAGE$48 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
18182
17392
|
const jsHoistRegexp = defineRule({
|
|
18183
17393
|
id: "js-hoist-regexp",
|
|
18184
17394
|
title: "RegExp built inside a loop",
|
|
@@ -18195,7 +17405,7 @@ const jsHoistRegexp = defineRule({
|
|
|
18195
17405
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
18196
17406
|
context.report({
|
|
18197
17407
|
node,
|
|
18198
|
-
message: MESSAGE$
|
|
17408
|
+
message: MESSAGE$48
|
|
18199
17409
|
});
|
|
18200
17410
|
};
|
|
18201
17411
|
return createLoopAwareVisitors({
|
|
@@ -18354,89 +17564,7 @@ const collectEarlierAndGuardOperands = (node) => {
|
|
|
18354
17564
|
return earlierOperands;
|
|
18355
17565
|
};
|
|
18356
17566
|
//#endregion
|
|
18357
|
-
//#region src/plugin/utils/unwrap-object-integrity-expression.ts
|
|
18358
|
-
const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
|
|
18359
|
-
"freeze",
|
|
18360
|
-
"seal",
|
|
18361
|
-
"preventExtensions"
|
|
18362
|
-
]);
|
|
18363
|
-
const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
|
|
18364
|
-
const getObjectIntegrityMethodName = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
18365
|
-
const expression = stripParenExpression(node);
|
|
18366
|
-
if (!isNodeOfType(expression, "CallExpression")) return null;
|
|
18367
|
-
const callee = stripParenExpression(expression.callee);
|
|
18368
|
-
const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : callee;
|
|
18369
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !scopes.isGlobalReference(receiver) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) return null;
|
|
18370
|
-
return callee.property.name;
|
|
18371
|
-
};
|
|
18372
|
-
const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
18373
|
-
let expression = stripParenExpression(node);
|
|
18374
|
-
while (isNodeOfType(expression, "CallExpression")) {
|
|
18375
|
-
if (!getObjectIntegrityMethodName(expression, scopes, methodNames)) break;
|
|
18376
|
-
const wrappedExpression = expression.arguments[0];
|
|
18377
|
-
if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
|
|
18378
|
-
expression = stripParenExpression(wrappedExpression);
|
|
18379
|
-
}
|
|
18380
|
-
return expression;
|
|
18381
|
-
};
|
|
18382
|
-
//#endregion
|
|
18383
17567
|
//#region src/plugin/rules/js-performance/js-length-check-first.ts
|
|
18384
|
-
const OBJECT_CARDINALITY_PROJECTION_METHOD_NAMES = new Set(["keys", "values"]);
|
|
18385
|
-
const getGlobalObjectProjection = (expression, context) => {
|
|
18386
|
-
const callExpression = stripParenExpression(expression);
|
|
18387
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
18388
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
18389
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
18390
|
-
const receiver = stripParenExpression(callee.object);
|
|
18391
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !context.scopes.isGlobalReference(receiver)) return null;
|
|
18392
|
-
const methodName = getStaticPropertyName(callee);
|
|
18393
|
-
if (!methodName || !OBJECT_CARDINALITY_PROJECTION_METHOD_NAMES.has(methodName)) return null;
|
|
18394
|
-
const callArguments = callExpression.arguments ?? [];
|
|
18395
|
-
if (callArguments.length !== 1 || isNodeOfType(callArguments[0], "SpreadElement")) return null;
|
|
18396
|
-
return {
|
|
18397
|
-
methodName,
|
|
18398
|
-
source: callArguments[0]
|
|
18399
|
-
};
|
|
18400
|
-
};
|
|
18401
|
-
const isPlainDataObjectLiteral = (expression) => {
|
|
18402
|
-
const objectExpression = stripParenExpression(expression);
|
|
18403
|
-
if (!isNodeOfType(objectExpression, "ObjectExpression")) return false;
|
|
18404
|
-
return (objectExpression.properties ?? []).every((property) => {
|
|
18405
|
-
if (!isNodeOfType(property, "Property") || property.kind !== "init" || property.method === true) return false;
|
|
18406
|
-
const propertyValue = stripParenExpression(property.value);
|
|
18407
|
-
return isNodeOfType(propertyValue, "Literal") || isNodeOfType(propertyValue, "TemplateLiteral") && propertyValue.expressions.length === 0;
|
|
18408
|
-
});
|
|
18409
|
-
};
|
|
18410
|
-
const getGlobalObjectFreezeArgument = (expression, context) => {
|
|
18411
|
-
const callExpression = stripParenExpression(expression);
|
|
18412
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
18413
|
-
if (getObjectIntegrityMethodName(callExpression, context.scopes) !== "freeze") return null;
|
|
18414
|
-
const callArguments = callExpression.arguments ?? [];
|
|
18415
|
-
if (callArguments.length !== 1 || isNodeOfType(callArguments[0], "SpreadElement")) return null;
|
|
18416
|
-
return callArguments[0];
|
|
18417
|
-
};
|
|
18418
|
-
const resolveStablePlainObjectRootId = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
18419
|
-
const source = stripParenExpression(expression);
|
|
18420
|
-
if (!isNodeOfType(source, "Identifier")) return null;
|
|
18421
|
-
const symbol = context.scopes.symbolFor(source);
|
|
18422
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
18423
|
-
const directConstInitializer = getDirectConstInitializer(symbol);
|
|
18424
|
-
if (!directConstInitializer) return null;
|
|
18425
|
-
visitedSymbolIds.add(symbol.id);
|
|
18426
|
-
const initializer = stripParenExpression(directConstInitializer);
|
|
18427
|
-
const frozenValue = getGlobalObjectFreezeArgument(initializer, context);
|
|
18428
|
-
if (frozenValue) return isPlainDataObjectLiteral(frozenValue) ? symbol.id : null;
|
|
18429
|
-
return resolveStablePlainObjectRootId(initializer, context, visitedSymbolIds);
|
|
18430
|
-
};
|
|
18431
|
-
const areEqualCardinalityObjectProjections = (receiverArray, indexedArray, context) => {
|
|
18432
|
-
const receiverProjection = getGlobalObjectProjection(receiverArray, context);
|
|
18433
|
-
const indexedProjection = getGlobalObjectProjection(indexedArray, context);
|
|
18434
|
-
if (!receiverProjection || !indexedProjection) return false;
|
|
18435
|
-
if (receiverProjection.methodName === indexedProjection.methodName) return false;
|
|
18436
|
-
const receiverRootId = resolveStablePlainObjectRootId(receiverProjection.source, context);
|
|
18437
|
-
const indexedRootId = resolveStablePlainObjectRootId(indexedProjection.source, context);
|
|
18438
|
-
return receiverRootId !== null && receiverRootId === indexedRootId;
|
|
18439
|
-
};
|
|
18440
17568
|
const findIndexedArrayObject = (callbackBody, indexParameterName) => {
|
|
18441
17569
|
let indexedArrayObject = null;
|
|
18442
17570
|
walkAst(callbackBody, (child) => {
|
|
@@ -18651,7 +17779,6 @@ const jsLengthCheckFirst = defineRule({
|
|
|
18651
17779
|
const resolvedReceiverSource = resolveComparedArraySource(receiverArrayObject, node);
|
|
18652
17780
|
const resolvedIndexedSource = resolveComparedArraySource(indexedArrayObject, node);
|
|
18653
17781
|
if (areExpressionsStructurallyEqual(resolvedReceiverSource, resolvedIndexedSource)) return;
|
|
18654
|
-
if (areEqualCardinalityObjectProjections(receiverArrayObject, indexedArrayObject, context) || areEqualCardinalityObjectProjections(resolvedReceiverSource, resolvedIndexedSource, context)) return;
|
|
18655
17782
|
const comparedArrayPairs = [[receiverArrayObject, indexedArrayObject], [resolvedReceiverSource, resolvedIndexedSource]];
|
|
18656
17783
|
if (collectEarlierAndGuardOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_ANY_COMPARISON_OPERATORS)))) return;
|
|
18657
17784
|
if (collectEarlierOrOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_MISMATCH_OPERATORS)))) return;
|
|
@@ -20898,7 +20025,7 @@ const jsxMaxDepth = defineRule({
|
|
|
20898
20025
|
});
|
|
20899
20026
|
//#endregion
|
|
20900
20027
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
20901
|
-
const MESSAGE$
|
|
20028
|
+
const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
20902
20029
|
const LITERAL_TEXT_TAGS = new Set([
|
|
20903
20030
|
"code",
|
|
20904
20031
|
"pre",
|
|
@@ -20968,7 +20095,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
20968
20095
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
20969
20096
|
context.report({
|
|
20970
20097
|
node,
|
|
20971
|
-
message: MESSAGE$
|
|
20098
|
+
message: MESSAGE$47
|
|
20972
20099
|
});
|
|
20973
20100
|
} })
|
|
20974
20101
|
});
|
|
@@ -20999,7 +20126,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
20999
20126
|
};
|
|
21000
20127
|
//#endregion
|
|
21001
20128
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
21002
|
-
const MESSAGE$
|
|
20129
|
+
const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
21003
20130
|
const CONTEXT_MODULES$1 = [
|
|
21004
20131
|
"react",
|
|
21005
20132
|
"use-context-selector",
|
|
@@ -21097,7 +20224,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
21097
20224
|
if (!isConstructedValue(innerExpression)) continue;
|
|
21098
20225
|
context.report({
|
|
21099
20226
|
node: attribute,
|
|
21100
|
-
message: MESSAGE$
|
|
20227
|
+
message: MESSAGE$46
|
|
21101
20228
|
});
|
|
21102
20229
|
}
|
|
21103
20230
|
}
|
|
@@ -21952,7 +21079,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
21952
21079
|
];
|
|
21953
21080
|
//#endregion
|
|
21954
21081
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
21955
|
-
const MESSAGE$
|
|
21082
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
21956
21083
|
const isDataArrayPropName = (propName) => {
|
|
21957
21084
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
21958
21085
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -22039,7 +21166,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
22039
21166
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
22040
21167
|
context.report({
|
|
22041
21168
|
node,
|
|
22042
|
-
message: MESSAGE$
|
|
21169
|
+
message: MESSAGE$45
|
|
22043
21170
|
});
|
|
22044
21171
|
}
|
|
22045
21172
|
};
|
|
@@ -22297,7 +21424,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
22297
21424
|
]);
|
|
22298
21425
|
//#endregion
|
|
22299
21426
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
22300
|
-
const MESSAGE$
|
|
21427
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
22301
21428
|
const isAccessorPredicateName = (propName) => {
|
|
22302
21429
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
22303
21430
|
if (propName.length <= prefix.length) continue;
|
|
@@ -22503,7 +21630,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
22503
21630
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
22504
21631
|
context.report({
|
|
22505
21632
|
node,
|
|
22506
|
-
message: MESSAGE$
|
|
21633
|
+
message: MESSAGE$44
|
|
22507
21634
|
});
|
|
22508
21635
|
}
|
|
22509
21636
|
};
|
|
@@ -22723,7 +21850,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
22723
21850
|
];
|
|
22724
21851
|
//#endregion
|
|
22725
21852
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
22726
|
-
const MESSAGE$
|
|
21853
|
+
const MESSAGE$43 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
22727
21854
|
const isConfigObjectPropName = (propName) => {
|
|
22728
21855
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
22729
21856
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -22812,7 +21939,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22812
21939
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
22813
21940
|
context.report({
|
|
22814
21941
|
node,
|
|
22815
|
-
message: MESSAGE$
|
|
21942
|
+
message: MESSAGE$43
|
|
22816
21943
|
});
|
|
22817
21944
|
}
|
|
22818
21945
|
};
|
|
@@ -22820,7 +21947,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22820
21947
|
});
|
|
22821
21948
|
//#endregion
|
|
22822
21949
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
22823
|
-
const MESSAGE$
|
|
21950
|
+
const MESSAGE$42 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
22824
21951
|
const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
|
|
22825
21952
|
const resolveSettings$29 = (settings) => {
|
|
22826
21953
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -22858,7 +21985,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
22858
21985
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
22859
21986
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
22860
21987
|
node: attribute,
|
|
22861
|
-
message: MESSAGE$
|
|
21988
|
+
message: MESSAGE$42
|
|
22862
21989
|
});
|
|
22863
21990
|
}
|
|
22864
21991
|
} };
|
|
@@ -23478,7 +22605,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
23478
22605
|
});
|
|
23479
22606
|
//#endregion
|
|
23480
22607
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
23481
|
-
const MESSAGE$
|
|
22608
|
+
const MESSAGE$41 = "You can't tell what props reach this element when you spread them.";
|
|
23482
22609
|
const resolveSettings$25 = (settings) => {
|
|
23483
22610
|
const reactDoctor = settings?.["react-doctor"];
|
|
23484
22611
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -23521,7 +22648,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
23521
22648
|
}
|
|
23522
22649
|
context.report({
|
|
23523
22650
|
node: attribute,
|
|
23524
|
-
message: MESSAGE$
|
|
22651
|
+
message: MESSAGE$41
|
|
23525
22652
|
});
|
|
23526
22653
|
didReportInFile = true;
|
|
23527
22654
|
return;
|
|
@@ -23797,7 +22924,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
23797
22924
|
});
|
|
23798
22925
|
//#endregion
|
|
23799
22926
|
//#region src/plugin/rules/a11y/lang.ts
|
|
23800
|
-
const MESSAGE$
|
|
22927
|
+
const MESSAGE$40 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
|
|
23801
22928
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
23802
22929
|
"aa",
|
|
23803
22930
|
"ab",
|
|
@@ -24277,7 +23404,7 @@ const lang = defineRule({
|
|
|
24277
23404
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
24278
23405
|
context.report({
|
|
24279
23406
|
node: langAttr,
|
|
24280
|
-
message: MESSAGE$
|
|
23407
|
+
message: MESSAGE$40
|
|
24281
23408
|
});
|
|
24282
23409
|
return;
|
|
24283
23410
|
}
|
|
@@ -24286,7 +23413,7 @@ const lang = defineRule({
|
|
|
24286
23413
|
if (value === null) return;
|
|
24287
23414
|
if (!isValidLangTag(value)) context.report({
|
|
24288
23415
|
node: langAttr,
|
|
24289
|
-
message: MESSAGE$
|
|
23416
|
+
message: MESSAGE$40
|
|
24290
23417
|
});
|
|
24291
23418
|
} })
|
|
24292
23419
|
});
|
|
@@ -24337,7 +23464,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
24337
23464
|
});
|
|
24338
23465
|
//#endregion
|
|
24339
23466
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
24340
|
-
const MESSAGE$
|
|
23467
|
+
const MESSAGE$39 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
24341
23468
|
const DEFAULT_AUDIO = ["audio"];
|
|
24342
23469
|
const DEFAULT_VIDEO = ["video"];
|
|
24343
23470
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -24426,7 +23553,7 @@ const mediaHasCaption = defineRule({
|
|
|
24426
23553
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
24427
23554
|
context.report({
|
|
24428
23555
|
node: node.name,
|
|
24429
|
-
message: MESSAGE$
|
|
23556
|
+
message: MESSAGE$39
|
|
24430
23557
|
});
|
|
24431
23558
|
return;
|
|
24432
23559
|
}
|
|
@@ -24445,7 +23572,7 @@ const mediaHasCaption = defineRule({
|
|
|
24445
23572
|
return kindValue.value.toLowerCase() === "captions";
|
|
24446
23573
|
})) context.report({
|
|
24447
23574
|
node: node.name,
|
|
24448
|
-
message: MESSAGE$
|
|
23575
|
+
message: MESSAGE$39
|
|
24449
23576
|
});
|
|
24450
23577
|
} };
|
|
24451
23578
|
}
|
|
@@ -24529,6 +23656,32 @@ const hasDirective = (programNode, directive) => {
|
|
|
24529
23656
|
return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
|
|
24530
23657
|
};
|
|
24531
23658
|
//#endregion
|
|
23659
|
+
//#region src/plugin/utils/unwrap-object-integrity-expression.ts
|
|
23660
|
+
const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
|
|
23661
|
+
"freeze",
|
|
23662
|
+
"seal",
|
|
23663
|
+
"preventExtensions"
|
|
23664
|
+
]);
|
|
23665
|
+
const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
|
|
23666
|
+
const getObjectIntegrityMethodName = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
23667
|
+
const expression = stripParenExpression(node);
|
|
23668
|
+
if (!isNodeOfType(expression, "CallExpression")) return null;
|
|
23669
|
+
const callee = stripParenExpression(expression.callee);
|
|
23670
|
+
const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : callee;
|
|
23671
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !scopes.isGlobalReference(receiver) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) return null;
|
|
23672
|
+
return callee.property.name;
|
|
23673
|
+
};
|
|
23674
|
+
const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
23675
|
+
let expression = stripParenExpression(node);
|
|
23676
|
+
while (isNodeOfType(expression, "CallExpression")) {
|
|
23677
|
+
if (!getObjectIntegrityMethodName(expression, scopes, methodNames)) break;
|
|
23678
|
+
const wrappedExpression = expression.arguments[0];
|
|
23679
|
+
if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
|
|
23680
|
+
expression = stripParenExpression(wrappedExpression);
|
|
23681
|
+
}
|
|
23682
|
+
return expression;
|
|
23683
|
+
};
|
|
23684
|
+
//#endregion
|
|
24532
23685
|
//#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
|
|
24533
23686
|
const nextjsAsyncClientComponent = defineRule({
|
|
24534
23687
|
id: "nextjs-async-client-component",
|
|
@@ -25962,7 +25115,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
25962
25115
|
});
|
|
25963
25116
|
//#endregion
|
|
25964
25117
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
25965
|
-
const MESSAGE$
|
|
25118
|
+
const MESSAGE$38 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
25966
25119
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
25967
25120
|
const noAccessKey = defineRule({
|
|
25968
25121
|
id: "no-access-key",
|
|
@@ -25981,7 +25134,7 @@ const noAccessKey = defineRule({
|
|
|
25981
25134
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
25982
25135
|
context.report({
|
|
25983
25136
|
node: accessKey,
|
|
25984
|
-
message: MESSAGE$
|
|
25137
|
+
message: MESSAGE$38
|
|
25985
25138
|
});
|
|
25986
25139
|
return;
|
|
25987
25140
|
}
|
|
@@ -25991,7 +25144,7 @@ const noAccessKey = defineRule({
|
|
|
25991
25144
|
if (isUndefinedIdentifier(expression)) return;
|
|
25992
25145
|
context.report({
|
|
25993
25146
|
node: accessKey,
|
|
25994
|
-
message: MESSAGE$
|
|
25147
|
+
message: MESSAGE$38
|
|
25995
25148
|
});
|
|
25996
25149
|
}
|
|
25997
25150
|
} };
|
|
@@ -26296,11 +25449,8 @@ const unwrapUseCallback = (node) => {
|
|
|
26296
25449
|
const callee = node.callee;
|
|
26297
25450
|
return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
|
|
26298
25451
|
};
|
|
26299
|
-
const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
|
|
26300
25452
|
const resolveToFunction = (ref) => {
|
|
26301
|
-
const
|
|
26302
|
-
if (!definition || hasParameterDefinition(ref)) return null;
|
|
26303
|
-
const definitionNode = definition.node;
|
|
25453
|
+
const definitionNode = ref.resolved?.defs[0]?.node;
|
|
26304
25454
|
if (!definitionNode) return null;
|
|
26305
25455
|
if (isFunctionLike$2(definitionNode)) return definitionNode;
|
|
26306
25456
|
if (isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
@@ -26710,7 +25860,7 @@ const isCleanupReturnArgument = (analysis, node) => {
|
|
|
26710
25860
|
if (isNodeOfType(node, "MemberExpression")) return true;
|
|
26711
25861
|
if (isNodeOfType(node, "Identifier")) {
|
|
26712
25862
|
const ref = getRef(analysis, node);
|
|
26713
|
-
if (ref &&
|
|
25863
|
+
if (ref && resolveToFunction(ref)) return true;
|
|
26714
25864
|
}
|
|
26715
25865
|
if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
|
|
26716
25866
|
return false;
|
|
@@ -26868,7 +26018,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
26868
26018
|
};
|
|
26869
26019
|
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
26870
26020
|
if (!setterRef.resolved) return false;
|
|
26871
|
-
const componentFunction = findEnclosingFunction
|
|
26021
|
+
const componentFunction = findEnclosingFunction(effectNode);
|
|
26872
26022
|
if (!componentFunction) return false;
|
|
26873
26023
|
for (const reference of setterRef.resolved.references) {
|
|
26874
26024
|
if (reference.init) continue;
|
|
@@ -27182,7 +26332,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
27182
26332
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
27183
26333
|
const reference = getRef(analysis, candidate);
|
|
27184
26334
|
if (!reference) return null;
|
|
27185
|
-
if (reference.resolved?.defs.some((definition) => definition.type === "ImportBinding")) return null;
|
|
26335
|
+
if (reference.resolved?.defs.some((definition) => definition.type === "Parameter" || definition.type === "ImportBinding")) return null;
|
|
27186
26336
|
const resolved = resolveToFunction(reference);
|
|
27187
26337
|
if (resolved) return resolved;
|
|
27188
26338
|
const definitionNode = reference.resolved?.defs[0]?.node;
|
|
@@ -27846,7 +26996,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
27846
26996
|
});
|
|
27847
26997
|
//#endregion
|
|
27848
26998
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
27849
|
-
const MESSAGE$
|
|
26999
|
+
const MESSAGE$37 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
|
|
27850
27000
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
27851
27001
|
"button",
|
|
27852
27002
|
"embed",
|
|
@@ -27958,7 +27108,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
27958
27108
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
27959
27109
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
27960
27110
|
node: ariaHidden,
|
|
27961
|
-
message: MESSAGE$
|
|
27111
|
+
message: MESSAGE$37
|
|
27962
27112
|
});
|
|
27963
27113
|
} })
|
|
27964
27114
|
});
|
|
@@ -28880,7 +28030,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
28880
28030
|
};
|
|
28881
28031
|
//#endregion
|
|
28882
28032
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
28883
|
-
const MESSAGE$
|
|
28033
|
+
const MESSAGE$36 = "Your users can see & submit the wrong data when this list reorders.";
|
|
28884
28034
|
const SECOND_INDEX_METHODS = new Set([
|
|
28885
28035
|
"every",
|
|
28886
28036
|
"filter",
|
|
@@ -28999,14 +28149,14 @@ const noArrayIndexKey = defineRule({
|
|
|
28999
28149
|
if (propName !== "key") continue;
|
|
29000
28150
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
29001
28151
|
node: property,
|
|
29002
|
-
message: MESSAGE$
|
|
28152
|
+
message: MESSAGE$36
|
|
29003
28153
|
});
|
|
29004
28154
|
}
|
|
29005
28155
|
} })
|
|
29006
28156
|
});
|
|
29007
28157
|
//#endregion
|
|
29008
28158
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
29009
|
-
const MESSAGE$
|
|
28159
|
+
const MESSAGE$35 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
|
|
29010
28160
|
const noAsyncEffectCallback = defineRule({
|
|
29011
28161
|
id: "no-async-effect-callback",
|
|
29012
28162
|
title: "Async effect callback",
|
|
@@ -29024,13 +28174,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
29024
28174
|
if (!callback.async) return;
|
|
29025
28175
|
context.report({
|
|
29026
28176
|
node: callback,
|
|
29027
|
-
message: MESSAGE$
|
|
28177
|
+
message: MESSAGE$35
|
|
29028
28178
|
});
|
|
29029
28179
|
} })
|
|
29030
28180
|
});
|
|
29031
28181
|
//#endregion
|
|
29032
28182
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
29033
|
-
const MESSAGE$
|
|
28183
|
+
const MESSAGE$34 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
29034
28184
|
const resolveSettings$21 = (settings) => {
|
|
29035
28185
|
const reactDoctor = settings?.["react-doctor"];
|
|
29036
28186
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -29127,7 +28277,7 @@ const noAutofocus = defineRule({
|
|
|
29127
28277
|
if (isConditionallyRendered(node)) return;
|
|
29128
28278
|
context.report({
|
|
29129
28279
|
node: autoFocusAttribute,
|
|
29130
|
-
message: MESSAGE$
|
|
28280
|
+
message: MESSAGE$34
|
|
29131
28281
|
});
|
|
29132
28282
|
} };
|
|
29133
28283
|
}
|
|
@@ -29641,6 +28791,138 @@ const noBarrelImport = defineRule({
|
|
|
29641
28791
|
}
|
|
29642
28792
|
});
|
|
29643
28793
|
//#endregion
|
|
28794
|
+
//#region src/plugin/utils/has-static-property-write-before.ts
|
|
28795
|
+
const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
28796
|
+
const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
|
|
28797
|
+
"CatchClause",
|
|
28798
|
+
"ConditionalExpression",
|
|
28799
|
+
"DoWhileStatement",
|
|
28800
|
+
"ForInStatement",
|
|
28801
|
+
"ForOfStatement",
|
|
28802
|
+
"ForStatement",
|
|
28803
|
+
"IfStatement",
|
|
28804
|
+
"LogicalExpression",
|
|
28805
|
+
"SwitchCase",
|
|
28806
|
+
"SwitchStatement",
|
|
28807
|
+
"TryStatement",
|
|
28808
|
+
"WhileStatement"
|
|
28809
|
+
]);
|
|
28810
|
+
const getResolvedStaticPropertyName = (memberExpression, scopes) => {
|
|
28811
|
+
if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
|
|
28812
|
+
const directPropertyName = getStaticPropertyName(memberExpression);
|
|
28813
|
+
if (directPropertyName || !memberExpression.computed) return directPropertyName;
|
|
28814
|
+
const property = stripParenExpression(memberExpression.property);
|
|
28815
|
+
if (!isNodeOfType(property, "Identifier")) return null;
|
|
28816
|
+
const propertySymbol = resolveConstIdentifierAlias(property, scopes);
|
|
28817
|
+
const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
|
|
28818
|
+
return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
|
|
28819
|
+
};
|
|
28820
|
+
const collectScopeSymbols = (scope, symbols) => {
|
|
28821
|
+
symbols.push(...scope.symbols);
|
|
28822
|
+
for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
|
|
28823
|
+
};
|
|
28824
|
+
const getEquivalentSymbols = (identifier, scopes) => {
|
|
28825
|
+
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
28826
|
+
if (!rootSymbol) return [];
|
|
28827
|
+
let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
|
|
28828
|
+
if (!symbolsByRootId) {
|
|
28829
|
+
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
28830
|
+
equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
28831
|
+
}
|
|
28832
|
+
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
28833
|
+
if (cachedSymbols) return cachedSymbols;
|
|
28834
|
+
const allSymbols = [];
|
|
28835
|
+
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
28836
|
+
const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
|
|
28837
|
+
symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
|
|
28838
|
+
return equivalentSymbols;
|
|
28839
|
+
};
|
|
28840
|
+
const findExecutionBoundary = (node) => {
|
|
28841
|
+
let current = node;
|
|
28842
|
+
while (current) {
|
|
28843
|
+
if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
|
|
28844
|
+
current = current.parent ?? null;
|
|
28845
|
+
}
|
|
28846
|
+
return null;
|
|
28847
|
+
};
|
|
28848
|
+
const isOnUnconditionalPath = (node, boundary) => {
|
|
28849
|
+
let current = node.parent ?? null;
|
|
28850
|
+
while (current && current !== boundary) {
|
|
28851
|
+
if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
|
|
28852
|
+
current = current.parent ?? null;
|
|
28853
|
+
}
|
|
28854
|
+
return current === boundary;
|
|
28855
|
+
};
|
|
28856
|
+
const findFunctionBindingIdentifier = (functionNode) => {
|
|
28857
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
|
|
28858
|
+
const parent = functionNode.parent;
|
|
28859
|
+
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
28860
|
+
return null;
|
|
28861
|
+
};
|
|
28862
|
+
const findDirectCall = (identifier) => {
|
|
28863
|
+
let callee = identifier;
|
|
28864
|
+
let parent = callee.parent;
|
|
28865
|
+
while (parent && stripParenExpression(parent) === identifier) {
|
|
28866
|
+
callee = parent;
|
|
28867
|
+
parent = callee.parent;
|
|
28868
|
+
}
|
|
28869
|
+
return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
28870
|
+
};
|
|
28871
|
+
const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
28872
|
+
if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
|
|
28873
|
+
visitedFunctionNodes.add(functionNode);
|
|
28874
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
28875
|
+
if (!referenceBoundary) return false;
|
|
28876
|
+
const invocationCalls = [];
|
|
28877
|
+
const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
|
|
28878
|
+
if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
|
|
28879
|
+
const call = findDirectCall(reference.identifier);
|
|
28880
|
+
if (call) invocationCalls.push(call);
|
|
28881
|
+
}
|
|
28882
|
+
else {
|
|
28883
|
+
const call = findDirectCall(functionNode);
|
|
28884
|
+
if (call) invocationCalls.push(call);
|
|
28885
|
+
}
|
|
28886
|
+
return invocationCalls.some((call) => {
|
|
28887
|
+
if (call.range[0] >= referenceNode.range[0]) return false;
|
|
28888
|
+
const callBoundary = findExecutionBoundary(call);
|
|
28889
|
+
if (!callBoundary) return false;
|
|
28890
|
+
if (callBoundary === referenceBoundary) return true;
|
|
28891
|
+
if (!isFunctionLike$2(callBoundary)) return false;
|
|
28892
|
+
return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
|
|
28893
|
+
});
|
|
28894
|
+
};
|
|
28895
|
+
const isMemberWriteTarget = (memberExpression) => {
|
|
28896
|
+
const parent = memberExpression.parent;
|
|
28897
|
+
if (!parent) return false;
|
|
28898
|
+
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
|
|
28899
|
+
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
|
|
28900
|
+
return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
|
|
28901
|
+
};
|
|
28902
|
+
const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
28903
|
+
let parent = reference.identifier.parent;
|
|
28904
|
+
while (parent && stripParenExpression(parent) === reference.identifier) parent = parent.parent;
|
|
28905
|
+
if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== reference.identifier || getResolvedStaticPropertyName(parent, scopes) !== propertyName || !isMemberWriteTarget(parent)) return false;
|
|
28906
|
+
const writeBoundary = findExecutionBoundary(parent);
|
|
28907
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
28908
|
+
if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(parent, writeBoundary)) return false;
|
|
28909
|
+
if (writeBoundary === referenceBoundary) return parent.range[0] < referenceNode.range[0];
|
|
28910
|
+
return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
|
|
28911
|
+
});
|
|
28912
|
+
const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
28913
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
28914
|
+
return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
|
|
28915
|
+
};
|
|
28916
|
+
//#endregion
|
|
28917
|
+
//#region src/plugin/utils/has-symbol-write-before.ts
|
|
28918
|
+
const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
28919
|
+
if (reference.flag === "read") return false;
|
|
28920
|
+
const writeFunction = findEnclosingFunction(reference.identifier);
|
|
28921
|
+
if (writeFunction === findEnclosingFunction(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
|
|
28922
|
+
if (!writeFunction) return true;
|
|
28923
|
+
return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
|
|
28924
|
+
});
|
|
28925
|
+
//#endregion
|
|
29644
28926
|
//#region src/plugin/utils/has-stable-call-target.ts
|
|
29645
28927
|
const hasStableCallTarget = (callExpression, scopes) => {
|
|
29646
28928
|
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -29968,7 +29250,7 @@ const noChainStateUpdates = defineRule({
|
|
|
29968
29250
|
});
|
|
29969
29251
|
//#endregion
|
|
29970
29252
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
29971
|
-
const MESSAGE$
|
|
29253
|
+
const MESSAGE$33 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
29972
29254
|
const noChildrenProp = defineRule({
|
|
29973
29255
|
id: "no-children-prop",
|
|
29974
29256
|
title: "Children passed as a prop",
|
|
@@ -29980,7 +29262,7 @@ const noChildrenProp = defineRule({
|
|
|
29980
29262
|
if (node.name.name !== "children") return;
|
|
29981
29263
|
context.report({
|
|
29982
29264
|
node: node.name,
|
|
29983
|
-
message: MESSAGE$
|
|
29265
|
+
message: MESSAGE$33
|
|
29984
29266
|
});
|
|
29985
29267
|
},
|
|
29986
29268
|
CallExpression(node) {
|
|
@@ -29993,7 +29275,7 @@ const noChildrenProp = defineRule({
|
|
|
29993
29275
|
const propertyKey = property.key;
|
|
29994
29276
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
29995
29277
|
node: propertyKey,
|
|
29996
|
-
message: MESSAGE$
|
|
29278
|
+
message: MESSAGE$33
|
|
29997
29279
|
});
|
|
29998
29280
|
}
|
|
29999
29281
|
}
|
|
@@ -30001,7 +29283,7 @@ const noChildrenProp = defineRule({
|
|
|
30001
29283
|
});
|
|
30002
29284
|
//#endregion
|
|
30003
29285
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
30004
|
-
const MESSAGE$
|
|
29286
|
+
const MESSAGE$32 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
30005
29287
|
const noCloneElement = defineRule({
|
|
30006
29288
|
id: "no-clone-element",
|
|
30007
29289
|
title: "cloneElement makes child props fragile",
|
|
@@ -30014,7 +29296,7 @@ const noCloneElement = defineRule({
|
|
|
30014
29296
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
30015
29297
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
30016
29298
|
node: callee,
|
|
30017
|
-
message: MESSAGE$
|
|
29299
|
+
message: MESSAGE$32
|
|
30018
29300
|
});
|
|
30019
29301
|
return;
|
|
30020
29302
|
}
|
|
@@ -30027,7 +29309,7 @@ const noCloneElement = defineRule({
|
|
|
30027
29309
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
30028
29310
|
context.report({
|
|
30029
29311
|
node: callee,
|
|
30030
|
-
message: MESSAGE$
|
|
29312
|
+
message: MESSAGE$32
|
|
30031
29313
|
});
|
|
30032
29314
|
}
|
|
30033
29315
|
} })
|
|
@@ -30047,7 +29329,7 @@ const getCallMethodName = (callee) => {
|
|
|
30047
29329
|
};
|
|
30048
29330
|
//#endregion
|
|
30049
29331
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
30050
|
-
const MESSAGE$
|
|
29332
|
+
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
30051
29333
|
const CONTEXT_MODULES = [
|
|
30052
29334
|
"react",
|
|
30053
29335
|
"use-context-selector",
|
|
@@ -30095,13 +29377,13 @@ const noCreateContextInRender = defineRule({
|
|
|
30095
29377
|
if (!componentOrHookName) return;
|
|
30096
29378
|
context.report({
|
|
30097
29379
|
node,
|
|
30098
|
-
message: `${MESSAGE$
|
|
29380
|
+
message: `${MESSAGE$31} (called inside "${componentOrHookName}")`
|
|
30099
29381
|
});
|
|
30100
29382
|
} })
|
|
30101
29383
|
});
|
|
30102
29384
|
//#endregion
|
|
30103
29385
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
30104
|
-
const MESSAGE$
|
|
29386
|
+
const MESSAGE$30 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
|
|
30105
29387
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
30106
29388
|
const parent = functionNode.parent;
|
|
30107
29389
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -30109,8 +29391,8 @@ const isUseMemoCallbackArgument = (functionNode) => {
|
|
|
30109
29391
|
return isReactFunctionCall(parent, "useMemo");
|
|
30110
29392
|
};
|
|
30111
29393
|
const findEnclosingRenderFunction = (node) => {
|
|
30112
|
-
let enclosingFunction = findEnclosingFunction
|
|
30113
|
-
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction
|
|
29394
|
+
let enclosingFunction = findEnclosingFunction(node);
|
|
29395
|
+
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
30114
29396
|
return enclosingFunction;
|
|
30115
29397
|
};
|
|
30116
29398
|
const noCreateRefInFunctionComponent = defineRule({
|
|
@@ -30131,7 +29413,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
30131
29413
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
|
|
30132
29414
|
context.report({
|
|
30133
29415
|
node,
|
|
30134
|
-
message: MESSAGE$
|
|
29416
|
+
message: MESSAGE$30
|
|
30135
29417
|
});
|
|
30136
29418
|
} })
|
|
30137
29419
|
});
|
|
@@ -30271,7 +29553,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
30271
29553
|
});
|
|
30272
29554
|
//#endregion
|
|
30273
29555
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
30274
|
-
const MESSAGE$
|
|
29556
|
+
const MESSAGE$29 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
30275
29557
|
const noDanger = defineRule({
|
|
30276
29558
|
id: "no-danger",
|
|
30277
29559
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -30285,7 +29567,7 @@ const noDanger = defineRule({
|
|
|
30285
29567
|
if (!propAttribute) return;
|
|
30286
29568
|
context.report({
|
|
30287
29569
|
node: propAttribute.name,
|
|
30288
|
-
message: MESSAGE$
|
|
29570
|
+
message: MESSAGE$29
|
|
30289
29571
|
});
|
|
30290
29572
|
},
|
|
30291
29573
|
CallExpression(node) {
|
|
@@ -30297,7 +29579,7 @@ const noDanger = defineRule({
|
|
|
30297
29579
|
const propertyKey = property.key;
|
|
30298
29580
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
30299
29581
|
node: propertyKey,
|
|
30300
|
-
message: MESSAGE$
|
|
29582
|
+
message: MESSAGE$29
|
|
30301
29583
|
});
|
|
30302
29584
|
}
|
|
30303
29585
|
}
|
|
@@ -30320,7 +29602,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
30320
29602
|
};
|
|
30321
29603
|
//#endregion
|
|
30322
29604
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
30323
|
-
const MESSAGE$
|
|
29605
|
+
const MESSAGE$28 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
30324
29606
|
const mergePropsShape = (target, source) => {
|
|
30325
29607
|
target.hasDangerously ||= source.hasDangerously;
|
|
30326
29608
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -30395,7 +29677,7 @@ const noDangerWithChildren = defineRule({
|
|
|
30395
29677
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
30396
29678
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
30397
29679
|
node: opening,
|
|
30398
|
-
message: MESSAGE$
|
|
29680
|
+
message: MESSAGE$28
|
|
30399
29681
|
});
|
|
30400
29682
|
},
|
|
30401
29683
|
CallExpression(node) {
|
|
@@ -30408,7 +29690,7 @@ const noDangerWithChildren = defineRule({
|
|
|
30408
29690
|
const positionalChildren = node.arguments.slice(2);
|
|
30409
29691
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
30410
29692
|
node,
|
|
30411
|
-
message: MESSAGE$
|
|
29693
|
+
message: MESSAGE$28
|
|
30412
29694
|
});
|
|
30413
29695
|
}
|
|
30414
29696
|
})
|
|
@@ -31161,7 +30443,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
31161
30443
|
const isEffectDrivenResync = (useStateCall) => {
|
|
31162
30444
|
const setterName = getStateSetterName(useStateCall);
|
|
31163
30445
|
if (!setterName) return false;
|
|
31164
|
-
const componentFunction = findEnclosingFunction
|
|
30446
|
+
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31165
30447
|
if (!componentFunction) return false;
|
|
31166
30448
|
let isExempt = false;
|
|
31167
30449
|
walkAst(componentFunction, (child) => {
|
|
@@ -31265,7 +30547,7 @@ const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
|
|
|
31265
30547
|
const setterName = getStateSetterName(useStateCall);
|
|
31266
30548
|
if (!setterName) return false;
|
|
31267
30549
|
const stateValueName = getStateValueName(useStateCall);
|
|
31268
|
-
const componentFunction = findEnclosingFunction
|
|
30550
|
+
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31269
30551
|
if (!componentFunction) return false;
|
|
31270
30552
|
let isExempt = false;
|
|
31271
30553
|
walkAst(componentFunction, (child) => {
|
|
@@ -31311,7 +30593,7 @@ const noDerivedUseState = defineRule({
|
|
|
31311
30593
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
31312
30594
|
if (isEffectDrivenResync(node)) return;
|
|
31313
30595
|
if (isNextjsDataFetchingPage(node)) return;
|
|
31314
|
-
const componentFunction = findEnclosingFunction
|
|
30596
|
+
const componentFunction = findEnclosingFunction(node);
|
|
31315
30597
|
if (componentFunction) {
|
|
31316
30598
|
if (isDefaultedDestructuredProp(componentFunction, propName)) return;
|
|
31317
30599
|
if (isDraftCommittedToParent(componentFunction, getStateValueName(node), propStackTracker.isPropName)) return;
|
|
@@ -31373,7 +30655,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
31373
30655
|
//#endregion
|
|
31374
30656
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
31375
30657
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
31376
|
-
const MESSAGE$
|
|
30658
|
+
const MESSAGE$27 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
31377
30659
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
31378
30660
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
31379
30661
|
let ancestor = setStateCall.parent;
|
|
@@ -31501,7 +30783,7 @@ const noDidMountSetState = defineRule({
|
|
|
31501
30783
|
}
|
|
31502
30784
|
context.report({
|
|
31503
30785
|
node: node.callee,
|
|
31504
|
-
message: MESSAGE$
|
|
30786
|
+
message: MESSAGE$27
|
|
31505
30787
|
});
|
|
31506
30788
|
} };
|
|
31507
30789
|
}
|
|
@@ -31509,7 +30791,7 @@ const noDidMountSetState = defineRule({
|
|
|
31509
30791
|
//#endregion
|
|
31510
30792
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
31511
30793
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
31512
|
-
const MESSAGE$
|
|
30794
|
+
const MESSAGE$26 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
31513
30795
|
const EQUALITY_OPERATORS = new Set([
|
|
31514
30796
|
"==",
|
|
31515
30797
|
"===",
|
|
@@ -31683,7 +30965,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
31683
30965
|
if (isInsideDiffGuard(node)) return;
|
|
31684
30966
|
context.report({
|
|
31685
30967
|
node: node.callee,
|
|
31686
|
-
message: MESSAGE$
|
|
30968
|
+
message: MESSAGE$26
|
|
31687
30969
|
});
|
|
31688
30970
|
} };
|
|
31689
30971
|
}
|
|
@@ -31706,7 +30988,7 @@ const isStateMemberExpression = (node) => {
|
|
|
31706
30988
|
};
|
|
31707
30989
|
//#endregion
|
|
31708
30990
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
31709
|
-
const MESSAGE$
|
|
30991
|
+
const MESSAGE$25 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
|
|
31710
30992
|
const shouldIgnoreMutation = (node) => {
|
|
31711
30993
|
let isConstructor = false;
|
|
31712
30994
|
let isInsideCallExpression = false;
|
|
@@ -31728,7 +31010,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
31728
31010
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
31729
31011
|
context.report({
|
|
31730
31012
|
node: reportNode,
|
|
31731
|
-
message: MESSAGE$
|
|
31013
|
+
message: MESSAGE$25
|
|
31732
31014
|
});
|
|
31733
31015
|
};
|
|
31734
31016
|
const noDirectMutationState = defineRule({
|
|
@@ -32079,7 +31361,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
32079
31361
|
});
|
|
32080
31362
|
//#endregion
|
|
32081
31363
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
32082
|
-
const MESSAGE$
|
|
31364
|
+
const MESSAGE$24 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
32083
31365
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
32084
31366
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
32085
31367
|
const noDocumentWrite = defineRule({
|
|
@@ -32096,7 +31378,7 @@ const noDocumentWrite = defineRule({
|
|
|
32096
31378
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
32097
31379
|
context.report({
|
|
32098
31380
|
node,
|
|
32099
|
-
message: MESSAGE$
|
|
31381
|
+
message: MESSAGE$24
|
|
32100
31382
|
});
|
|
32101
31383
|
} })
|
|
32102
31384
|
});
|
|
@@ -32786,29 +32068,6 @@ const isReturnOnlyStatement = (node) => {
|
|
|
32786
32068
|
return isNodeOfType(node, "BlockStatement") && (node.body?.length ?? 0) === 1 && isNodeOfType(node.body?.[0], "ReturnStatement");
|
|
32787
32069
|
};
|
|
32788
32070
|
const hasEventLikeRemainingStatements = (statements) => statements.some((statement) => !isNodeOfType(statement, "ReturnStatement") && hasEventLikeNode(statement));
|
|
32789
|
-
const collectLeadingEarlyReturnGuards = (statements) => {
|
|
32790
|
-
const guardExpressions = [];
|
|
32791
|
-
for (const statement of statements) {
|
|
32792
|
-
if (isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
32793
|
-
if (!isNodeOfType(statement, "IfStatement") || statement.alternate || !isReturnOnlyStatement(statement.consequent)) break;
|
|
32794
|
-
collectGuardExpressions(statement.test, guardExpressions);
|
|
32795
|
-
}
|
|
32796
|
-
return guardExpressions;
|
|
32797
|
-
};
|
|
32798
|
-
const collectImmutableExpressionOrigins = (expression, scopes) => {
|
|
32799
|
-
const origins = [];
|
|
32800
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
32801
|
-
let currentExpression = stripParenExpression(expression);
|
|
32802
|
-
while (currentExpression) {
|
|
32803
|
-
origins.push(currentExpression);
|
|
32804
|
-
if (!isNodeOfType(currentExpression, "Identifier")) break;
|
|
32805
|
-
const bindingSymbol = scopes.symbolFor(currentExpression);
|
|
32806
|
-
if (!bindingSymbol || bindingSymbol.kind !== "const" || visitedSymbolIds.has(bindingSymbol.id) || !bindingSymbol.initializer) break;
|
|
32807
|
-
visitedSymbolIds.add(bindingSymbol.id);
|
|
32808
|
-
currentExpression = stripParenExpression(bindingSymbol.initializer);
|
|
32809
|
-
}
|
|
32810
|
-
return origins;
|
|
32811
|
-
};
|
|
32812
32071
|
const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
|
|
32813
32072
|
const unwrappedDependencyExpression = unwrapChainExpression$1(dependencyExpression);
|
|
32814
32073
|
if (!unwrappedDependencyExpression) return false;
|
|
@@ -32816,69 +32075,6 @@ const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
|
|
|
32816
32075
|
return isNodeOfType(unwrappedDependencyExpression, "Identifier") && unwrappedDependencyExpression.name === guardExpression.rootIdentifierName;
|
|
32817
32076
|
};
|
|
32818
32077
|
const hasDependencyMatch = (guardExpression, dependencyExpressions) => dependencyExpressions.some((dependencyExpression) => doesGuardMatchDependency(guardExpression, dependencyExpression));
|
|
32819
|
-
const hasAliasedDependencyMatch = (guardExpression, dependencyExpressions, scopes) => {
|
|
32820
|
-
const guardOrigins = collectImmutableExpressionOrigins(guardExpression.expression, scopes);
|
|
32821
|
-
return dependencyExpressions.some((dependencyExpression) => {
|
|
32822
|
-
if (!dependencyExpression) return false;
|
|
32823
|
-
const dependencyOrigins = collectImmutableExpressionOrigins(dependencyExpression, scopes);
|
|
32824
|
-
return guardOrigins.some((guardOrigin) => dependencyOrigins.some((dependencyOrigin) => areExpressionsStructurallyEqual(guardOrigin, dependencyOrigin)));
|
|
32825
|
-
});
|
|
32826
|
-
};
|
|
32827
|
-
const isStaticallyTrue = (expression, scopes) => collectImmutableExpressionOrigins(expression, scopes).some((origin) => isNodeOfType(origin, "Literal") && origin.value === true);
|
|
32828
|
-
const isReactRouterReplacementNavigation = (node, rootIdentifierNames, scopes) => {
|
|
32829
|
-
let didFindNavigation = false;
|
|
32830
|
-
let didFindOtherTriggeredSideEffect = false;
|
|
32831
|
-
walkAst(node, (child) => {
|
|
32832
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
32833
|
-
if (!(findTriggeredSideEffectCalleeName(child) !== null || hasDocumentClassListMutation(child))) return;
|
|
32834
|
-
const destinationExpression = child.arguments?.[0];
|
|
32835
|
-
const doesDestinationReferenceReconciliationValue = Boolean(destinationExpression && collectImmutableExpressionOrigins(destinationExpression, scopes).some((origin) => doesNodeReferenceAnyRoot(origin, rootIdentifierNames)));
|
|
32836
|
-
if (!isNodeOfType(child.callee, "Identifier") || !doesDestinationReferenceReconciliationValue) {
|
|
32837
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32838
|
-
return;
|
|
32839
|
-
}
|
|
32840
|
-
const navigationSymbol = scopes.symbolFor(child.callee);
|
|
32841
|
-
const navigationInitializer = navigationSymbol?.initializer ? stripParenExpression(navigationSymbol.initializer) : null;
|
|
32842
|
-
if (navigationSymbol?.kind !== "const" || !isNodeOfType(navigationInitializer, "CallExpression") || !isNodeOfType(navigationInitializer.callee, "Identifier")) {
|
|
32843
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32844
|
-
return;
|
|
32845
|
-
}
|
|
32846
|
-
if (scopes.symbolFor(navigationInitializer.callee)?.kind !== "import") {
|
|
32847
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32848
|
-
return;
|
|
32849
|
-
}
|
|
32850
|
-
if ((getImportedNameFromModule(navigationInitializer.callee, navigationInitializer.callee.name, "react-router-dom") ?? getImportedNameFromModule(navigationInitializer.callee, navigationInitializer.callee.name, "react-router")) !== "useNavigate") {
|
|
32851
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32852
|
-
return;
|
|
32853
|
-
}
|
|
32854
|
-
const navigationOptionsArgument = child.arguments?.[1];
|
|
32855
|
-
const navigationOptions = navigationOptionsArgument ? stripParenExpression(navigationOptionsArgument) : null;
|
|
32856
|
-
if (!isNodeOfType(navigationOptions, "ObjectExpression")) {
|
|
32857
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32858
|
-
return;
|
|
32859
|
-
}
|
|
32860
|
-
let isReplacementGuaranteed = false;
|
|
32861
|
-
for (const property of navigationOptions.properties) {
|
|
32862
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
32863
|
-
isReplacementGuaranteed = false;
|
|
32864
|
-
continue;
|
|
32865
|
-
}
|
|
32866
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
32867
|
-
if (propertyName === null) {
|
|
32868
|
-
isReplacementGuaranteed = false;
|
|
32869
|
-
continue;
|
|
32870
|
-
}
|
|
32871
|
-
if (propertyName !== "replace") continue;
|
|
32872
|
-
isReplacementGuaranteed = isStaticallyTrue(property.value, scopes);
|
|
32873
|
-
}
|
|
32874
|
-
if (isReplacementGuaranteed) {
|
|
32875
|
-
didFindNavigation = true;
|
|
32876
|
-
return;
|
|
32877
|
-
}
|
|
32878
|
-
didFindOtherTriggeredSideEffect = true;
|
|
32879
|
-
});
|
|
32880
|
-
return didFindNavigation && !didFindOtherTriggeredSideEffect;
|
|
32881
|
-
};
|
|
32882
32078
|
const isEqualityToLiteralGuard = (guardExpression) => {
|
|
32883
32079
|
const parent = guardExpression.expression.parent;
|
|
32884
32080
|
if (!isNodeOfType(parent, "BinaryExpression")) return false;
|
|
@@ -32943,35 +32139,18 @@ const noEffectEventHandler = defineRule({
|
|
|
32943
32139
|
if (statements.length === 0) return;
|
|
32944
32140
|
const soleStatement = statements[0];
|
|
32945
32141
|
if (!isNodeOfType(soleStatement, "IfStatement")) return;
|
|
32946
|
-
const
|
|
32947
|
-
collectGuardExpressions(soleStatement.test,
|
|
32948
|
-
const
|
|
32949
|
-
if (guardExpressions.length === 0) guardExpressions.push(...initialGuardExpressions);
|
|
32950
|
-
const matchingPropGuardExpressions = initialGuardExpressions.filter((guardExpression) => hasDependencyMatch(guardExpression, dependencyExpressions) && propStackTracker.isPropName(guardExpression.rootIdentifierName, node));
|
|
32142
|
+
const guardExpressions = [];
|
|
32143
|
+
collectGuardExpressions(soleStatement.test, guardExpressions);
|
|
32144
|
+
const matchingPropGuardExpressions = guardExpressions.filter((guardExpression) => hasDependencyMatch(guardExpression, dependencyExpressions) && propStackTracker.isPropName(guardExpression.rootIdentifierName, node));
|
|
32951
32145
|
if (matchingPropGuardExpressions.length === 0) return;
|
|
32952
32146
|
const isSingleGuardedEventLikeStatement = statements.length === 1 && hasEventLikeNode(soleStatement.consequent);
|
|
32953
32147
|
const isEarlyReturnGuardedEventLikeBody = statements.length > 1 && !soleStatement.alternate && isReturnOnlyStatement(soleStatement.consequent) && hasEventLikeRemainingStatements(statements.slice(1));
|
|
32954
32148
|
if (!isSingleGuardedEventLikeStatement && !isEarlyReturnGuardedEventLikeBody) return;
|
|
32955
32149
|
if (isEarlyReturnGuardedEventLikeBody && !isSingleGuardedEventLikeStatement && matchingPropGuardExpressions.every(isEqualityToLiteralGuard)) return;
|
|
32956
|
-
if (
|
|
32150
|
+
if (guardExpressions.some((guardExpression) => !matchingPropGuardExpressions.some((matchingGuardExpression) => matchingGuardExpression.expression === guardExpression.expression))) {
|
|
32957
32151
|
const matchingPropRootNames = new Set(matchingPropGuardExpressions.map((guardExpression) => guardExpression.rootIdentifierName));
|
|
32958
32152
|
if (!(isSingleGuardedEventLikeStatement ? doesEventLikeCallReferenceAnyRoot(soleStatement.consequent, matchingPropRootNames) : doesAnyEventLikeCallReferenceAnyRoot(statements.slice(1), matchingPropRootNames))) return;
|
|
32959
32153
|
}
|
|
32960
|
-
if (isEarlyReturnGuardedEventLikeBody) {
|
|
32961
|
-
const reconciliationGuardRootNames = new Set(guardExpressions.filter((guardExpression) => !propStackTracker.isPropName(guardExpression.rootIdentifierName, node) && hasAliasedDependencyMatch(guardExpression, dependencyExpressions, context.scopes) && guardExpressions.some((comparisonGuardExpression) => {
|
|
32962
|
-
if (comparisonGuardExpression.rootIdentifierName !== guardExpression.rootIdentifierName) return false;
|
|
32963
|
-
const comparisonExpression = comparisonGuardExpression.expression.parent;
|
|
32964
|
-
if (!isNodeOfType(comparisonExpression, "BinaryExpression") || comparisonExpression.operator !== "===" && comparisonExpression.operator !== "==") return false;
|
|
32965
|
-
const comparedRootIdentifierName = getRootIdentifierName(comparisonExpression.left === comparisonGuardExpression.expression ? comparisonExpression.right : comparisonExpression.left);
|
|
32966
|
-
return Boolean(comparedRootIdentifierName && propStackTracker.isPropName(comparedRootIdentifierName, node));
|
|
32967
|
-
})).map((guardExpression) => guardExpression.rootIdentifierName));
|
|
32968
|
-
if (reconciliationGuardRootNames.size > 0) {
|
|
32969
|
-
const matchingPropRootNames = new Set(matchingPropGuardExpressions.map((guardExpression) => guardExpression.rootIdentifierName));
|
|
32970
|
-
const eventLikeStatements = statements.slice(1);
|
|
32971
|
-
const triggeredStatements = eventLikeStatements.filter(hasEventLikeNode);
|
|
32972
|
-
if (triggeredStatements.length > 0 && triggeredStatements.every((statement) => isReactRouterReplacementNavigation(statement, reconciliationGuardRootNames, context.scopes)) && !doesAnyEventLikeCallReferenceAnyRoot(eventLikeStatements, matchingPropRootNames)) return;
|
|
32973
|
-
}
|
|
32974
|
-
}
|
|
32975
32154
|
context.report({
|
|
32976
32155
|
node,
|
|
32977
32156
|
message: "This useEffect is simulating an event handler, which costs an extra render & runs late."
|
|
@@ -33252,7 +32431,7 @@ const isFunctionUsedOutsideHandlers = (analysis, functionNode, componentFunction
|
|
|
33252
32431
|
if (isInsideInlineEventHandler(identifier, componentFunction)) return false;
|
|
33253
32432
|
const parent = identifier.parent;
|
|
33254
32433
|
if (parent && isNodeOfType(parent, "CallExpression") && parent.callee === identifier) {
|
|
33255
|
-
if (findEnclosingFunction
|
|
32434
|
+
if (findEnclosingFunction(parent) === functionNode) return false;
|
|
33256
32435
|
if (isInsideProvenEventHandler(analysis, parent, componentFunction, false)) return false;
|
|
33257
32436
|
}
|
|
33258
32437
|
return true;
|
|
@@ -33298,7 +32477,7 @@ const isStateWrittenOnlyFromEventHandlers = (analysis, stateReference) => {
|
|
|
33298
32477
|
if (!setterVariable) return false;
|
|
33299
32478
|
const stateDeclarator = getUseStateDecl(analysis, stateReference);
|
|
33300
32479
|
if (!stateDeclarator) return false;
|
|
33301
|
-
const componentFunction = findEnclosingFunction
|
|
32480
|
+
const componentFunction = findEnclosingFunction(stateDeclarator);
|
|
33302
32481
|
if (!componentFunction) return false;
|
|
33303
32482
|
let hasWriter = false;
|
|
33304
32483
|
for (const reference of setterVariable.references) {
|
|
@@ -34360,8 +33539,8 @@ const isAwaitedInFunction = (request, functionNode) => {
|
|
|
34360
33539
|
return false;
|
|
34361
33540
|
};
|
|
34362
33541
|
const isCompletionSinkForRequest = (completionSink, request) => {
|
|
34363
|
-
const requestFunction = findEnclosingFunction
|
|
34364
|
-
const completionSinkFunction = findEnclosingFunction
|
|
33542
|
+
const requestFunction = findEnclosingFunction(request);
|
|
33543
|
+
const completionSinkFunction = findEnclosingFunction(completionSink);
|
|
34365
33544
|
if (!requestFunction || !completionSinkFunction) return false;
|
|
34366
33545
|
if (requestFunction === completionSinkFunction) {
|
|
34367
33546
|
if (!isAwaitedInFunction(request, requestFunction)) return false;
|
|
@@ -34417,7 +33596,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
34417
33596
|
"ReactDOM",
|
|
34418
33597
|
"ReactDom"
|
|
34419
33598
|
]);
|
|
34420
|
-
const MESSAGE$
|
|
33599
|
+
const MESSAGE$23 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
34421
33600
|
const noFindDomNode = defineRule({
|
|
34422
33601
|
id: "no-find-dom-node",
|
|
34423
33602
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -34428,7 +33607,7 @@ const noFindDomNode = defineRule({
|
|
|
34428
33607
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
34429
33608
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
34430
33609
|
node: callee,
|
|
34431
|
-
message: MESSAGE$
|
|
33610
|
+
message: MESSAGE$23
|
|
34432
33611
|
});
|
|
34433
33612
|
return;
|
|
34434
33613
|
}
|
|
@@ -34439,7 +33618,7 @@ const noFindDomNode = defineRule({
|
|
|
34439
33618
|
if (callee.property.name !== "findDOMNode") return;
|
|
34440
33619
|
context.report({
|
|
34441
33620
|
node: callee.property,
|
|
34442
|
-
message: MESSAGE$
|
|
33621
|
+
message: MESSAGE$23
|
|
34443
33622
|
});
|
|
34444
33623
|
}
|
|
34445
33624
|
} })
|
|
@@ -34648,6 +33827,13 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
34648
33827
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
34649
33828
|
};
|
|
34650
33829
|
//#endregion
|
|
33830
|
+
//#region src/plugin/utils/is-type-only-import.ts
|
|
33831
|
+
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
33832
|
+
if (!specifiers || specifiers.length === 0) return false;
|
|
33833
|
+
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
33834
|
+
};
|
|
33835
|
+
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
33836
|
+
//#endregion
|
|
34651
33837
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
34652
33838
|
const isLibraryDevPage = (filename) => {
|
|
34653
33839
|
if (!filename) return false;
|
|
@@ -35211,7 +34397,7 @@ const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
|
|
|
35211
34397
|
return attribute ? !isEventHandlerAttribute(attribute) : true;
|
|
35212
34398
|
}
|
|
35213
34399
|
if (isNodeOfType(parentNode, "ReturnStatement")) {
|
|
35214
|
-
if (findEnclosingFunction
|
|
34400
|
+
if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
|
|
35215
34401
|
}
|
|
35216
34402
|
if (parentNode === componentOrHookNode) return isFunctionLike$2(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
|
|
35217
34403
|
if (isFunctionLike$2(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
|
|
@@ -35334,7 +34520,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
35334
34520
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
35335
34521
|
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
35336
34522
|
if (!componentOrHookNode) return;
|
|
35337
|
-
const enclosingFunction = findEnclosingFunction
|
|
34523
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
35338
34524
|
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
|
|
35339
34525
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
35340
34526
|
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
@@ -35346,7 +34532,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
35346
34532
|
});
|
|
35347
34533
|
//#endregion
|
|
35348
34534
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
35349
|
-
const MESSAGE$
|
|
34535
|
+
const MESSAGE$22 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
|
|
35350
34536
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
35351
34537
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
35352
34538
|
title: "Lazy image with high fetchPriority",
|
|
@@ -35360,7 +34546,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
35360
34546
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
35361
34547
|
context.report({
|
|
35362
34548
|
node: node.name,
|
|
35363
|
-
message: MESSAGE$
|
|
34549
|
+
message: MESSAGE$22
|
|
35364
34550
|
});
|
|
35365
34551
|
} })
|
|
35366
34552
|
});
|
|
@@ -35549,7 +34735,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
35549
34735
|
});
|
|
35550
34736
|
//#endregion
|
|
35551
34737
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
35552
|
-
const MESSAGE$
|
|
34738
|
+
const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
35553
34739
|
const REACT_USE_REF_OPTIONS = {
|
|
35554
34740
|
allowGlobalReactNamespace: false,
|
|
35555
34741
|
allowUnboundBareCalls: false
|
|
@@ -35650,7 +34836,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
35650
34836
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
35651
34837
|
context.report({
|
|
35652
34838
|
node: indeterminateAttribute,
|
|
35653
|
-
message: MESSAGE$
|
|
34839
|
+
message: MESSAGE$21
|
|
35654
34840
|
});
|
|
35655
34841
|
},
|
|
35656
34842
|
CallExpression(node) {
|
|
@@ -35659,7 +34845,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
35659
34845
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
35660
34846
|
context.report({
|
|
35661
34847
|
node,
|
|
35662
|
-
message: MESSAGE$
|
|
34848
|
+
message: MESSAGE$21
|
|
35663
34849
|
});
|
|
35664
34850
|
}
|
|
35665
34851
|
};
|
|
@@ -35782,10 +34968,10 @@ const isInsideInstanceField = (node) => {
|
|
|
35782
34968
|
return false;
|
|
35783
34969
|
};
|
|
35784
34970
|
const isOneShotModuleInitialization = (node, context) => {
|
|
35785
|
-
let functionNode = findEnclosingFunction
|
|
34971
|
+
let functionNode = findEnclosingFunction(node);
|
|
35786
34972
|
while (functionNode) {
|
|
35787
34973
|
if (!executesDuringRender(functionNode, context.scopes)) return false;
|
|
35788
|
-
functionNode = findEnclosingFunction
|
|
34974
|
+
functionNode = findEnclosingFunction(functionNode);
|
|
35789
34975
|
}
|
|
35790
34976
|
return !isInsideInstanceField(node);
|
|
35791
34977
|
};
|
|
@@ -35947,7 +35133,7 @@ const noIsMounted = defineRule({
|
|
|
35947
35133
|
});
|
|
35948
35134
|
//#endregion
|
|
35949
35135
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
35950
|
-
const MESSAGE$
|
|
35136
|
+
const MESSAGE$20 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
|
|
35951
35137
|
const isJsonMethodCall = (node, method) => {
|
|
35952
35138
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
35953
35139
|
const callee = node.callee;
|
|
@@ -36011,13 +35197,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
36011
35197
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
36012
35198
|
context.report({
|
|
36013
35199
|
node,
|
|
36014
|
-
message: MESSAGE$
|
|
35200
|
+
message: MESSAGE$20
|
|
36015
35201
|
});
|
|
36016
35202
|
} })
|
|
36017
35203
|
});
|
|
36018
35204
|
//#endregion
|
|
36019
35205
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
36020
|
-
const MESSAGE$
|
|
35206
|
+
const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
36021
35207
|
const isJsxElementTypeReference = (node) => {
|
|
36022
35208
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
36023
35209
|
const typeName = node.typeName;
|
|
@@ -36071,7 +35257,7 @@ const noJsxElementType = defineRule({
|
|
|
36071
35257
|
if (isJsxImported) return;
|
|
36072
35258
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
36073
35259
|
node: typeAnnotation,
|
|
36074
|
-
message: MESSAGE$
|
|
35260
|
+
message: MESSAGE$19
|
|
36075
35261
|
});
|
|
36076
35262
|
}
|
|
36077
35263
|
};
|
|
@@ -36303,221 +35489,6 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
36303
35489
|
}
|
|
36304
35490
|
});
|
|
36305
35491
|
//#endregion
|
|
36306
|
-
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
36307
|
-
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
36308
|
-
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
36309
|
-
const expression = stripParenExpression(node);
|
|
36310
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
36311
|
-
const propertyName = getStaticPropertyName(expression);
|
|
36312
|
-
const receiver = stripParenExpression(expression.object);
|
|
36313
|
-
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
36314
|
-
}
|
|
36315
|
-
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36316
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
36317
|
-
const symbol = scopes.symbolFor(expression);
|
|
36318
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
36319
|
-
visitedSymbolIds.add(symbol.id);
|
|
36320
|
-
if (isImportedFromReact(symbol)) {
|
|
36321
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
36322
|
-
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
36323
|
-
}
|
|
36324
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36325
|
-
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
36326
|
-
};
|
|
36327
|
-
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36328
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
36329
|
-
visitedClassNodes.add(classNode);
|
|
36330
|
-
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36331
|
-
};
|
|
36332
|
-
//#endregion
|
|
36333
|
-
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
36334
|
-
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
36335
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
36336
|
-
let containsReactHookCall = false;
|
|
36337
|
-
walkAst(functionNode.body, (node) => {
|
|
36338
|
-
if (containsReactHookCall) return false;
|
|
36339
|
-
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
36340
|
-
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36341
|
-
containsReactHookCall = true;
|
|
36342
|
-
return false;
|
|
36343
|
-
}
|
|
36344
|
-
});
|
|
36345
|
-
return containsReactHookCall;
|
|
36346
|
-
};
|
|
36347
|
-
//#endregion
|
|
36348
|
-
//#region src/plugin/utils/function-returns-props-children.ts
|
|
36349
|
-
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
36350
|
-
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
36351
|
-
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
36352
|
-
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
36353
|
-
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
36354
|
-
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
36355
|
-
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
36356
|
-
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
36357
|
-
const propertyValue = stripParenExpression(property.value);
|
|
36358
|
-
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
36359
|
-
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
36360
|
-
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
36361
|
-
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
36362
|
-
}
|
|
36363
|
-
}
|
|
36364
|
-
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
36365
|
-
const candidate = stripParenExpression(expression);
|
|
36366
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
36367
|
-
const symbol = scopes.symbolFor(candidate);
|
|
36368
|
-
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
36369
|
-
}
|
|
36370
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
36371
|
-
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
36372
|
-
const receiver = stripParenExpression(candidate.object);
|
|
36373
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
36374
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
36375
|
-
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
36376
|
-
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
36377
|
-
}, controlFlow);
|
|
36378
|
-
};
|
|
36379
|
-
//#endregion
|
|
36380
|
-
//#region src/plugin/utils/function-returns-only-null.ts
|
|
36381
|
-
const isNullExpression = (expression) => {
|
|
36382
|
-
const candidate = stripParenExpression(expression);
|
|
36383
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
36384
|
-
};
|
|
36385
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
36386
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
36387
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
36388
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
36389
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
36390
|
-
};
|
|
36391
|
-
//#endregion
|
|
36392
|
-
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
36393
|
-
const findFactoryRoot = (node) => {
|
|
36394
|
-
const candidate = stripParenExpression(node);
|
|
36395
|
-
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
36396
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
36397
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
36398
|
-
return null;
|
|
36399
|
-
};
|
|
36400
|
-
const findFactoryPropertyName = (node) => {
|
|
36401
|
-
const candidate = stripParenExpression(node);
|
|
36402
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
36403
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
36404
|
-
return null;
|
|
36405
|
-
};
|
|
36406
|
-
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
36407
|
-
const candidate = stripParenExpression(expression);
|
|
36408
|
-
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
36409
|
-
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
36410
|
-
if (!factoryRoot) return false;
|
|
36411
|
-
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
36412
|
-
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
36413
|
-
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
36414
|
-
if (!symbol || symbol.kind !== "import") return false;
|
|
36415
|
-
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
36416
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
36417
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
36418
|
-
};
|
|
36419
|
-
//#endregion
|
|
36420
|
-
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
36421
|
-
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
36422
|
-
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
36423
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
36424
|
-
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36425
|
-
const candidate = stripParenExpression(expression);
|
|
36426
|
-
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
36427
|
-
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
36428
|
-
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
36429
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
36430
|
-
const symbol = scopes.symbolFor(candidate);
|
|
36431
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
36432
|
-
visitedSymbolIds.add(symbol.id);
|
|
36433
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
36434
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
36435
|
-
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
36436
|
-
}
|
|
36437
|
-
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
36438
|
-
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
36439
|
-
const factoryCallee = stripParenExpression(candidate.callee);
|
|
36440
|
-
if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
|
|
36441
|
-
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36442
|
-
const wrappedComponent = candidate.arguments[0];
|
|
36443
|
-
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
36444
|
-
}
|
|
36445
|
-
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
36446
|
-
const factory = candidate.arguments[0];
|
|
36447
|
-
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
36448
|
-
const unwrappedFactory = stripParenExpression(factory);
|
|
36449
|
-
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
36450
|
-
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
36451
|
-
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
36452
|
-
const returnedExpression = returnStatements[0]?.argument;
|
|
36453
|
-
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
36454
|
-
};
|
|
36455
|
-
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
36456
|
-
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
36457
|
-
for (const candidateSymbol of candidateSymbols) {
|
|
36458
|
-
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
36459
|
-
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
36460
|
-
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
36461
|
-
continue;
|
|
36462
|
-
}
|
|
36463
|
-
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
36464
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
36465
|
-
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
36466
|
-
continue;
|
|
36467
|
-
}
|
|
36468
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
36469
|
-
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
36470
|
-
continue;
|
|
36471
|
-
}
|
|
36472
|
-
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
36473
|
-
}
|
|
36474
|
-
return false;
|
|
36475
|
-
};
|
|
36476
|
-
//#endregion
|
|
36477
|
-
//#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
|
|
36478
|
-
const REACT_COMPONENT_TYPE_NAMES = new Set([
|
|
36479
|
-
"ComponentClass",
|
|
36480
|
-
"ComponentType",
|
|
36481
|
-
"FC",
|
|
36482
|
-
"FunctionComponent"
|
|
36483
|
-
]);
|
|
36484
|
-
const findVisibleSymbol = (identifier, scopes) => {
|
|
36485
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
36486
|
-
let scope = scopes.scopeFor(identifier);
|
|
36487
|
-
while (true) {
|
|
36488
|
-
const symbol = scope.symbolsByName.get(identifier.name);
|
|
36489
|
-
if (symbol) return symbol;
|
|
36490
|
-
if (!scope.parent) return null;
|
|
36491
|
-
scope = scope.parent;
|
|
36492
|
-
}
|
|
36493
|
-
};
|
|
36494
|
-
const isReactNamespaceType = (identifier, scopes) => {
|
|
36495
|
-
const symbol = findVisibleSymbol(identifier, scopes);
|
|
36496
|
-
return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
36497
|
-
};
|
|
36498
|
-
const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
|
|
36499
|
-
if (!typeNode) return false;
|
|
36500
|
-
if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36501
|
-
if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
|
|
36502
|
-
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
36503
|
-
const typeName = typeNode.typeName;
|
|
36504
|
-
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);
|
|
36505
|
-
if (!isNodeOfType(typeName, "Identifier")) return false;
|
|
36506
|
-
const typeSymbol = findVisibleSymbol(typeName, scopes);
|
|
36507
|
-
if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
|
|
36508
|
-
if (isImportedFromReact(typeSymbol)) {
|
|
36509
|
-
const importedName = getImportedName(typeSymbol.declarationNode);
|
|
36510
|
-
return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
|
|
36511
|
-
}
|
|
36512
|
-
if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
|
|
36513
|
-
visitedSymbolIds.add(typeSymbol.id);
|
|
36514
|
-
return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36515
|
-
};
|
|
36516
|
-
const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
|
|
36517
|
-
const bindingIdentifier = symbol.bindingIdentifier;
|
|
36518
|
-
return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
|
|
36519
|
-
};
|
|
36520
|
-
//#endregion
|
|
36521
35492
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
36522
35493
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
36523
35494
|
"childContextTypes",
|
|
@@ -36528,6 +35499,15 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
36528
35499
|
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.`;
|
|
36529
35500
|
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.";
|
|
36530
35501
|
};
|
|
35502
|
+
const isInsideClassBody = (node) => {
|
|
35503
|
+
let current = node.parent;
|
|
35504
|
+
while (current) {
|
|
35505
|
+
if (isNodeOfType(current, "ClassBody")) return true;
|
|
35506
|
+
if (isFunctionLike$2(current)) return false;
|
|
35507
|
+
current = current.parent;
|
|
35508
|
+
}
|
|
35509
|
+
return false;
|
|
35510
|
+
};
|
|
36531
35511
|
const noLegacyContextApi = defineRule({
|
|
36532
35512
|
id: "no-legacy-context-api",
|
|
36533
35513
|
title: "Legacy context API",
|
|
@@ -36542,7 +35522,6 @@ const noLegacyContextApi = defineRule({
|
|
|
36542
35522
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
36543
35523
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
36544
35524
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
36545
|
-
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
36546
35525
|
context.report({
|
|
36547
35526
|
node: memberNode.key,
|
|
36548
35527
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -36550,8 +35529,6 @@ const noLegacyContextApi = defineRule({
|
|
|
36550
35529
|
};
|
|
36551
35530
|
return {
|
|
36552
35531
|
ClassBody(node) {
|
|
36553
|
-
const classNode = node.parent;
|
|
36554
|
-
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
36555
35532
|
for (const member of node.body ?? []) checkMember(member);
|
|
36556
35533
|
},
|
|
36557
35534
|
AssignmentExpression(node) {
|
|
@@ -36561,11 +35538,9 @@ const noLegacyContextApi = defineRule({
|
|
|
36561
35538
|
if (left.computed) return;
|
|
36562
35539
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
36563
35540
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
36564
|
-
if (left.
|
|
36565
|
-
|
|
36566
|
-
if (
|
|
36567
|
-
const symbol = context.scopes.symbolFor(component);
|
|
36568
|
-
if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
|
|
35541
|
+
if (!isNodeOfType(left.object, "Identifier")) return;
|
|
35542
|
+
if (!isUppercaseName(left.object.name)) return;
|
|
35543
|
+
if (isInsideClassBody(node)) return;
|
|
36569
35544
|
context.report({
|
|
36570
35545
|
node: left,
|
|
36571
35546
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -36769,10 +35744,10 @@ const getMethodCalls = (functionNode, scopes) => {
|
|
|
36769
35744
|
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
36770
35745
|
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
36771
35746
|
visitedFunctionNodes.add(functionNode);
|
|
36772
|
-
const usageFunction = findEnclosingFunction
|
|
35747
|
+
const usageFunction = findEnclosingFunction(usageNode);
|
|
36773
35748
|
const immediateCall = getDirectCallForExpression(functionNode);
|
|
36774
35749
|
if (immediateCall) {
|
|
36775
|
-
const immediateCallFunction = findEnclosingFunction
|
|
35750
|
+
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
36776
35751
|
if (immediateCallFunction === usageFunction) {
|
|
36777
35752
|
const immediateCallStart = getRangeStart(immediateCall);
|
|
36778
35753
|
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
@@ -36781,7 +35756,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
36781
35756
|
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
36782
35757
|
}
|
|
36783
35758
|
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
36784
|
-
const methodCallFunction = findEnclosingFunction
|
|
35759
|
+
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
36785
35760
|
if (methodCallFunction === usageFunction) {
|
|
36786
35761
|
const methodCallStart = getRangeStart(methodCall);
|
|
36787
35762
|
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
@@ -36804,7 +35779,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
36804
35779
|
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
36805
35780
|
const call = getDirectCallForExpression(child);
|
|
36806
35781
|
if (!call) return false;
|
|
36807
|
-
const callFunction = findEnclosingFunction
|
|
35782
|
+
const callFunction = findEnclosingFunction(call);
|
|
36808
35783
|
if (callFunction === usageFunction) {
|
|
36809
35784
|
const callStart = getRangeStart(call);
|
|
36810
35785
|
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
@@ -36835,14 +35810,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
|
|
|
36835
35810
|
visitedMutationSymbolIds.add(symbol.id);
|
|
36836
35811
|
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
36837
35812
|
if (typeof usageBoundary !== "number") return true;
|
|
36838
|
-
const usageFunction = findEnclosingFunction
|
|
35813
|
+
const usageFunction = findEnclosingFunction(usageNode);
|
|
36839
35814
|
return symbol.references.some((reference) => {
|
|
36840
35815
|
const referenceStart = getRangeStart(reference.identifier);
|
|
36841
35816
|
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
36842
35817
|
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
36843
35818
|
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
36844
35819
|
if (referenceStart === null) return true;
|
|
36845
|
-
const mutationFunction = findEnclosingFunction
|
|
35820
|
+
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
36846
35821
|
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
36847
35822
|
if (!mutationFunction) return usageFunction !== null;
|
|
36848
35823
|
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
@@ -37387,7 +36362,7 @@ const noMoment = defineRule({
|
|
|
37387
36362
|
});
|
|
37388
36363
|
//#endregion
|
|
37389
36364
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
37390
|
-
const MESSAGE$
|
|
36365
|
+
const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
37391
36366
|
const resolveSettings$16 = (settings) => {
|
|
37392
36367
|
const reactDoctor = settings?.["react-doctor"];
|
|
37393
36368
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -37424,6 +36399,13 @@ const isReactNamespaceExpression = (node, scopes) => {
|
|
|
37424
36399
|
return isReactNamespaceImport(node, scopes);
|
|
37425
36400
|
};
|
|
37426
36401
|
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));
|
|
36402
|
+
const getDestructuredPropertyName$1 = (symbol) => {
|
|
36403
|
+
const property = symbol.bindingIdentifier.parent;
|
|
36404
|
+
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
36405
|
+
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
36406
|
+
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
36407
|
+
return null;
|
|
36408
|
+
};
|
|
37427
36409
|
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
37428
36410
|
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
37429
36411
|
visitedSymbolIds.add(symbol.id);
|
|
@@ -37433,7 +36415,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
|
37433
36415
|
}
|
|
37434
36416
|
const init = symbol.initializer;
|
|
37435
36417
|
if (!init) return false;
|
|
37436
|
-
const destructuredPropertyName =
|
|
36418
|
+
const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
|
|
37437
36419
|
if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
|
|
37438
36420
|
if (isReactHocMemberReference(init, scopes)) return true;
|
|
37439
36421
|
if (isNodeOfType(init, "Identifier")) {
|
|
@@ -37739,7 +36721,7 @@ const noMultiComp = defineRule({
|
|
|
37739
36721
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
37740
36722
|
for (const component of flagged.slice(1)) context.report({
|
|
37741
36723
|
node: component.reportNode,
|
|
37742
|
-
message: MESSAGE$
|
|
36724
|
+
message: MESSAGE$18
|
|
37743
36725
|
});
|
|
37744
36726
|
} };
|
|
37745
36727
|
}
|
|
@@ -37952,7 +36934,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
37952
36934
|
};
|
|
37953
36935
|
//#endregion
|
|
37954
36936
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
37955
|
-
const MESSAGE$
|
|
36937
|
+
const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
|
|
37956
36938
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
37957
36939
|
"copyWithin",
|
|
37958
36940
|
"fill",
|
|
@@ -38161,7 +37143,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
38161
37143
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
38162
37144
|
context.report({
|
|
38163
37145
|
node: options.crossFileConsumerCallSite,
|
|
38164
|
-
message: `${MESSAGE$
|
|
37146
|
+
message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
38165
37147
|
});
|
|
38166
37148
|
return;
|
|
38167
37149
|
}
|
|
@@ -38170,7 +37152,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
38170
37152
|
reportedNodes.add(mutation.node);
|
|
38171
37153
|
context.report({
|
|
38172
37154
|
node: mutation.node,
|
|
38173
|
-
message: MESSAGE$
|
|
37155
|
+
message: MESSAGE$17
|
|
38174
37156
|
});
|
|
38175
37157
|
}
|
|
38176
37158
|
};
|
|
@@ -38577,7 +37559,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
38577
37559
|
});
|
|
38578
37560
|
//#endregion
|
|
38579
37561
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
38580
|
-
const MESSAGE$
|
|
37562
|
+
const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
38581
37563
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
38582
37564
|
"onKeyDown",
|
|
38583
37565
|
"onKeyUp",
|
|
@@ -38696,7 +37678,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38696
37678
|
if (numeric === null) {
|
|
38697
37679
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
38698
37680
|
node: tabIndex,
|
|
38699
|
-
message: MESSAGE$
|
|
37681
|
+
message: MESSAGE$16
|
|
38700
37682
|
});
|
|
38701
37683
|
return;
|
|
38702
37684
|
}
|
|
@@ -38718,7 +37700,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38718
37700
|
if (!roleAttribute) {
|
|
38719
37701
|
context.report({
|
|
38720
37702
|
node: tabIndex,
|
|
38721
|
-
message: MESSAGE$
|
|
37703
|
+
message: MESSAGE$16
|
|
38722
37704
|
});
|
|
38723
37705
|
return;
|
|
38724
37706
|
}
|
|
@@ -38732,7 +37714,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38732
37714
|
}
|
|
38733
37715
|
context.report({
|
|
38734
37716
|
node: tabIndex,
|
|
38735
|
-
message: MESSAGE$
|
|
37717
|
+
message: MESSAGE$16
|
|
38736
37718
|
});
|
|
38737
37719
|
} };
|
|
38738
37720
|
}
|
|
@@ -39159,6 +38141,8 @@ const getWrapperHookWrappedFunction = (initializer) => {
|
|
|
39159
38141
|
if (!wrapped || !isFunctionLike$2(wrapped)) return null;
|
|
39160
38142
|
return wrapped;
|
|
39161
38143
|
};
|
|
38144
|
+
const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
|
|
38145
|
+
const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
|
|
39162
38146
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
39163
38147
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
39164
38148
|
if (!isProp(analysis, innerRef)) return false;
|
|
@@ -39187,6 +38171,12 @@ const getDeclarationKind = (declarator) => {
|
|
|
39187
38171
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
39188
38172
|
};
|
|
39189
38173
|
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
38174
|
+
const getDestructuredPropertyName = (bindingIdentifier) => {
|
|
38175
|
+
const property = bindingIdentifier.parent;
|
|
38176
|
+
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
38177
|
+
if (property.computed) return isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? String(property.key.value) : null;
|
|
38178
|
+
return isNodeOfType(property.key, "Identifier") ? property.key.name : null;
|
|
38179
|
+
};
|
|
39190
38180
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
39191
38181
|
const unwrappedExpression = stripParenExpression(expression);
|
|
39192
38182
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -39196,7 +38186,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
39196
38186
|
if (isProp(analysis, callbackReference)) {
|
|
39197
38187
|
if (isWholePropsObjectReference(analysis, callbackReference)) return null;
|
|
39198
38188
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
39199
|
-
return (bindingIdentifier &&
|
|
38189
|
+
return (bindingIdentifier && getDestructuredPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
39200
38190
|
}
|
|
39201
38191
|
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
39202
38192
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -39206,7 +38196,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
39206
38196
|
visitedVariables.add(callbackVariable);
|
|
39207
38197
|
if (isNodeOfType(declarator.id, "ObjectPattern")) {
|
|
39208
38198
|
const bindingIdentifier = callbackVariable.defs[0]?.name;
|
|
39209
|
-
const propertyName = bindingIdentifier ?
|
|
38199
|
+
const propertyName = bindingIdentifier ? getDestructuredPropertyName(bindingIdentifier) : null;
|
|
39210
38200
|
const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
|
|
39211
38201
|
return propertyName && propsReference ? propertyName : null;
|
|
39212
38202
|
}
|
|
@@ -39309,7 +38299,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
39309
38299
|
const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
|
|
39310
38300
|
if (!bindingProvenance) return null;
|
|
39311
38301
|
const { refCall, variables } = bindingProvenance;
|
|
39312
|
-
const componentFunction = findEnclosingFunction
|
|
38302
|
+
const componentFunction = findEnclosingFunction(effectCall);
|
|
39313
38303
|
if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
39314
38304
|
if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
|
|
39315
38305
|
const callbackPropNames = /* @__PURE__ */ new Set();
|
|
@@ -39473,7 +38463,7 @@ const noPassDataToParent = defineRule({
|
|
|
39473
38463
|
if (isConstant(argRef)) return false;
|
|
39474
38464
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
39475
38465
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
39476
|
-
if (
|
|
38466
|
+
if (resolvesToFunctionBinding(argRef)) return false;
|
|
39477
38467
|
const argIdentifier = argRef.identifier;
|
|
39478
38468
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
39479
38469
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
@@ -40145,6 +39135,174 @@ const noPropCallbackInRender = defineRule({
|
|
|
40145
39135
|
} })
|
|
40146
39136
|
});
|
|
40147
39137
|
//#endregion
|
|
39138
|
+
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
39139
|
+
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
39140
|
+
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
39141
|
+
const expression = stripParenExpression(node);
|
|
39142
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39143
|
+
const propertyName = getStaticPropertyName(expression);
|
|
39144
|
+
const receiver = stripParenExpression(expression.object);
|
|
39145
|
+
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
39146
|
+
}
|
|
39147
|
+
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39148
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39149
|
+
const symbol = scopes.symbolFor(expression);
|
|
39150
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
39151
|
+
visitedSymbolIds.add(symbol.id);
|
|
39152
|
+
if (isImportedFromReact(symbol)) {
|
|
39153
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
39154
|
+
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
39155
|
+
}
|
|
39156
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39157
|
+
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
39158
|
+
};
|
|
39159
|
+
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39160
|
+
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
39161
|
+
visitedClassNodes.add(classNode);
|
|
39162
|
+
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39163
|
+
};
|
|
39164
|
+
//#endregion
|
|
39165
|
+
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
39166
|
+
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
39167
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
39168
|
+
let containsReactHookCall = false;
|
|
39169
|
+
walkAst(functionNode.body, (node) => {
|
|
39170
|
+
if (containsReactHookCall) return false;
|
|
39171
|
+
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
39172
|
+
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39173
|
+
containsReactHookCall = true;
|
|
39174
|
+
return false;
|
|
39175
|
+
}
|
|
39176
|
+
});
|
|
39177
|
+
return containsReactHookCall;
|
|
39178
|
+
};
|
|
39179
|
+
//#endregion
|
|
39180
|
+
//#region src/plugin/utils/function-returns-props-children.ts
|
|
39181
|
+
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
39182
|
+
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
39183
|
+
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
39184
|
+
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
39185
|
+
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
39186
|
+
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
39187
|
+
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
39188
|
+
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
39189
|
+
const propertyValue = stripParenExpression(property.value);
|
|
39190
|
+
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
39191
|
+
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
39192
|
+
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
39193
|
+
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
39194
|
+
}
|
|
39195
|
+
}
|
|
39196
|
+
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
39197
|
+
const candidate = stripParenExpression(expression);
|
|
39198
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
39199
|
+
const symbol = scopes.symbolFor(candidate);
|
|
39200
|
+
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
39201
|
+
}
|
|
39202
|
+
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
39203
|
+
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
39204
|
+
const receiver = stripParenExpression(candidate.object);
|
|
39205
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
39206
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
39207
|
+
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
39208
|
+
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
39209
|
+
}, controlFlow);
|
|
39210
|
+
};
|
|
39211
|
+
//#endregion
|
|
39212
|
+
//#region src/plugin/utils/function-returns-only-null.ts
|
|
39213
|
+
const isNullExpression = (expression) => {
|
|
39214
|
+
const candidate = stripParenExpression(expression);
|
|
39215
|
+
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39216
|
+
};
|
|
39217
|
+
const functionReturnsOnlyNull = (functionNode) => {
|
|
39218
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
39219
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39220
|
+
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39221
|
+
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39222
|
+
};
|
|
39223
|
+
//#endregion
|
|
39224
|
+
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
39225
|
+
const findFactoryRoot = (node) => {
|
|
39226
|
+
const candidate = stripParenExpression(node);
|
|
39227
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
39228
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
39229
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
39230
|
+
return null;
|
|
39231
|
+
};
|
|
39232
|
+
const findFactoryPropertyName = (node) => {
|
|
39233
|
+
const candidate = stripParenExpression(node);
|
|
39234
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
39235
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
39236
|
+
return null;
|
|
39237
|
+
};
|
|
39238
|
+
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
39239
|
+
const candidate = stripParenExpression(expression);
|
|
39240
|
+
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
39241
|
+
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
39242
|
+
if (!factoryRoot) return false;
|
|
39243
|
+
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
39244
|
+
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
39245
|
+
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
39246
|
+
if (!symbol || symbol.kind !== "import") return false;
|
|
39247
|
+
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
39248
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
39249
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
39250
|
+
};
|
|
39251
|
+
//#endregion
|
|
39252
|
+
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
39253
|
+
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
39254
|
+
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
39255
|
+
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39256
|
+
const candidate = stripParenExpression(expression);
|
|
39257
|
+
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
39258
|
+
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
39259
|
+
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
39260
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
39261
|
+
const symbol = scopes.symbolFor(candidate);
|
|
39262
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
39263
|
+
visitedSymbolIds.add(symbol.id);
|
|
39264
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
39265
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
39266
|
+
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
39267
|
+
}
|
|
39268
|
+
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
39269
|
+
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
39270
|
+
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39271
|
+
const wrappedComponent = candidate.arguments[0];
|
|
39272
|
+
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
39273
|
+
}
|
|
39274
|
+
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
39275
|
+
const factory = candidate.arguments[0];
|
|
39276
|
+
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
39277
|
+
const unwrappedFactory = stripParenExpression(factory);
|
|
39278
|
+
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
39279
|
+
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
39280
|
+
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
39281
|
+
const returnedExpression = returnStatements[0]?.argument;
|
|
39282
|
+
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
39283
|
+
};
|
|
39284
|
+
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
39285
|
+
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
39286
|
+
for (const candidateSymbol of candidateSymbols) {
|
|
39287
|
+
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
39288
|
+
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
39289
|
+
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
39290
|
+
continue;
|
|
39291
|
+
}
|
|
39292
|
+
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
39293
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
39294
|
+
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
39295
|
+
continue;
|
|
39296
|
+
}
|
|
39297
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
39298
|
+
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
39299
|
+
continue;
|
|
39300
|
+
}
|
|
39301
|
+
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
39302
|
+
}
|
|
39303
|
+
return false;
|
|
39304
|
+
};
|
|
39305
|
+
//#endregion
|
|
40148
39306
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
40149
39307
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
40150
39308
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -40297,7 +39455,7 @@ const isModuleScopedBinding = (identifier) => {
|
|
|
40297
39455
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
40298
39456
|
if (!binding) return false;
|
|
40299
39457
|
if (isNodeOfType(binding.scopeOwner, "Program")) return true;
|
|
40300
|
-
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction
|
|
39458
|
+
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
|
|
40301
39459
|
return false;
|
|
40302
39460
|
};
|
|
40303
39461
|
const looksLikeFreshUpdateExpression = (expression) => {
|
|
@@ -40352,7 +39510,7 @@ const noRandomKey = defineRule({
|
|
|
40352
39510
|
});
|
|
40353
39511
|
//#endregion
|
|
40354
39512
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
40355
|
-
const MESSAGE$
|
|
39513
|
+
const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
40356
39514
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
40357
39515
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
40358
39516
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -40378,13 +39536,13 @@ const noReactChildren = defineRule({
|
|
|
40378
39536
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
40379
39537
|
context.report({
|
|
40380
39538
|
node: calleeOuter,
|
|
40381
|
-
message: MESSAGE$
|
|
39539
|
+
message: MESSAGE$15
|
|
40382
39540
|
});
|
|
40383
39541
|
return;
|
|
40384
39542
|
}
|
|
40385
39543
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
40386
39544
|
node: calleeOuter,
|
|
40387
|
-
message: MESSAGE$
|
|
39545
|
+
message: MESSAGE$15
|
|
40388
39546
|
});
|
|
40389
39547
|
} })
|
|
40390
39548
|
});
|
|
@@ -41065,7 +40223,7 @@ const noRenderPropChildren = defineRule({
|
|
|
41065
40223
|
});
|
|
41066
40224
|
//#endregion
|
|
41067
40225
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
41068
|
-
const MESSAGE$
|
|
40226
|
+
const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
41069
40227
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
41070
40228
|
const isUsedAsReturnValue = (parent) => {
|
|
41071
40229
|
if (!parent) return false;
|
|
@@ -41083,7 +40241,7 @@ const noRenderReturnValue = defineRule({
|
|
|
41083
40241
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
41084
40242
|
context.report({
|
|
41085
40243
|
node: node.callee,
|
|
41086
|
-
message: MESSAGE$
|
|
40244
|
+
message: MESSAGE$14
|
|
41087
40245
|
});
|
|
41088
40246
|
} })
|
|
41089
40247
|
});
|
|
@@ -41892,7 +41050,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
41892
41050
|
});
|
|
41893
41051
|
//#endregion
|
|
41894
41052
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
41895
|
-
const MESSAGE$
|
|
41053
|
+
const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
41896
41054
|
const noSetState = defineRule({
|
|
41897
41055
|
id: "no-set-state",
|
|
41898
41056
|
title: "Local class state forbidden",
|
|
@@ -41907,7 +41065,7 @@ const noSetState = defineRule({
|
|
|
41907
41065
|
if (!getParentComponent(node)) return;
|
|
41908
41066
|
context.report({
|
|
41909
41067
|
node: node.callee,
|
|
41910
|
-
message: MESSAGE$
|
|
41068
|
+
message: MESSAGE$13
|
|
41911
41069
|
});
|
|
41912
41070
|
} })
|
|
41913
41071
|
});
|
|
@@ -42214,9 +41372,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
|
42214
41372
|
return isReturnedFromEffect;
|
|
42215
41373
|
};
|
|
42216
41374
|
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
42217
|
-
let functionNode = findEnclosingFunction
|
|
41375
|
+
let functionNode = findEnclosingFunction(node);
|
|
42218
41376
|
while (functionNode) {
|
|
42219
|
-
const outerFunction = findEnclosingFunction
|
|
41377
|
+
const outerFunction = findEnclosingFunction(functionNode);
|
|
42220
41378
|
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
42221
41379
|
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
42222
41380
|
functionNode = outerFunction;
|
|
@@ -42224,7 +41382,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
|
42224
41382
|
return false;
|
|
42225
41383
|
};
|
|
42226
41384
|
const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
|
|
42227
|
-
const enclosingFunction = findEnclosingFunction
|
|
41385
|
+
const enclosingFunction = findEnclosingFunction(clearCall);
|
|
42228
41386
|
if (!isFunctionLike$2(enclosingFunction)) return false;
|
|
42229
41387
|
if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
42230
41388
|
const clearStart = getRangeStart(clearCall);
|
|
@@ -42277,7 +41435,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
42277
41435
|
};
|
|
42278
41436
|
//#endregion
|
|
42279
41437
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
42280
|
-
const MESSAGE$
|
|
41438
|
+
const MESSAGE$12 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
|
|
42281
41439
|
const DEFAULT_HANDLERS = [
|
|
42282
41440
|
"onClick",
|
|
42283
41441
|
"onMouseDown",
|
|
@@ -42392,7 +41550,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42392
41550
|
if (!roleAttribute || !roleAttribute.value) {
|
|
42393
41551
|
context.report({
|
|
42394
41552
|
node: node.name,
|
|
42395
|
-
message: MESSAGE$
|
|
41553
|
+
message: MESSAGE$12
|
|
42396
41554
|
});
|
|
42397
41555
|
return;
|
|
42398
41556
|
}
|
|
@@ -42402,7 +41560,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42402
41560
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
42403
41561
|
context.report({
|
|
42404
41562
|
node: node.name,
|
|
42405
|
-
message: MESSAGE$
|
|
41563
|
+
message: MESSAGE$12
|
|
42406
41564
|
});
|
|
42407
41565
|
return;
|
|
42408
41566
|
}
|
|
@@ -42410,7 +41568,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42410
41568
|
if (!settings.allowExpressionValues) {
|
|
42411
41569
|
context.report({
|
|
42412
41570
|
node: node.name,
|
|
42413
|
-
message: MESSAGE$
|
|
41571
|
+
message: MESSAGE$12
|
|
42414
41572
|
});
|
|
42415
41573
|
return;
|
|
42416
41574
|
}
|
|
@@ -42418,7 +41576,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42418
41576
|
if (isStaticNullishExpression(expression)) {
|
|
42419
41577
|
context.report({
|
|
42420
41578
|
node: node.name,
|
|
42421
|
-
message: MESSAGE$
|
|
41579
|
+
message: MESSAGE$12
|
|
42422
41580
|
});
|
|
42423
41581
|
return;
|
|
42424
41582
|
}
|
|
@@ -42428,7 +41586,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42428
41586
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
42429
41587
|
context.report({
|
|
42430
41588
|
node: node.name,
|
|
42431
|
-
message: MESSAGE$
|
|
41589
|
+
message: MESSAGE$12
|
|
42432
41590
|
});
|
|
42433
41591
|
return;
|
|
42434
41592
|
}
|
|
@@ -42436,7 +41594,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42436
41594
|
}
|
|
42437
41595
|
context.report({
|
|
42438
41596
|
node: node.name,
|
|
42439
|
-
message: MESSAGE$
|
|
41597
|
+
message: MESSAGE$12
|
|
42440
41598
|
});
|
|
42441
41599
|
} };
|
|
42442
41600
|
}
|
|
@@ -42542,7 +41700,7 @@ const noStringRefs = defineRule({
|
|
|
42542
41700
|
});
|
|
42543
41701
|
//#endregion
|
|
42544
41702
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
42545
|
-
const MESSAGE$
|
|
41703
|
+
const MESSAGE$11 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
|
|
42546
41704
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
42547
41705
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
42548
41706
|
const noSyncXhr = defineRule({
|
|
@@ -42563,7 +41721,7 @@ const noSyncXhr = defineRule({
|
|
|
42563
41721
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
42564
41722
|
context.report({
|
|
42565
41723
|
node,
|
|
42566
|
-
message: MESSAGE$
|
|
41724
|
+
message: MESSAGE$11
|
|
42567
41725
|
});
|
|
42568
41726
|
};
|
|
42569
41727
|
return {
|
|
@@ -42588,7 +41746,7 @@ const noSyncXhr = defineRule({
|
|
|
42588
41746
|
});
|
|
42589
41747
|
//#endregion
|
|
42590
41748
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
42591
|
-
const MESSAGE$
|
|
41749
|
+
const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
|
|
42592
41750
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
42593
41751
|
let ancestor = node.parent;
|
|
42594
41752
|
while (ancestor) {
|
|
@@ -42676,7 +41834,7 @@ const noThisInSfc = defineRule({
|
|
|
42676
41834
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
42677
41835
|
context.report({
|
|
42678
41836
|
node,
|
|
42679
|
-
message: MESSAGE$
|
|
41837
|
+
message: MESSAGE$10
|
|
42680
41838
|
});
|
|
42681
41839
|
} };
|
|
42682
41840
|
}
|
|
@@ -43054,7 +42212,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
|
|
|
43054
42212
|
return false;
|
|
43055
42213
|
};
|
|
43056
42214
|
const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
|
|
43057
|
-
const enclosingFunction = findEnclosingFunction
|
|
42215
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
43058
42216
|
if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
43059
42217
|
let currentNode = node;
|
|
43060
42218
|
while (currentNode !== enclosingFunction) {
|
|
@@ -44321,11 +43479,7 @@ const resolveSettings$8 = (settings) => {
|
|
|
44321
43479
|
propNamePattern: ruleSettings.propNamePattern ?? "render*"
|
|
44322
43480
|
};
|
|
44323
43481
|
};
|
|
44324
|
-
const
|
|
44325
|
-
allowGlobalReactNamespace: true,
|
|
44326
|
-
resolveNamedAliases: true
|
|
44327
|
-
});
|
|
44328
|
-
const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
43482
|
+
const expressionContainsJsxOrCreateElement = (root) => {
|
|
44329
43483
|
let found = false;
|
|
44330
43484
|
walkAst(root, (node) => {
|
|
44331
43485
|
if (found) return false;
|
|
@@ -44334,17 +43488,17 @@ const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
|
44334
43488
|
found = true;
|
|
44335
43489
|
return false;
|
|
44336
43490
|
}
|
|
44337
|
-
if (isNodeOfType(node, "CallExpression") &&
|
|
43491
|
+
if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
|
|
44338
43492
|
found = true;
|
|
44339
43493
|
return false;
|
|
44340
43494
|
}
|
|
44341
43495
|
});
|
|
44342
43496
|
return found;
|
|
44343
43497
|
};
|
|
44344
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode
|
|
44345
|
-
const isReactClassComponent = (classNode
|
|
43498
|
+
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
|
|
43499
|
+
const isReactClassComponent = (classNode) => {
|
|
44346
43500
|
if (isEs6Component(classNode)) return true;
|
|
44347
|
-
return expressionContainsJsxOrCreateElement(classNode
|
|
43501
|
+
return expressionContainsJsxOrCreateElement(classNode);
|
|
44348
43502
|
};
|
|
44349
43503
|
const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
44350
43504
|
let walker = node.parent;
|
|
@@ -44361,7 +43515,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
|
44361
43515
|
};
|
|
44362
43516
|
}
|
|
44363
43517
|
if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
|
|
44364
|
-
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker
|
|
43518
|
+
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
|
|
44365
43519
|
component: walker,
|
|
44366
43520
|
name: walker.id.name
|
|
44367
43521
|
};
|
|
@@ -44450,12 +43604,12 @@ const isObjectCallbackCandidate = (node) => {
|
|
|
44450
43604
|
}
|
|
44451
43605
|
return false;
|
|
44452
43606
|
};
|
|
44453
|
-
const hocCallContainsComponent = (call
|
|
43607
|
+
const hocCallContainsComponent = (call) => {
|
|
44454
43608
|
const firstArgument = call.arguments[0];
|
|
44455
43609
|
if (!firstArgument) return false;
|
|
44456
|
-
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument
|
|
44457
|
-
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument
|
|
44458
|
-
return expressionContainsJsxOrCreateElement(firstArgument
|
|
43610
|
+
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument);
|
|
43611
|
+
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument);
|
|
43612
|
+
return expressionContainsJsxOrCreateElement(firstArgument);
|
|
44459
43613
|
};
|
|
44460
43614
|
const isFirstArgumentOfHocCall = (node) => {
|
|
44461
43615
|
const parent = node.parent;
|
|
@@ -44488,35 +43642,19 @@ const isReturnOfMapCallback = (node) => {
|
|
|
44488
43642
|
}
|
|
44489
43643
|
return false;
|
|
44490
43644
|
};
|
|
43645
|
+
const isCloneElementCall = (node) => {
|
|
43646
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
43647
|
+
const callee = node.callee;
|
|
43648
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name === "cloneElement";
|
|
43649
|
+
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "cloneElement";
|
|
43650
|
+
};
|
|
44491
43651
|
const TS_VALUE_PASSTHROUGH_TYPES = new Set([
|
|
44492
43652
|
"TSAsExpression",
|
|
44493
43653
|
"TSNonNullExpression",
|
|
44494
43654
|
"TSSatisfiesExpression",
|
|
44495
43655
|
"TSTypeAssertion"
|
|
44496
43656
|
]);
|
|
44497
|
-
const
|
|
44498
|
-
"as",
|
|
44499
|
-
"body",
|
|
44500
|
-
"calendarcontainer",
|
|
44501
|
-
"component",
|
|
44502
|
-
"fallback",
|
|
44503
|
-
"tooltip"
|
|
44504
|
-
]);
|
|
44505
|
-
const isElementTypeJsxAttribute = (node) => {
|
|
44506
|
-
if (!isNodeOfType(node, "JSXAttribute")) return false;
|
|
44507
|
-
if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
|
|
44508
|
-
const attributeName = node.name.name;
|
|
44509
|
-
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
44510
|
-
};
|
|
44511
|
-
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
44512
|
-
allowGlobalReactNamespace: true,
|
|
44513
|
-
resolveNamedAliases: true
|
|
44514
|
-
});
|
|
44515
|
-
const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
|
|
44516
|
-
allowGlobalReactNamespace: true,
|
|
44517
|
-
resolveNamedAliases: true
|
|
44518
|
-
});
|
|
44519
|
-
const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
43657
|
+
const isRenderFlowingReadReference = (identifier) => {
|
|
44520
43658
|
let valueNode = identifier;
|
|
44521
43659
|
let parent = valueNode.parent;
|
|
44522
43660
|
while (parent) {
|
|
@@ -44526,26 +43664,20 @@ const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @_
|
|
|
44526
43664
|
continue;
|
|
44527
43665
|
}
|
|
44528
43666
|
switch (parent.type) {
|
|
44529
|
-
case "
|
|
44530
|
-
case "
|
|
44531
|
-
case "
|
|
44532
|
-
case "ArrowFunctionExpression": return false;
|
|
43667
|
+
case "JSXExpressionContainer":
|
|
43668
|
+
case "ReturnStatement": return true;
|
|
43669
|
+
case "ArrowFunctionExpression": return parent.body === valueNode;
|
|
44533
43670
|
case "CallExpression":
|
|
44534
43671
|
if (parent.callee === valueNode) return false;
|
|
44535
|
-
if (
|
|
44536
|
-
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
43672
|
+
if (isCreateElementCall(parent) || isCloneElementCall(parent)) return true;
|
|
44537
43673
|
valueNode = parent;
|
|
44538
43674
|
parent = parent.parent;
|
|
44539
43675
|
continue;
|
|
44540
|
-
case "VariableDeclarator":
|
|
44541
|
-
|
|
44542
|
-
const
|
|
44543
|
-
|
|
44544
|
-
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
44545
|
-
nextVisitedSymbols.add(aliasSymbol.id);
|
|
44546
|
-
return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
|
|
43676
|
+
case "VariableDeclarator": return parent.init === valueNode && isNodeOfType(parent.id, "Identifier") && isReactComponentName(parent.id.name);
|
|
43677
|
+
case "AssignmentExpression": {
|
|
43678
|
+
const assignmentTarget = parent.left;
|
|
43679
|
+
return parent.right === valueNode && isNodeOfType(assignmentTarget, "Identifier") && isReactComponentName(assignmentTarget.name);
|
|
44547
43680
|
}
|
|
44548
|
-
case "AssignmentExpression": return false;
|
|
44549
43681
|
case "Property":
|
|
44550
43682
|
if (parent.value !== valueNode) return false;
|
|
44551
43683
|
valueNode = parent;
|
|
@@ -44583,7 +43715,6 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44583
43715
|
severity: "warn",
|
|
44584
43716
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
44585
43717
|
category: "Performance",
|
|
44586
|
-
tags: ["react-jsx-only"],
|
|
44587
43718
|
create: (context) => {
|
|
44588
43719
|
const settings = resolveSettings$8(context.settings);
|
|
44589
43720
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -44646,7 +43777,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44646
43777
|
},
|
|
44647
43778
|
Identifier(node) {
|
|
44648
43779
|
if (!isReactComponentName(node.name)) return;
|
|
44649
|
-
if (!isRenderFlowingReadReference(node
|
|
43780
|
+
if (!isRenderFlowingReadReference(node)) return;
|
|
44650
43781
|
recordInstantiation(node, node.name);
|
|
44651
43782
|
},
|
|
44652
43783
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -44655,33 +43786,28 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44655
43786
|
ClassDeclaration(node) {
|
|
44656
43787
|
if (!node.id) return;
|
|
44657
43788
|
if (!isReactComponentName(node.id.name)) return;
|
|
44658
|
-
if (!isReactClassComponent(node
|
|
43789
|
+
if (!isReactClassComponent(node)) return;
|
|
44659
43790
|
enqueueCandidate(node, null);
|
|
44660
43791
|
},
|
|
44661
43792
|
ClassExpression(node) {
|
|
44662
43793
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
44663
43794
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
44664
|
-
if (!isReactClassComponent(node
|
|
43795
|
+
if (!isReactClassComponent(node)) return;
|
|
44665
43796
|
enqueueCandidate(node, null);
|
|
44666
43797
|
},
|
|
44667
43798
|
CallExpression(node) {
|
|
44668
|
-
if (
|
|
43799
|
+
if (isCreateElementCall(node)) {
|
|
44669
43800
|
const firstArgument = node.arguments[0];
|
|
44670
43801
|
if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
|
|
44671
43802
|
else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
|
|
44672
43803
|
}
|
|
44673
|
-
|
|
44674
|
-
if (!
|
|
44675
|
-
|
|
44676
|
-
const inferredName = inferFunctionLikeName(node);
|
|
44677
|
-
const propInfo = isComponentDeclaredInProp(node);
|
|
44678
|
-
if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
|
|
44679
|
-
enqueueCandidate(node, propInfo === null ? inferredName : null);
|
|
43804
|
+
if (!isHocCallee$1(node)) return;
|
|
43805
|
+
if (!hocCallContainsComponent(node)) return;
|
|
43806
|
+
enqueueCandidate(node, null);
|
|
44680
43807
|
},
|
|
44681
43808
|
"Program:exit"() {
|
|
44682
43809
|
for (const report of queuedReports) {
|
|
44683
43810
|
if (report.requiredInstantiationName !== null) {
|
|
44684
|
-
if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
|
|
44685
43811
|
if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
|
|
44686
43812
|
}
|
|
44687
43813
|
context.report({
|
|
@@ -44855,7 +43981,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
44855
43981
|
//#endregion
|
|
44856
43982
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
44857
43983
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
44858
|
-
const MESSAGE$
|
|
43984
|
+
const MESSAGE$9 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
44859
43985
|
const resolveSettings$7 = (settings) => {
|
|
44860
43986
|
const reactDoctor = settings?.["react-doctor"];
|
|
44861
43987
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -44889,7 +44015,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
44889
44015
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
44890
44016
|
context.report({
|
|
44891
44017
|
node: node.callee,
|
|
44892
|
-
message: MESSAGE$
|
|
44018
|
+
message: MESSAGE$9
|
|
44893
44019
|
});
|
|
44894
44020
|
} };
|
|
44895
44021
|
}
|
|
@@ -45714,7 +44840,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
|
|
|
45714
44840
|
const symbol = scopes.symbolFor(identifier);
|
|
45715
44841
|
if (symbol) {
|
|
45716
44842
|
if (symbol.kind !== "parameter") return null;
|
|
45717
|
-
const declaringFunction = findEnclosingFunction
|
|
44843
|
+
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45718
44844
|
if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
|
|
45719
44845
|
return null;
|
|
45720
44846
|
}
|
|
@@ -45760,7 +44886,7 @@ const isChildrenMemberExpression = (node, scopes) => {
|
|
|
45760
44886
|
const symbol = scopes.symbolFor(propsObject);
|
|
45761
44887
|
if (symbol) {
|
|
45762
44888
|
if (symbol.kind !== "parameter") return false;
|
|
45763
|
-
const declaringFunction = findEnclosingFunction
|
|
44889
|
+
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45764
44890
|
return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
|
|
45765
44891
|
}
|
|
45766
44892
|
return hasComponentLikeAncestorFunction(node);
|
|
@@ -45885,7 +45011,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
45885
45011
|
});
|
|
45886
45012
|
//#endregion
|
|
45887
45013
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
45888
|
-
const MESSAGE$
|
|
45014
|
+
const MESSAGE$8 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
|
|
45889
45015
|
const preactPreferOndblclick = defineRule({
|
|
45890
45016
|
id: "preact-prefer-ondblclick",
|
|
45891
45017
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -45900,7 +45026,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
45900
45026
|
if (!onDoubleClickAttribute) return;
|
|
45901
45027
|
context.report({
|
|
45902
45028
|
node: onDoubleClickAttribute,
|
|
45903
|
-
message: MESSAGE$
|
|
45029
|
+
message: MESSAGE$8
|
|
45904
45030
|
});
|
|
45905
45031
|
} })
|
|
45906
45032
|
});
|
|
@@ -46151,7 +45277,7 @@ const preferExplicitVariants = defineRule({
|
|
|
46151
45277
|
});
|
|
46152
45278
|
//#endregion
|
|
46153
45279
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
46154
|
-
const MESSAGE$
|
|
45280
|
+
const MESSAGE$7 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
|
|
46155
45281
|
const resolveSettings$4 = (settings) => {
|
|
46156
45282
|
const reactDoctor = settings?.["react-doctor"];
|
|
46157
45283
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -46190,7 +45316,7 @@ const preferFunctionComponent = defineRule({
|
|
|
46190
45316
|
const reportNode = node.id ?? node;
|
|
46191
45317
|
context.report({
|
|
46192
45318
|
node: reportNode,
|
|
46193
|
-
message: MESSAGE$
|
|
45319
|
+
message: MESSAGE$7
|
|
46194
45320
|
});
|
|
46195
45321
|
};
|
|
46196
45322
|
return {
|
|
@@ -46412,7 +45538,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
|
|
|
46412
45538
|
//#endregion
|
|
46413
45539
|
//#region src/plugin/utils/enclosing-component-or-hook-scope.ts
|
|
46414
45540
|
const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
|
|
46415
|
-
const functionNode = findEnclosingFunction
|
|
45541
|
+
const functionNode = findEnclosingFunction(startNode);
|
|
46416
45542
|
if (!functionNode) return null;
|
|
46417
45543
|
const displayName = componentOrHookDisplayNameForFunction(functionNode);
|
|
46418
45544
|
if (!displayName) return null;
|
|
@@ -47474,7 +46600,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
|
|
|
47474
46600
|
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
47475
46601
|
const isHookReturnForwardingSpread = (objectExpression) => {
|
|
47476
46602
|
const objectParent = objectExpression.parent;
|
|
47477
|
-
const enclosingFunction = findEnclosingFunction
|
|
46603
|
+
const enclosingFunction = findEnclosingFunction(objectExpression);
|
|
47478
46604
|
if (!enclosingFunction) return false;
|
|
47479
46605
|
if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
|
|
47480
46606
|
const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
|
|
@@ -47768,258 +46894,7 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
47768
46894
|
}
|
|
47769
46895
|
});
|
|
47770
46896
|
//#endregion
|
|
47771
|
-
//#region src/plugin/utils/is-node-conditionally-executed.ts
|
|
47772
|
-
const isNodeConditionallyExecuted = (node, boundary) => {
|
|
47773
|
-
let child = node;
|
|
47774
|
-
let parent = child.parent ?? null;
|
|
47775
|
-
while (parent && parent !== boundary) {
|
|
47776
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) return true;
|
|
47777
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) return true;
|
|
47778
|
-
if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) return true;
|
|
47779
|
-
child = parent;
|
|
47780
|
-
parent = child.parent ?? null;
|
|
47781
|
-
}
|
|
47782
|
-
return false;
|
|
47783
|
-
};
|
|
47784
|
-
//#endregion
|
|
47785
|
-
//#region src/plugin/rules/tanstack-query/utils/resolve-tanstack-query-hook-name.ts
|
|
47786
|
-
const resolveTanstackNamespaceHookName = (memberExpression, contextNode, scopes) => {
|
|
47787
|
-
const hookName = getStaticPropertyKeyName(memberExpression, { allowComputedString: true });
|
|
47788
|
-
const namespaceObject = stripParenExpression(memberExpression.object);
|
|
47789
|
-
if (!hookName || !TANSTACK_QUERY_HOOKS.has(hookName)) return null;
|
|
47790
|
-
if (!isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
47791
|
-
const resolvedNamespaceSymbol = resolveConstIdentifierAlias(namespaceObject, scopes);
|
|
47792
|
-
if (resolvedNamespaceSymbol?.kind !== "import") return null;
|
|
47793
|
-
const namespaceBinding = getImportBindingForName(contextNode, resolvedNamespaceSymbol.name);
|
|
47794
|
-
return namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source) ? hookName : null;
|
|
47795
|
-
};
|
|
47796
|
-
const resolveTanstackQueryHookName = (callExpression, scopes) => {
|
|
47797
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
47798
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
47799
|
-
const resolvedSymbol = resolveConstIdentifierAlias(callee, scopes);
|
|
47800
|
-
if (!resolvedSymbol) return null;
|
|
47801
|
-
if (resolvedSymbol.kind === "const" && resolvedSymbol.initializer) {
|
|
47802
|
-
const initializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47803
|
-
return isNodeOfType(initializer, "MemberExpression") ? resolveTanstackNamespaceHookName(initializer, callExpression, scopes) : null;
|
|
47804
|
-
}
|
|
47805
|
-
if (resolvedSymbol.kind !== "import") return null;
|
|
47806
|
-
const importBinding = getImportBindingForName(callExpression, resolvedSymbol.name);
|
|
47807
|
-
if (importBinding === null) return null;
|
|
47808
|
-
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
47809
|
-
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
47810
|
-
}
|
|
47811
|
-
if (isNodeOfType(callee, "MemberExpression")) return resolveTanstackNamespaceHookName(callee, callExpression, scopes);
|
|
47812
|
-
return null;
|
|
47813
|
-
};
|
|
47814
|
-
const resolveTanstackQueryHookNameFromInitializer = (initializer, scopes) => {
|
|
47815
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
47816
|
-
if (isNodeOfType(unwrappedInitializer, "CallExpression")) return resolveTanstackQueryHookName(unwrappedInitializer, scopes);
|
|
47817
|
-
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return null;
|
|
47818
|
-
const resolvedSymbol = resolveConstIdentifierAlias(unwrappedInitializer, scopes);
|
|
47819
|
-
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
47820
|
-
const resolvedInitializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47821
|
-
if (!isNodeOfType(resolvedInitializer, "CallExpression")) return null;
|
|
47822
|
-
return resolveTanstackQueryHookName(resolvedInitializer, scopes);
|
|
47823
|
-
};
|
|
47824
|
-
//#endregion
|
|
47825
46897
|
//#region src/plugin/rules/tanstack-query/query-no-query-in-effect.ts
|
|
47826
|
-
const isTanstackQueryResult = (expression, context) => Boolean(resolveTanstackQueryHookNameFromInitializer(expression, context.scopes));
|
|
47827
|
-
const isStaticRefetchMember = (memberExpression) => getStaticPropertyKeyName(memberExpression, { allowComputedString: true }) === "refetch";
|
|
47828
|
-
const resolveCalledFunction = (callee, context) => {
|
|
47829
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
47830
|
-
if (isFunctionLike$2(unwrappedCallee)) return unwrappedCallee;
|
|
47831
|
-
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
47832
|
-
const symbol = resolveConstIdentifierAlias(unwrappedCallee, context.scopes);
|
|
47833
|
-
if (!symbol) return null;
|
|
47834
|
-
const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
|
|
47835
|
-
return candidate && isFunctionLike$2(candidate) ? candidate : null;
|
|
47836
|
-
};
|
|
47837
|
-
const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
47838
|
-
if (!isFunctionLike$2(functionNode)) return true;
|
|
47839
|
-
if (functionNode.generator) return true;
|
|
47840
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47841
|
-
if (boundaryStart === null) return true;
|
|
47842
|
-
let hasSuspension = false;
|
|
47843
|
-
walkAst(functionNode, (node) => {
|
|
47844
|
-
if (node !== functionNode && isFunctionLike$2(node)) return false;
|
|
47845
|
-
if (!isNodeOfType(node, "AwaitExpression") || !isNodeReachableWithinFunction(node, context)) return;
|
|
47846
|
-
const suspensionStart = getRangeStart(node);
|
|
47847
|
-
if (suspensionStart !== null && suspensionStart < boundaryStart) {
|
|
47848
|
-
hasSuspension = true;
|
|
47849
|
-
return false;
|
|
47850
|
-
}
|
|
47851
|
-
});
|
|
47852
|
-
return hasSuspension;
|
|
47853
|
-
};
|
|
47854
|
-
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47855
|
-
let enclosingFunction = findEnclosingFunction$1(functionNode);
|
|
47856
|
-
while (enclosingFunction) {
|
|
47857
|
-
if (enclosingFunction === ancestor) return true;
|
|
47858
|
-
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47859
|
-
}
|
|
47860
|
-
return false;
|
|
47861
|
-
};
|
|
47862
|
-
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 === "??="));
|
|
47863
|
-
const functionInvokesTarget = (callerFunction, targetFunction, context, visitedFunctions, canCrossSuspension = false) => {
|
|
47864
|
-
if (visitedFunctions.has(callerFunction)) return false;
|
|
47865
|
-
visitedFunctions.add(callerFunction);
|
|
47866
|
-
let invokesTarget = false;
|
|
47867
|
-
walkAst(callerFunction, (node) => {
|
|
47868
|
-
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47869
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47870
|
-
if (!isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context) || !canCrossSuspension && hasSuspensionBefore(callerFunction, node, context)) return;
|
|
47871
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47872
|
-
if (calledFunction === targetFunction || calledFunction && functionInvokesTarget(calledFunction, targetFunction, context, visitedFunctions, canCrossSuspension)) {
|
|
47873
|
-
invokesTarget = true;
|
|
47874
|
-
return false;
|
|
47875
|
-
}
|
|
47876
|
-
});
|
|
47877
|
-
return invokesTarget;
|
|
47878
|
-
};
|
|
47879
|
-
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47880
|
-
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47881
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47882
|
-
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47883
|
-
let isInvokedBefore = false;
|
|
47884
|
-
walkAst(boundaryFunction, (node) => {
|
|
47885
|
-
if (node !== boundaryFunction && isFunctionLike$2(node)) return false;
|
|
47886
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47887
|
-
const callStart = getRangeStart(node);
|
|
47888
|
-
if (callStart === null || callStart >= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, boundaryFunction, context) || hasSuspensionBefore(boundaryFunction, node, context)) return;
|
|
47889
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47890
|
-
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set())) {
|
|
47891
|
-
isInvokedBefore = true;
|
|
47892
|
-
return false;
|
|
47893
|
-
}
|
|
47894
|
-
});
|
|
47895
|
-
return isInvokedBefore;
|
|
47896
|
-
};
|
|
47897
|
-
const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, context) => {
|
|
47898
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47899
|
-
if (boundaryStart === null) return false;
|
|
47900
|
-
let isInvokedAfter = false;
|
|
47901
|
-
walkAst(callerFunction, (node) => {
|
|
47902
|
-
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47903
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47904
|
-
const callStart = getRangeStart(node);
|
|
47905
|
-
if (callStart === null || callStart <= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context)) return;
|
|
47906
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47907
|
-
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set(), true)) {
|
|
47908
|
-
isInvokedAfter = true;
|
|
47909
|
-
return false;
|
|
47910
|
-
}
|
|
47911
|
-
});
|
|
47912
|
-
return isInvokedAfter;
|
|
47913
|
-
};
|
|
47914
|
-
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47915
|
-
const writeStart = getRangeStart(writeNode);
|
|
47916
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47917
|
-
const writeFunction = findEnclosingFunction$1(writeNode);
|
|
47918
|
-
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47919
|
-
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47920
|
-
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47921
|
-
const renderFunction = deferredExecutionFunction ? findEnclosingFunction$1(deferredExecutionFunction) : null;
|
|
47922
|
-
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47923
|
-
if (!writeFunction) return writeStart < boundaryStart;
|
|
47924
|
-
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
47925
|
-
if (Boolean(renderFunction && (writeFunction === renderFunction || isFunctionAncestor(writeFunction, renderFunction)))) return true;
|
|
47926
|
-
if (deferredExecutionFunction && boundaryFunction !== deferredExecutionFunction) {
|
|
47927
|
-
if (hasSuspensionBefore(boundaryFunction, boundary, context) && isFunctionInvokedBefore(boundaryFunction, writeNode, context)) return true;
|
|
47928
|
-
return isFunctionInvokedAfter(boundaryFunction, writeNode, writeFunction, context);
|
|
47929
|
-
}
|
|
47930
|
-
return writeStart < boundaryStart;
|
|
47931
|
-
}
|
|
47932
|
-
if (renderFunction && isFunctionAncestor(renderFunction, writeFunction) && !hasSuspensionBefore(writeFunction, writeNode, context) && functionInvokesTarget(renderFunction, writeFunction, context, /* @__PURE__ */ new Set())) return true;
|
|
47933
|
-
return !hasSuspensionBefore(writeFunction, writeNode, context) && isFunctionInvokedBefore(writeFunction, boundary, context);
|
|
47934
|
-
};
|
|
47935
|
-
const getStaticStringValue = (node) => {
|
|
47936
|
-
const unwrappedNode = stripParenExpression(node);
|
|
47937
|
-
if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "string") return unwrappedNode.value;
|
|
47938
|
-
if (isNodeOfType(unwrappedNode, "TemplateLiteral") && unwrappedNode.expressions.length === 0) return getStaticTemplateLiteralValue(unwrappedNode);
|
|
47939
|
-
return null;
|
|
47940
|
-
};
|
|
47941
|
-
const isSameRefetchMember = (target, candidate, context) => {
|
|
47942
|
-
const unwrappedTarget = stripParenExpression(target);
|
|
47943
|
-
const unwrappedCandidate = stripParenExpression(candidate);
|
|
47944
|
-
if (!isNodeOfType(unwrappedTarget, "Identifier") || !isNodeOfType(unwrappedCandidate, "MemberExpression") || !isStaticRefetchMember(unwrappedCandidate)) return false;
|
|
47945
|
-
const candidateTarget = stripParenExpression(unwrappedCandidate.object);
|
|
47946
|
-
if (!isNodeOfType(candidateTarget, "Identifier")) return false;
|
|
47947
|
-
const targetSymbol = resolveConstIdentifierAlias(unwrappedTarget, context.scopes);
|
|
47948
|
-
const candidateSymbol = resolveConstIdentifierAlias(candidateTarget, context.scopes);
|
|
47949
|
-
return Boolean(targetSymbol && candidateSymbol?.id === targetSymbol.id);
|
|
47950
|
-
};
|
|
47951
|
-
const getRefetchMutationTarget = (node, context) => {
|
|
47952
|
-
if (isNodeOfType(node, "MemberExpression") && isStaticRefetchMember(node)) {
|
|
47953
|
-
const parent = node.parent;
|
|
47954
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.left === node && isSameRefetchMember(node.object, parent.right, context)) return null;
|
|
47955
|
-
return isNodeOfType(parent, "AssignmentExpression") && parent.left === node || isNodeOfType(parent, "UpdateExpression") && parent.argument === node || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" ? node.object : null;
|
|
47956
|
-
}
|
|
47957
|
-
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
47958
|
-
const callee = stripParenExpression(node.callee);
|
|
47959
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47960
|
-
const receiver = stripParenExpression(callee.object);
|
|
47961
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || context.scopes.symbolFor(receiver)) return null;
|
|
47962
|
-
const methodName = getStaticPropertyKeyName(callee, { allowComputedString: true });
|
|
47963
|
-
const target = node.arguments[0];
|
|
47964
|
-
if (!target || isNodeOfType(target, "SpreadElement")) return null;
|
|
47965
|
-
if (methodName === "defineProperty") {
|
|
47966
|
-
const propertyKey = node.arguments[1];
|
|
47967
|
-
if (!propertyKey || getStaticStringValue(propertyKey) !== "refetch") return null;
|
|
47968
|
-
const descriptor = node.arguments[2];
|
|
47969
|
-
if (isNodeOfType(descriptor, "ObjectExpression")) {
|
|
47970
|
-
const valueProperty = descriptor.properties.find((property) => isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "value");
|
|
47971
|
-
if (isNodeOfType(valueProperty, "Property") && isSameRefetchMember(target, valueProperty.value, context)) return null;
|
|
47972
|
-
}
|
|
47973
|
-
return target;
|
|
47974
|
-
}
|
|
47975
|
-
if (methodName !== "assign") return null;
|
|
47976
|
-
let finalRefetchValue = null;
|
|
47977
|
-
for (const source of node.arguments.slice(1)) {
|
|
47978
|
-
if (!isNodeOfType(source, "ObjectExpression")) continue;
|
|
47979
|
-
for (const property of source.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "refetch") finalRefetchValue = property.value;
|
|
47980
|
-
}
|
|
47981
|
-
if (!finalRefetchValue || isSameRefetchMember(target, finalRefetchValue, context)) return null;
|
|
47982
|
-
return target;
|
|
47983
|
-
};
|
|
47984
|
-
const hasRefetchMemberWriteBefore = (expression, boundary, context, deferredExecutionFunction) => {
|
|
47985
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
47986
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47987
|
-
const resultSymbol = resolveConstIdentifierAlias(unwrappedExpression, context.scopes);
|
|
47988
|
-
if (!resultSymbol) return false;
|
|
47989
|
-
const program = findProgramRoot(expression);
|
|
47990
|
-
if (!program) return true;
|
|
47991
|
-
if (getRangeStart(boundary) === null) return true;
|
|
47992
|
-
let hasWrite = false;
|
|
47993
|
-
walkAst(program, (node) => {
|
|
47994
|
-
const mutationTarget = getRefetchMutationTarget(node, context);
|
|
47995
|
-
if (!mutationTarget) return;
|
|
47996
|
-
if (!isWriteExecutedBefore(node, boundary, context, deferredExecutionFunction)) return;
|
|
47997
|
-
const object = stripParenExpression(mutationTarget);
|
|
47998
|
-
if (!isNodeOfType(object, "Identifier")) return;
|
|
47999
|
-
if (resolveConstIdentifierAlias(object, context.scopes)?.id === resultSymbol.id) {
|
|
48000
|
-
hasWrite = true;
|
|
48001
|
-
return false;
|
|
48002
|
-
}
|
|
48003
|
-
});
|
|
48004
|
-
return hasWrite;
|
|
48005
|
-
};
|
|
48006
|
-
const isTanstackRefetchExpression = (expression, context, deferredExecutionFunction, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
48007
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
48008
|
-
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return isStaticRefetchMember(unwrappedExpression) && isTanstackQueryResult(unwrappedExpression.object, context) && !hasRefetchMemberWriteBefore(unwrappedExpression.object, unwrappedExpression, context, deferredExecutionFunction);
|
|
48009
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
48010
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
48011
|
-
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read") || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
48012
|
-
visitedSymbolIds.add(symbol.id);
|
|
48013
|
-
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
48014
|
-
if (isNodeOfType(bindingProperty, "Property") && getStaticPropertyKeyName(bindingProperty, { allowComputedString: true }) === "refetch") {
|
|
48015
|
-
const initializer = symbol.declarationNode.init;
|
|
48016
|
-
return Boolean(initializer && isTanstackQueryResult(initializer, context) && !hasRefetchMemberWriteBefore(initializer, symbol.declarationNode, context, null));
|
|
48017
|
-
}
|
|
48018
|
-
return Boolean(symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer && isTanstackRefetchExpression(symbol.initializer, context, null, visitedSymbolIds));
|
|
48019
|
-
};
|
|
48020
|
-
const isTanstackRefetchCall = (callExpression, context, effectCallback) => {
|
|
48021
|
-
return isTanstackRefetchExpression(callExpression.callee, context, effectCallback);
|
|
48022
|
-
};
|
|
48023
46898
|
const queryNoQueryInEffect = defineRule({
|
|
48024
46899
|
id: "query-no-query-in-effect",
|
|
48025
46900
|
title: "Query refetch inside useEffect",
|
|
@@ -48035,7 +46910,8 @@ const queryNoQueryInEffect = defineRule({
|
|
|
48035
46910
|
walkAst(callback, (child) => {
|
|
48036
46911
|
if (child !== callback && isFunctionLike$2(child) && !effectInvokedFunctions.has(child)) return false;
|
|
48037
46912
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
48038
|
-
|
|
46913
|
+
const callee = child.callee;
|
|
46914
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "refetch" || isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "refetch") context.report({
|
|
48039
46915
|
node: child,
|
|
48040
46916
|
message: "refetch() inside useEffect duplicates work React Query already does, causing extra fetches."
|
|
48041
46917
|
});
|
|
@@ -48044,6 +46920,28 @@ const queryNoQueryInEffect = defineRule({
|
|
|
48044
46920
|
});
|
|
48045
46921
|
//#endregion
|
|
48046
46922
|
//#region src/plugin/rules/tanstack-query/query-no-rest-destructuring.ts
|
|
46923
|
+
const resolveTanstackQueryHookName = (callExpression) => {
|
|
46924
|
+
const callee = callExpression.callee;
|
|
46925
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
46926
|
+
const importBinding = getImportBindingForName(callExpression, callee.name);
|
|
46927
|
+
if (importBinding === null) return TANSTACK_QUERY_HOOKS.has(callee.name) ? callee.name : null;
|
|
46928
|
+
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
46929
|
+
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
46930
|
+
}
|
|
46931
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && TANSTACK_QUERY_HOOKS.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) {
|
|
46932
|
+
const namespaceBinding = getImportBindingForName(callExpression, callee.object.name);
|
|
46933
|
+
if (namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source)) return callee.property.name;
|
|
46934
|
+
}
|
|
46935
|
+
return null;
|
|
46936
|
+
};
|
|
46937
|
+
const resolveHookNameFromInitializer = (initializer, scopes) => {
|
|
46938
|
+
if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
|
|
46939
|
+
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
46940
|
+
const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
|
|
46941
|
+
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
46942
|
+
if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
|
|
46943
|
+
return resolveTanstackQueryHookName(resolvedSymbol.initializer);
|
|
46944
|
+
};
|
|
48047
46945
|
const queryNoRestDestructuring = defineRule({
|
|
48048
46946
|
id: "query-no-rest-destructuring",
|
|
48049
46947
|
title: "Rest destructuring on query result",
|
|
@@ -48055,7 +46953,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
48055
46953
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
48056
46954
|
if (!node.init) return;
|
|
48057
46955
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
48058
|
-
const hookName =
|
|
46956
|
+
const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
|
|
48059
46957
|
if (!hookName) return;
|
|
48060
46958
|
context.report({
|
|
48061
46959
|
node: node.id,
|
|
@@ -48190,8 +47088,8 @@ const queryStableQueryClient = defineRule({
|
|
|
48190
47088
|
create: (context) => ({ NewExpression(node) {
|
|
48191
47089
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
|
|
48192
47090
|
if (isStableHookWrapperArgument(node)) return;
|
|
48193
|
-
let enclosingFunction = findEnclosingFunction
|
|
48194
|
-
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction
|
|
47091
|
+
let enclosingFunction = findEnclosingFunction(node);
|
|
47092
|
+
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
48195
47093
|
if (!enclosingFunction) return;
|
|
48196
47094
|
if (!isComponentFunction(enclosingFunction)) return;
|
|
48197
47095
|
context.report({
|
|
@@ -48286,7 +47184,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
48286
47184
|
const comparatorArgument = node.arguments?.[1];
|
|
48287
47185
|
if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
|
|
48288
47186
|
} else {
|
|
48289
|
-
const enclosingFunction = findEnclosingFunction
|
|
47187
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
48290
47188
|
if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
|
|
48291
47189
|
}
|
|
48292
47190
|
const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
|
|
@@ -48299,7 +47197,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
48299
47197
|
});
|
|
48300
47198
|
//#endregion
|
|
48301
47199
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
48302
|
-
const MESSAGE$
|
|
47200
|
+
const MESSAGE$6 = "This JSX crashes because `React` isn't in scope.";
|
|
48303
47201
|
const reactInJsxScope = defineRule({
|
|
48304
47202
|
id: "react-in-jsx-scope",
|
|
48305
47203
|
title: "React not in scope for JSX",
|
|
@@ -48311,190 +47209,19 @@ const reactInJsxScope = defineRule({
|
|
|
48311
47209
|
if (findVariableInitializer(node, "React")) return;
|
|
48312
47210
|
context.report({
|
|
48313
47211
|
node: node.name,
|
|
48314
|
-
message: MESSAGE$
|
|
47212
|
+
message: MESSAGE$6
|
|
48315
47213
|
});
|
|
48316
47214
|
},
|
|
48317
47215
|
JSXFragment(node) {
|
|
48318
47216
|
if (findVariableInitializer(node, "React")) return;
|
|
48319
47217
|
context.report({
|
|
48320
47218
|
node: node.openingFragment,
|
|
48321
|
-
message: MESSAGE$
|
|
47219
|
+
message: MESSAGE$6
|
|
48322
47220
|
});
|
|
48323
47221
|
}
|
|
48324
47222
|
})
|
|
48325
47223
|
});
|
|
48326
47224
|
//#endregion
|
|
48327
|
-
//#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
|
|
48328
|
-
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.";
|
|
48329
|
-
const REACT_MARKDOWN_MODULE = "react-markdown";
|
|
48330
|
-
const REHYPE_RAW_MODULE = "rehype-raw";
|
|
48331
|
-
const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
|
|
48332
|
-
const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
|
|
48333
|
-
const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
|
|
48334
|
-
const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
|
|
48335
|
-
const DEFAULT_EXPORT_NAMES = new Set(["default"]);
|
|
48336
|
-
const getImportDeclaration = (symbol) => {
|
|
48337
|
-
if (symbol.kind !== "import") return null;
|
|
48338
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
48339
|
-
return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
|
|
48340
|
-
};
|
|
48341
|
-
const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
|
|
48342
|
-
const isDefaultImportSymbol = (symbol, moduleName) => {
|
|
48343
|
-
if (!isImportFromModule(symbol, moduleName)) return false;
|
|
48344
|
-
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
48345
|
-
};
|
|
48346
|
-
const resolveImportedIdentifier = (node, scopes) => {
|
|
48347
|
-
if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
|
|
48348
|
-
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
48349
|
-
return symbol?.kind === "import" ? symbol : null;
|
|
48350
|
-
};
|
|
48351
|
-
const getStaticMemberParts = (node) => {
|
|
48352
|
-
if (isNodeOfType(node, "MemberExpression")) {
|
|
48353
|
-
if (node.computed || !isNodeOfType(node.property, "Identifier")) return null;
|
|
48354
|
-
return {
|
|
48355
|
-
object: node.object,
|
|
48356
|
-
propertyName: node.property.name
|
|
48357
|
-
};
|
|
48358
|
-
}
|
|
48359
|
-
if (isNodeOfType(node, "JSXMemberExpression")) {
|
|
48360
|
-
if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
|
|
48361
|
-
return {
|
|
48362
|
-
object: node.object,
|
|
48363
|
-
propertyName: node.property.name
|
|
48364
|
-
};
|
|
48365
|
-
}
|
|
48366
|
-
return null;
|
|
48367
|
-
};
|
|
48368
|
-
const isNamespaceMemberFromModule = (node, moduleName, memberNames, scopes) => {
|
|
48369
|
-
const memberParts = getStaticMemberParts(node);
|
|
48370
|
-
if (!memberParts || !memberNames.has(memberParts.propertyName)) return false;
|
|
48371
|
-
const namespaceSymbol = resolveImportedIdentifier(memberParts.object, scopes);
|
|
48372
|
-
return Boolean(namespaceSymbol && isImportFromModule(namespaceSymbol, moduleName) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
|
|
48373
|
-
};
|
|
48374
|
-
const isReactMarkdownComponent = (node, scopes) => {
|
|
48375
|
-
if (isNodeOfType(node, "JSXIdentifier")) {
|
|
48376
|
-
const symbol = resolveImportedIdentifier(node, scopes);
|
|
48377
|
-
if (!symbol || !isImportFromModule(symbol, REACT_MARKDOWN_MODULE)) return false;
|
|
48378
|
-
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return true;
|
|
48379
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
48380
|
-
return importedName === "default" || Boolean(importedName && REACT_MARKDOWN_NAMED_EXPORTS.has(importedName));
|
|
48381
|
-
}
|
|
48382
|
-
return isNamespaceMemberFromModule(node, REACT_MARKDOWN_MODULE, REACT_MARKDOWN_NAMESPACE_EXPORTS, scopes);
|
|
48383
|
-
};
|
|
48384
|
-
const isPluginFromModule = (rawNode, moduleName, scopes, visitedSymbolIds) => {
|
|
48385
|
-
const node = stripParenExpression(rawNode);
|
|
48386
|
-
if (isNodeOfType(node, "Identifier")) {
|
|
48387
|
-
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
48388
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
48389
|
-
if (isDefaultImportSymbol(symbol, moduleName)) return true;
|
|
48390
|
-
if (symbol.kind !== "const" || !symbol.initializer) return false;
|
|
48391
|
-
visitedSymbolIds.add(symbol.id);
|
|
48392
|
-
return isPluginFromModule(symbol.initializer, moduleName, scopes, visitedSymbolIds);
|
|
48393
|
-
}
|
|
48394
|
-
if (isNamespaceMemberFromModule(node, moduleName, DEFAULT_EXPORT_NAMES, scopes)) return true;
|
|
48395
|
-
if (!isNodeOfType(node, "ArrayExpression")) return false;
|
|
48396
|
-
for (const element of node.elements) {
|
|
48397
|
-
if (!element || isNodeOfType(element, "SpreadElement")) continue;
|
|
48398
|
-
return isPluginFromModule(element, moduleName, scopes, visitedSymbolIds);
|
|
48399
|
-
}
|
|
48400
|
-
return false;
|
|
48401
|
-
};
|
|
48402
|
-
const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
|
|
48403
|
-
const node = stripParenExpression(rawNode);
|
|
48404
|
-
if (isNodeOfType(node, "Identifier")) {
|
|
48405
|
-
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
48406
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
48407
|
-
visitedSymbolIds.add(symbol.id);
|
|
48408
|
-
return collectPluginEntries(symbol.initializer, scopes, visitedSymbolIds);
|
|
48409
|
-
}
|
|
48410
|
-
if (!isNodeOfType(node, "ArrayExpression")) return null;
|
|
48411
|
-
const entries = [];
|
|
48412
|
-
for (const element of node.elements) {
|
|
48413
|
-
if (!element) continue;
|
|
48414
|
-
if (isNodeOfType(element, "SpreadElement")) {
|
|
48415
|
-
const spreadEntries = collectPluginEntries(element.argument, scopes, new Set(visitedSymbolIds));
|
|
48416
|
-
if (spreadEntries === null) return null;
|
|
48417
|
-
entries.push(...spreadEntries);
|
|
48418
|
-
continue;
|
|
48419
|
-
}
|
|
48420
|
-
entries.push(element);
|
|
48421
|
-
}
|
|
48422
|
-
return entries;
|
|
48423
|
-
};
|
|
48424
|
-
const findEffectiveExplicitAttribute = (attributes, attributeName) => {
|
|
48425
|
-
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
48426
|
-
const attribute = attributes[attributeIndex];
|
|
48427
|
-
if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
|
|
48428
|
-
if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
|
|
48429
|
-
}
|
|
48430
|
-
return null;
|
|
48431
|
-
};
|
|
48432
|
-
const getAttributeExpression = (attribute) => {
|
|
48433
|
-
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
48434
|
-
return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
|
|
48435
|
-
};
|
|
48436
|
-
const isDomPurifyNamespace = (node, scopes) => {
|
|
48437
|
-
const symbol = resolveImportedIdentifier(node, scopes);
|
|
48438
|
-
if (!symbol) return false;
|
|
48439
|
-
const importDeclaration = getImportDeclaration(symbol);
|
|
48440
|
-
if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
|
|
48441
|
-
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
48442
|
-
};
|
|
48443
|
-
const isDomPurifySanitizeCall = (node, scopes) => {
|
|
48444
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
48445
|
-
const memberParts = getStaticMemberParts(stripParenExpression(node.callee));
|
|
48446
|
-
return Boolean(memberParts && memberParts.propertyName === "sanitize" && isDomPurifyNamespace(memberParts.object, scopes));
|
|
48447
|
-
};
|
|
48448
|
-
const isStaticOrSanitizedMarkdownExpression = (rawNode, scopes, visitedSymbolIds) => {
|
|
48449
|
-
const node = stripParenExpression(rawNode);
|
|
48450
|
-
if (isNodeOfType(node, "Literal")) return true;
|
|
48451
|
-
if (isNodeOfType(node, "TemplateLiteral")) return node.expressions.every((expression) => isStaticOrSanitizedMarkdownExpression(expression, scopes, new Set(visitedSymbolIds)));
|
|
48452
|
-
if (isDomPurifySanitizeCall(node, scopes)) return true;
|
|
48453
|
-
if (isNodeOfType(node, "ConditionalExpression")) return isStaticOrSanitizedMarkdownExpression(node.consequent, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.alternate, scopes, new Set(visitedSymbolIds));
|
|
48454
|
-
if (isNodeOfType(node, "BinaryExpression") && node.operator === "+") return isStaticOrSanitizedMarkdownExpression(node.left, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.right, scopes, new Set(visitedSymbolIds));
|
|
48455
|
-
if (!isNodeOfType(node, "Identifier")) return false;
|
|
48456
|
-
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
48457
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
48458
|
-
visitedSymbolIds.add(symbol.id);
|
|
48459
|
-
return isStaticOrSanitizedMarkdownExpression(symbol.initializer, scopes, visitedSymbolIds);
|
|
48460
|
-
};
|
|
48461
|
-
const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
|
|
48462
|
-
const jsxElement = openingElement.parent;
|
|
48463
|
-
if (!isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
48464
|
-
const meaningfulChildren = jsxElement.children.filter((child) => !isNodeOfType(child, "JSXText") || child.value.trim().length > 0);
|
|
48465
|
-
if (meaningfulChildren.length > 0) return meaningfulChildren.some((child) => {
|
|
48466
|
-
if (isNodeOfType(child, "JSXText")) return false;
|
|
48467
|
-
if (!isNodeOfType(child, "JSXExpressionContainer")) return true;
|
|
48468
|
-
if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
|
|
48469
|
-
return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
|
|
48470
|
-
});
|
|
48471
|
-
const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
|
|
48472
|
-
if (!childrenAttribute) return false;
|
|
48473
|
-
const childrenExpression = getAttributeExpression(childrenAttribute);
|
|
48474
|
-
return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
|
|
48475
|
-
};
|
|
48476
|
-
const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
48477
|
-
id: "react-markdown-unsanitized-raw-html",
|
|
48478
|
-
title: "Unsanitized raw HTML in React Markdown",
|
|
48479
|
-
severity: "warn",
|
|
48480
|
-
recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
|
|
48481
|
-
create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
|
|
48482
|
-
if (!isReactMarkdownComponent(node.name, context.scopes)) return;
|
|
48483
|
-
const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
|
|
48484
|
-
if (!pluginsAttribute) return;
|
|
48485
|
-
const pluginsExpression = getAttributeExpression(pluginsAttribute);
|
|
48486
|
-
if (!pluginsExpression) return;
|
|
48487
|
-
const pluginEntries = collectPluginEntries(pluginsExpression, context.scopes, /* @__PURE__ */ new Set());
|
|
48488
|
-
if (pluginEntries === null) return;
|
|
48489
|
-
if (!pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_RAW_MODULE, context.scopes, /* @__PURE__ */ new Set()))) return;
|
|
48490
|
-
if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
|
|
48491
|
-
context.report({
|
|
48492
|
-
node,
|
|
48493
|
-
message: MESSAGE$6
|
|
48494
|
-
});
|
|
48495
|
-
} }))
|
|
48496
|
-
});
|
|
48497
|
-
//#endregion
|
|
48498
47225
|
//#region src/plugin/utils/collect-react-redux-selector-aliases.ts
|
|
48499
47226
|
const REACT_REDUX_MODULE = "react-redux";
|
|
48500
47227
|
const collectReactReduxSelectorAliases = (programRoot) => {
|
|
@@ -51391,7 +50118,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
|
|
|
51391
50118
|
};
|
|
51392
50119
|
const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
|
|
51393
50120
|
const isInsideStyleFactoryCallback = (node) => {
|
|
51394
|
-
const enclosingFunction = findEnclosingFunction
|
|
50121
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
51395
50122
|
if (!enclosingFunction) return false;
|
|
51396
50123
|
const callExpression = enclosingFunction.parent;
|
|
51397
50124
|
if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -56922,6 +55649,22 @@ const isInsideClassComponent = (node) => {
|
|
|
56922
55649
|
}
|
|
56923
55650
|
return false;
|
|
56924
55651
|
};
|
|
55652
|
+
const hasShortCircuitAncestor = (descendant, ancestor) => {
|
|
55653
|
+
let current = descendant.parent;
|
|
55654
|
+
while (current && current !== ancestor) {
|
|
55655
|
+
if (isNodeOfType(current, "ConditionalExpression") && (isWithinRange(descendant, current.consequent) || isWithinRange(descendant, current.alternate))) return true;
|
|
55656
|
+
if (isNodeOfType(current, "LogicalExpression") && (current.operator === "&&" || current.operator === "||" || current.operator === "??") && isWithinRange(descendant, current.right)) return true;
|
|
55657
|
+
if (isNodeOfType(current, "AssignmentPattern") && isWithinRange(descendant, current.right)) return true;
|
|
55658
|
+
current = current.parent ?? null;
|
|
55659
|
+
}
|
|
55660
|
+
return false;
|
|
55661
|
+
};
|
|
55662
|
+
const isWithinRange = (descendant, sibling) => {
|
|
55663
|
+
const descendantSpan = descendant;
|
|
55664
|
+
const siblingSpan = sibling;
|
|
55665
|
+
if (typeof descendantSpan.start !== "number" || typeof siblingSpan.start !== "number" || typeof siblingSpan.end !== "number") return false;
|
|
55666
|
+
return descendantSpan.start >= siblingSpan.start && (descendantSpan.end ?? siblingSpan.end) <= siblingSpan.end;
|
|
55667
|
+
};
|
|
56925
55668
|
const isInsideTry = (descendant, ancestor) => {
|
|
56926
55669
|
let current = descendant.parent;
|
|
56927
55670
|
while (current && current !== ancestor) {
|
|
@@ -57113,7 +55856,7 @@ const rulesOfHooks = defineRule({
|
|
|
57113
55856
|
});
|
|
57114
55857
|
return;
|
|
57115
55858
|
}
|
|
57116
|
-
if (
|
|
55859
|
+
if (hasShortCircuitAncestor(node, enclosing.node)) {
|
|
57117
55860
|
context.report({
|
|
57118
55861
|
node: node.callee,
|
|
57119
55862
|
message: buildConditionalMessage(hookName)
|
|
@@ -58247,14 +56990,12 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
|
|
|
58247
56990
|
}
|
|
58248
56991
|
return false;
|
|
58249
56992
|
};
|
|
58250
|
-
const declarationAwaitsGate = (declaration
|
|
56993
|
+
const declarationAwaitsGate = (declaration) => {
|
|
58251
56994
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
58252
56995
|
for (const declarator of declaration.declarations ?? []) {
|
|
58253
56996
|
if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
|
|
58254
56997
|
const argument = declarator.init.argument;
|
|
58255
56998
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
58256
|
-
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
58257
|
-
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
58258
56999
|
const calleeName = getCalleeName$2(argument);
|
|
58259
57000
|
if (!calleeName) continue;
|
|
58260
57001
|
if (isAuthGuardName(calleeName)) return true;
|
|
@@ -58282,7 +57023,7 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
58282
57023
|
if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
|
|
58283
57024
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
58284
57025
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
58285
|
-
if (declarationAwaitsGate(currentStatement
|
|
57026
|
+
if (declarationAwaitsGate(currentStatement)) continue;
|
|
58286
57027
|
context.report({
|
|
58287
57028
|
node: nextStatement,
|
|
58288
57029
|
message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
|
|
@@ -58372,26 +57113,6 @@ const stateInConstructor = defineRule({
|
|
|
58372
57113
|
}
|
|
58373
57114
|
});
|
|
58374
57115
|
//#endregion
|
|
58375
|
-
//#region src/plugin/utils/collect-jsx-runtime-imports.ts
|
|
58376
|
-
const REACT_RUNTIME_PACKAGE_PREFIXES = ["react", "react-dom"];
|
|
58377
|
-
const matchesPackage = (source, packageName) => source === packageName || source.startsWith(`${packageName}/`);
|
|
58378
|
-
const collectJsxRuntimeImports = (program) => {
|
|
58379
|
-
let hasReactRuntime = false;
|
|
58380
|
-
let hasSolidRuntime = false;
|
|
58381
|
-
for (const statement of program.body) {
|
|
58382
|
-
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
58383
|
-
if (isTypeOnlyImport(statement)) continue;
|
|
58384
|
-
const source = statement.source.value;
|
|
58385
|
-
if (typeof source !== "string") continue;
|
|
58386
|
-
if (matchesPackage(source, "solid-js")) hasSolidRuntime = true;
|
|
58387
|
-
if (REACT_RUNTIME_PACKAGE_PREFIXES.some((packageName) => matchesPackage(source, packageName))) hasReactRuntime = true;
|
|
58388
|
-
}
|
|
58389
|
-
return {
|
|
58390
|
-
hasReactRuntime,
|
|
58391
|
-
hasSolidRuntime
|
|
58392
|
-
};
|
|
58393
|
-
};
|
|
58394
|
-
//#endregion
|
|
58395
57116
|
//#region src/plugin/rules/react-builtins/style-prop-object.ts
|
|
58396
57117
|
const MESSAGE$1 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
|
|
58397
57118
|
const resolveSettings = (settings) => {
|
|
@@ -58453,13 +57174,6 @@ const isInvalidStyleExpression = (expression) => {
|
|
|
58453
57174
|
}
|
|
58454
57175
|
return false;
|
|
58455
57176
|
};
|
|
58456
|
-
const hasObjectValuedClassList = (openingElement) => openingElement.attributes.some((attribute) => {
|
|
58457
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) return false;
|
|
58458
|
-
if (!isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
58459
|
-
if (attribute.name.name !== "classList") return false;
|
|
58460
|
-
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
|
|
58461
|
-
return isNodeOfType(attribute.value.expression, "ObjectExpression");
|
|
58462
|
-
});
|
|
58463
57177
|
const stylePropObject = defineRule({
|
|
58464
57178
|
id: "style-prop-object",
|
|
58465
57179
|
title: "Style prop is not an object",
|
|
@@ -58469,21 +57183,8 @@ const stylePropObject = defineRule({
|
|
|
58469
57183
|
create: (context) => {
|
|
58470
57184
|
const { allow } = resolveSettings(context.settings);
|
|
58471
57185
|
const allowSet = new Set(allow);
|
|
58472
|
-
let fileIsProvenSolidJsx = false;
|
|
58473
57186
|
return {
|
|
58474
|
-
Program: (node) => {
|
|
58475
|
-
const runtimeImports = collectJsxRuntimeImports(node);
|
|
58476
|
-
let hasSolidSyntaxMarker = false;
|
|
58477
|
-
if (!runtimeImports.hasReactRuntime && !runtimeImports.hasSolidRuntime) walkAst(node, (descendantNode) => {
|
|
58478
|
-
if (isNodeOfType(descendantNode, "JSXOpeningElement") && hasObjectValuedClassList(descendantNode)) {
|
|
58479
|
-
hasSolidSyntaxMarker = true;
|
|
58480
|
-
return false;
|
|
58481
|
-
}
|
|
58482
|
-
});
|
|
58483
|
-
fileIsProvenSolidJsx = !runtimeImports.hasReactRuntime && (runtimeImports.hasSolidRuntime || hasSolidSyntaxMarker);
|
|
58484
|
-
},
|
|
58485
57187
|
JSXOpeningElement(node) {
|
|
58486
|
-
if (fileIsProvenSolidJsx) return;
|
|
58487
57188
|
if (!isNodeOfType(node.name, "JSXIdentifier")) return;
|
|
58488
57189
|
const elementName = resolveJsxElementType(node);
|
|
58489
57190
|
if (elementName && allowSet.has(elementName)) return;
|
|
@@ -58514,7 +57215,6 @@ const stylePropObject = defineRule({
|
|
|
58514
57215
|
}
|
|
58515
57216
|
},
|
|
58516
57217
|
CallExpression(node) {
|
|
58517
|
-
if (fileIsProvenSolidJsx) return;
|
|
58518
57218
|
if (!isCreateElementCall(node)) return;
|
|
58519
57219
|
const firstArgument = node.arguments[0];
|
|
58520
57220
|
if (!firstArgument) return;
|
|
@@ -59309,7 +58009,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
59309
58009
|
const isReturnedFromCustomHook = (functionNode) => {
|
|
59310
58010
|
const parent = functionNode.parent;
|
|
59311
58011
|
if (isNodeOfType(parent, "ReturnStatement")) {
|
|
59312
|
-
const outerFunction = findEnclosingFunction
|
|
58012
|
+
const outerFunction = findEnclosingFunction(parent);
|
|
59313
58013
|
const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
|
|
59314
58014
|
return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
|
|
59315
58015
|
}
|
|
@@ -59326,13 +58026,13 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
59326
58026
|
return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
|
|
59327
58027
|
};
|
|
59328
58028
|
const isDeferredNavigateCall = (callNode) => {
|
|
59329
|
-
let enclosingFunction = findEnclosingFunction
|
|
58029
|
+
let enclosingFunction = findEnclosingFunction(callNode);
|
|
59330
58030
|
while (enclosingFunction) {
|
|
59331
58031
|
if (isDeferredCallbackPosition(enclosingFunction)) return true;
|
|
59332
58032
|
if (isWiredAsEventHandler(enclosingFunction)) return true;
|
|
59333
58033
|
if (isReturnedFromCustomHook(enclosingFunction)) return true;
|
|
59334
58034
|
if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
|
|
59335
|
-
enclosingFunction = findEnclosingFunction
|
|
58035
|
+
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
59336
58036
|
}
|
|
59337
58037
|
return false;
|
|
59338
58038
|
};
|
|
@@ -63771,17 +62471,6 @@ const reactDoctorRules = [
|
|
|
63771
62471
|
requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
|
|
63772
62472
|
}
|
|
63773
62473
|
},
|
|
63774
|
-
{
|
|
63775
|
-
key: "react-doctor/react-markdown-unsanitized-raw-html",
|
|
63776
|
-
id: "react-markdown-unsanitized-raw-html",
|
|
63777
|
-
source: "react-doctor",
|
|
63778
|
-
originallyExternal: false,
|
|
63779
|
-
rule: {
|
|
63780
|
-
...reactMarkdownUnsanitizedRawHtml,
|
|
63781
|
-
framework: "global",
|
|
63782
|
-
category: "Security"
|
|
63783
|
-
}
|
|
63784
|
-
},
|
|
63785
62474
|
{
|
|
63786
62475
|
key: "react-doctor/redux-useselector-inline-derivation",
|
|
63787
62476
|
id: "redux-useselector-inline-derivation",
|