oxlint-plugin-react-doctor 0.7.7-dev.ce4179b → 0.7.7-dev.e5b0690
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 +724 -1834
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,13 +7,6 @@ import { analyze } from "eslint-scope";
|
|
|
7
7
|
//#region src/plugin/utils/is-node-of-type.ts
|
|
8
8
|
const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
|
|
9
9
|
//#endregion
|
|
10
|
-
//#region src/plugin/utils/is-type-only-import.ts
|
|
11
|
-
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
12
|
-
if (!specifiers || specifiers.length === 0) return false;
|
|
13
|
-
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
14
|
-
};
|
|
15
|
-
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
16
|
-
//#endregion
|
|
17
10
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
18
11
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
19
12
|
"solid-js",
|
|
@@ -28,33 +21,17 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
|
28
21
|
"vidode"
|
|
29
22
|
]);
|
|
30
23
|
const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
|
|
31
|
-
const REACT_JSX_DIALECT_PACKAGE_PREFIXES = [
|
|
32
|
-
"react",
|
|
33
|
-
"react-dom",
|
|
34
|
-
"preact"
|
|
35
|
-
];
|
|
36
24
|
const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
|
|
37
|
-
const
|
|
38
|
-
let hasNonReactRuntime = false;
|
|
39
|
-
let hasReactRuntime = false;
|
|
25
|
+
const fileImportsNonReactJsxDialect = (program) => {
|
|
40
26
|
for (const statement of program.body) {
|
|
41
27
|
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
42
|
-
const
|
|
43
|
-
if (isTypeOnlyImport(importDeclaration)) continue;
|
|
44
|
-
const source = importDeclaration.source;
|
|
28
|
+
const source = statement.source;
|
|
45
29
|
const value = source && typeof source.value === "string" ? source.value : null;
|
|
46
30
|
if (!value) continue;
|
|
47
|
-
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)
|
|
48
|
-
if (startsWithAny(value,
|
|
31
|
+
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)) return true;
|
|
32
|
+
if (startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) return true;
|
|
49
33
|
}
|
|
50
|
-
return
|
|
51
|
-
hasNonReactRuntime,
|
|
52
|
-
hasReactRuntime
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
const fileImportsNonReactJsxDialect = (program) => {
|
|
56
|
-
const runtimeImports = collectJsxRuntimeImports(program);
|
|
57
|
-
return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
34
|
+
return false;
|
|
58
35
|
};
|
|
59
36
|
const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
|
|
60
37
|
for (const attribute of openingNode.attributes) {
|
|
@@ -267,7 +244,6 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
|
|
|
267
244
|
const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
268
245
|
const innerVisitors = create(context);
|
|
269
246
|
let fileIsNonReactJsx = false;
|
|
270
|
-
let fileImportsReactRuntime = false;
|
|
271
247
|
const wrappedVisitors = {};
|
|
272
248
|
for (const [key, visitor] of Object.entries(innerVisitors)) {
|
|
273
249
|
if (typeof visitor !== "function") {
|
|
@@ -280,16 +256,14 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
280
256
|
}
|
|
281
257
|
if (key === "Program") {
|
|
282
258
|
wrappedVisitors.Program = (node) => {
|
|
283
|
-
|
|
284
|
-
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
285
|
-
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
259
|
+
fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
|
|
286
260
|
visitor(node);
|
|
287
261
|
};
|
|
288
262
|
continue;
|
|
289
263
|
}
|
|
290
264
|
if (key === "JSXOpeningElement") {
|
|
291
265
|
wrappedVisitors.JSXOpeningElement = (node) => {
|
|
292
|
-
if (!
|
|
266
|
+
if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
293
267
|
if (fileIsNonReactJsx) return;
|
|
294
268
|
visitor(node);
|
|
295
269
|
};
|
|
@@ -301,9 +275,7 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
301
275
|
};
|
|
302
276
|
}
|
|
303
277
|
if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
|
|
304
|
-
|
|
305
|
-
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
306
|
-
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
278
|
+
fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
|
|
307
279
|
};
|
|
308
280
|
return wrappedVisitors;
|
|
309
281
|
});
|
|
@@ -2340,7 +2312,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2340
2312
|
});
|
|
2341
2313
|
//#endregion
|
|
2342
2314
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
2343
|
-
const MESSAGE$
|
|
2315
|
+
const MESSAGE$61 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
2344
2316
|
const isTransComponentsTemplate = (node) => {
|
|
2345
2317
|
let current = node.parent;
|
|
2346
2318
|
while (current) {
|
|
@@ -2376,7 +2348,7 @@ const anchorHasContent = defineRule({
|
|
|
2376
2348
|
if (isTransComponentsTemplate(node)) return;
|
|
2377
2349
|
context.report({
|
|
2378
2350
|
node: opening.name,
|
|
2379
|
-
message: MESSAGE$
|
|
2351
|
+
message: MESSAGE$61
|
|
2380
2352
|
});
|
|
2381
2353
|
} };
|
|
2382
2354
|
}
|
|
@@ -2808,7 +2780,7 @@ const parseJsxValue = (value) => {
|
|
|
2808
2780
|
};
|
|
2809
2781
|
//#endregion
|
|
2810
2782
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2811
|
-
const MESSAGE$
|
|
2783
|
+
const MESSAGE$60 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2812
2784
|
const mayBeContentEditable = (node) => {
|
|
2813
2785
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
|
|
2814
2786
|
if (!attribute) return false;
|
|
@@ -2839,7 +2811,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2839
2811
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2840
2812
|
context.report({
|
|
2841
2813
|
node: node.name,
|
|
2842
|
-
message: MESSAGE$
|
|
2814
|
+
message: MESSAGE$60
|
|
2843
2815
|
});
|
|
2844
2816
|
return;
|
|
2845
2817
|
}
|
|
@@ -2847,7 +2819,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2847
2819
|
if (mayBeContentEditable(node)) return;
|
|
2848
2820
|
context.report({
|
|
2849
2821
|
node: node.name,
|
|
2850
|
-
message: MESSAGE$
|
|
2822
|
+
message: MESSAGE$60
|
|
2851
2823
|
});
|
|
2852
2824
|
} })
|
|
2853
2825
|
});
|
|
@@ -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({
|
|
@@ -20815,7 +20025,7 @@ const jsxMaxDepth = defineRule({
|
|
|
20815
20025
|
});
|
|
20816
20026
|
//#endregion
|
|
20817
20027
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
20818
|
-
const MESSAGE$
|
|
20028
|
+
const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
20819
20029
|
const LITERAL_TEXT_TAGS = new Set([
|
|
20820
20030
|
"code",
|
|
20821
20031
|
"pre",
|
|
@@ -20885,7 +20095,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
20885
20095
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
20886
20096
|
context.report({
|
|
20887
20097
|
node,
|
|
20888
|
-
message: MESSAGE$
|
|
20098
|
+
message: MESSAGE$47
|
|
20889
20099
|
});
|
|
20890
20100
|
} })
|
|
20891
20101
|
});
|
|
@@ -20916,7 +20126,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
20916
20126
|
};
|
|
20917
20127
|
//#endregion
|
|
20918
20128
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
20919
|
-
const MESSAGE$
|
|
20129
|
+
const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
20920
20130
|
const CONTEXT_MODULES$1 = [
|
|
20921
20131
|
"react",
|
|
20922
20132
|
"use-context-selector",
|
|
@@ -21014,7 +20224,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
21014
20224
|
if (!isConstructedValue(innerExpression)) continue;
|
|
21015
20225
|
context.report({
|
|
21016
20226
|
node: attribute,
|
|
21017
|
-
message: MESSAGE$
|
|
20227
|
+
message: MESSAGE$46
|
|
21018
20228
|
});
|
|
21019
20229
|
}
|
|
21020
20230
|
}
|
|
@@ -21869,7 +21079,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
21869
21079
|
];
|
|
21870
21080
|
//#endregion
|
|
21871
21081
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
21872
|
-
const MESSAGE$
|
|
21082
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
21873
21083
|
const isDataArrayPropName = (propName) => {
|
|
21874
21084
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
21875
21085
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -21956,7 +21166,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
21956
21166
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
21957
21167
|
context.report({
|
|
21958
21168
|
node,
|
|
21959
|
-
message: MESSAGE$
|
|
21169
|
+
message: MESSAGE$45
|
|
21960
21170
|
});
|
|
21961
21171
|
}
|
|
21962
21172
|
};
|
|
@@ -22214,7 +21424,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
22214
21424
|
]);
|
|
22215
21425
|
//#endregion
|
|
22216
21426
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
22217
|
-
const MESSAGE$
|
|
21427
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
22218
21428
|
const isAccessorPredicateName = (propName) => {
|
|
22219
21429
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
22220
21430
|
if (propName.length <= prefix.length) continue;
|
|
@@ -22420,7 +21630,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
22420
21630
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
22421
21631
|
context.report({
|
|
22422
21632
|
node,
|
|
22423
|
-
message: MESSAGE$
|
|
21633
|
+
message: MESSAGE$44
|
|
22424
21634
|
});
|
|
22425
21635
|
}
|
|
22426
21636
|
};
|
|
@@ -22640,7 +21850,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
22640
21850
|
];
|
|
22641
21851
|
//#endregion
|
|
22642
21852
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
22643
|
-
const MESSAGE$
|
|
21853
|
+
const MESSAGE$43 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
22644
21854
|
const isConfigObjectPropName = (propName) => {
|
|
22645
21855
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
22646
21856
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -22729,7 +21939,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22729
21939
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
22730
21940
|
context.report({
|
|
22731
21941
|
node,
|
|
22732
|
-
message: MESSAGE$
|
|
21942
|
+
message: MESSAGE$43
|
|
22733
21943
|
});
|
|
22734
21944
|
}
|
|
22735
21945
|
};
|
|
@@ -22737,7 +21947,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22737
21947
|
});
|
|
22738
21948
|
//#endregion
|
|
22739
21949
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
22740
|
-
const MESSAGE$
|
|
21950
|
+
const MESSAGE$42 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
22741
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;
|
|
22742
21952
|
const resolveSettings$29 = (settings) => {
|
|
22743
21953
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -22775,7 +21985,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
22775
21985
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
22776
21986
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
22777
21987
|
node: attribute,
|
|
22778
|
-
message: MESSAGE$
|
|
21988
|
+
message: MESSAGE$42
|
|
22779
21989
|
});
|
|
22780
21990
|
}
|
|
22781
21991
|
} };
|
|
@@ -23395,7 +22605,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
23395
22605
|
});
|
|
23396
22606
|
//#endregion
|
|
23397
22607
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
23398
|
-
const MESSAGE$
|
|
22608
|
+
const MESSAGE$41 = "You can't tell what props reach this element when you spread them.";
|
|
23399
22609
|
const resolveSettings$25 = (settings) => {
|
|
23400
22610
|
const reactDoctor = settings?.["react-doctor"];
|
|
23401
22611
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -23438,7 +22648,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
23438
22648
|
}
|
|
23439
22649
|
context.report({
|
|
23440
22650
|
node: attribute,
|
|
23441
|
-
message: MESSAGE$
|
|
22651
|
+
message: MESSAGE$41
|
|
23442
22652
|
});
|
|
23443
22653
|
didReportInFile = true;
|
|
23444
22654
|
return;
|
|
@@ -23714,7 +22924,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
23714
22924
|
});
|
|
23715
22925
|
//#endregion
|
|
23716
22926
|
//#region src/plugin/rules/a11y/lang.ts
|
|
23717
|
-
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`.";
|
|
23718
22928
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
23719
22929
|
"aa",
|
|
23720
22930
|
"ab",
|
|
@@ -24194,7 +23404,7 @@ const lang = defineRule({
|
|
|
24194
23404
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
24195
23405
|
context.report({
|
|
24196
23406
|
node: langAttr,
|
|
24197
|
-
message: MESSAGE$
|
|
23407
|
+
message: MESSAGE$40
|
|
24198
23408
|
});
|
|
24199
23409
|
return;
|
|
24200
23410
|
}
|
|
@@ -24203,7 +23413,7 @@ const lang = defineRule({
|
|
|
24203
23413
|
if (value === null) return;
|
|
24204
23414
|
if (!isValidLangTag(value)) context.report({
|
|
24205
23415
|
node: langAttr,
|
|
24206
|
-
message: MESSAGE$
|
|
23416
|
+
message: MESSAGE$40
|
|
24207
23417
|
});
|
|
24208
23418
|
} })
|
|
24209
23419
|
});
|
|
@@ -24254,7 +23464,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
24254
23464
|
});
|
|
24255
23465
|
//#endregion
|
|
24256
23466
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
24257
|
-
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>`.";
|
|
24258
23468
|
const DEFAULT_AUDIO = ["audio"];
|
|
24259
23469
|
const DEFAULT_VIDEO = ["video"];
|
|
24260
23470
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -24343,7 +23553,7 @@ const mediaHasCaption = defineRule({
|
|
|
24343
23553
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
24344
23554
|
context.report({
|
|
24345
23555
|
node: node.name,
|
|
24346
|
-
message: MESSAGE$
|
|
23556
|
+
message: MESSAGE$39
|
|
24347
23557
|
});
|
|
24348
23558
|
return;
|
|
24349
23559
|
}
|
|
@@ -24362,7 +23572,7 @@ const mediaHasCaption = defineRule({
|
|
|
24362
23572
|
return kindValue.value.toLowerCase() === "captions";
|
|
24363
23573
|
})) context.report({
|
|
24364
23574
|
node: node.name,
|
|
24365
|
-
message: MESSAGE$
|
|
23575
|
+
message: MESSAGE$39
|
|
24366
23576
|
});
|
|
24367
23577
|
} };
|
|
24368
23578
|
}
|
|
@@ -25905,7 +25115,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
25905
25115
|
});
|
|
25906
25116
|
//#endregion
|
|
25907
25117
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
25908
|
-
const MESSAGE$
|
|
25118
|
+
const MESSAGE$38 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
25909
25119
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
25910
25120
|
const noAccessKey = defineRule({
|
|
25911
25121
|
id: "no-access-key",
|
|
@@ -25924,7 +25134,7 @@ const noAccessKey = defineRule({
|
|
|
25924
25134
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
25925
25135
|
context.report({
|
|
25926
25136
|
node: accessKey,
|
|
25927
|
-
message: MESSAGE$
|
|
25137
|
+
message: MESSAGE$38
|
|
25928
25138
|
});
|
|
25929
25139
|
return;
|
|
25930
25140
|
}
|
|
@@ -25934,7 +25144,7 @@ const noAccessKey = defineRule({
|
|
|
25934
25144
|
if (isUndefinedIdentifier(expression)) return;
|
|
25935
25145
|
context.report({
|
|
25936
25146
|
node: accessKey,
|
|
25937
|
-
message: MESSAGE$
|
|
25147
|
+
message: MESSAGE$38
|
|
25938
25148
|
});
|
|
25939
25149
|
}
|
|
25940
25150
|
} };
|
|
@@ -26239,11 +25449,8 @@ const unwrapUseCallback = (node) => {
|
|
|
26239
25449
|
const callee = node.callee;
|
|
26240
25450
|
return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
|
|
26241
25451
|
};
|
|
26242
|
-
const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
|
|
26243
25452
|
const resolveToFunction = (ref) => {
|
|
26244
|
-
const
|
|
26245
|
-
if (!definition || hasParameterDefinition(ref)) return null;
|
|
26246
|
-
const definitionNode = definition.node;
|
|
25453
|
+
const definitionNode = ref.resolved?.defs[0]?.node;
|
|
26247
25454
|
if (!definitionNode) return null;
|
|
26248
25455
|
if (isFunctionLike$2(definitionNode)) return definitionNode;
|
|
26249
25456
|
if (isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
@@ -26653,7 +25860,7 @@ const isCleanupReturnArgument = (analysis, node) => {
|
|
|
26653
25860
|
if (isNodeOfType(node, "MemberExpression")) return true;
|
|
26654
25861
|
if (isNodeOfType(node, "Identifier")) {
|
|
26655
25862
|
const ref = getRef(analysis, node);
|
|
26656
|
-
if (ref &&
|
|
25863
|
+
if (ref && resolveToFunction(ref)) return true;
|
|
26657
25864
|
}
|
|
26658
25865
|
if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
|
|
26659
25866
|
return false;
|
|
@@ -26811,7 +26018,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
26811
26018
|
};
|
|
26812
26019
|
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
26813
26020
|
if (!setterRef.resolved) return false;
|
|
26814
|
-
const componentFunction = findEnclosingFunction
|
|
26021
|
+
const componentFunction = findEnclosingFunction(effectNode);
|
|
26815
26022
|
if (!componentFunction) return false;
|
|
26816
26023
|
for (const reference of setterRef.resolved.references) {
|
|
26817
26024
|
if (reference.init) continue;
|
|
@@ -27125,7 +26332,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
27125
26332
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
27126
26333
|
const reference = getRef(analysis, candidate);
|
|
27127
26334
|
if (!reference) return null;
|
|
27128
|
-
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;
|
|
27129
26336
|
const resolved = resolveToFunction(reference);
|
|
27130
26337
|
if (resolved) return resolved;
|
|
27131
26338
|
const definitionNode = reference.resolved?.defs[0]?.node;
|
|
@@ -27789,7 +26996,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
27789
26996
|
});
|
|
27790
26997
|
//#endregion
|
|
27791
26998
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
27792
|
-
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.";
|
|
27793
27000
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
27794
27001
|
"button",
|
|
27795
27002
|
"embed",
|
|
@@ -27901,7 +27108,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
27901
27108
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
27902
27109
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
27903
27110
|
node: ariaHidden,
|
|
27904
|
-
message: MESSAGE$
|
|
27111
|
+
message: MESSAGE$37
|
|
27905
27112
|
});
|
|
27906
27113
|
} })
|
|
27907
27114
|
});
|
|
@@ -28823,7 +28030,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
28823
28030
|
};
|
|
28824
28031
|
//#endregion
|
|
28825
28032
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
28826
|
-
const MESSAGE$
|
|
28033
|
+
const MESSAGE$36 = "Your users can see & submit the wrong data when this list reorders.";
|
|
28827
28034
|
const SECOND_INDEX_METHODS = new Set([
|
|
28828
28035
|
"every",
|
|
28829
28036
|
"filter",
|
|
@@ -28942,14 +28149,14 @@ const noArrayIndexKey = defineRule({
|
|
|
28942
28149
|
if (propName !== "key") continue;
|
|
28943
28150
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
28944
28151
|
node: property,
|
|
28945
|
-
message: MESSAGE$
|
|
28152
|
+
message: MESSAGE$36
|
|
28946
28153
|
});
|
|
28947
28154
|
}
|
|
28948
28155
|
} })
|
|
28949
28156
|
});
|
|
28950
28157
|
//#endregion
|
|
28951
28158
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
28952
|
-
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.";
|
|
28953
28160
|
const noAsyncEffectCallback = defineRule({
|
|
28954
28161
|
id: "no-async-effect-callback",
|
|
28955
28162
|
title: "Async effect callback",
|
|
@@ -28967,13 +28174,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
28967
28174
|
if (!callback.async) return;
|
|
28968
28175
|
context.report({
|
|
28969
28176
|
node: callback,
|
|
28970
|
-
message: MESSAGE$
|
|
28177
|
+
message: MESSAGE$35
|
|
28971
28178
|
});
|
|
28972
28179
|
} })
|
|
28973
28180
|
});
|
|
28974
28181
|
//#endregion
|
|
28975
28182
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
28976
|
-
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.";
|
|
28977
28184
|
const resolveSettings$21 = (settings) => {
|
|
28978
28185
|
const reactDoctor = settings?.["react-doctor"];
|
|
28979
28186
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -29070,7 +28277,7 @@ const noAutofocus = defineRule({
|
|
|
29070
28277
|
if (isConditionallyRendered(node)) return;
|
|
29071
28278
|
context.report({
|
|
29072
28279
|
node: autoFocusAttribute,
|
|
29073
|
-
message: MESSAGE$
|
|
28280
|
+
message: MESSAGE$34
|
|
29074
28281
|
});
|
|
29075
28282
|
} };
|
|
29076
28283
|
}
|
|
@@ -29584,6 +28791,138 @@ const noBarrelImport = defineRule({
|
|
|
29584
28791
|
}
|
|
29585
28792
|
});
|
|
29586
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
|
|
29587
28926
|
//#region src/plugin/utils/has-stable-call-target.ts
|
|
29588
28927
|
const hasStableCallTarget = (callExpression, scopes) => {
|
|
29589
28928
|
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -29911,7 +29250,7 @@ const noChainStateUpdates = defineRule({
|
|
|
29911
29250
|
});
|
|
29912
29251
|
//#endregion
|
|
29913
29252
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
29914
|
-
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.";
|
|
29915
29254
|
const noChildrenProp = defineRule({
|
|
29916
29255
|
id: "no-children-prop",
|
|
29917
29256
|
title: "Children passed as a prop",
|
|
@@ -29923,7 +29262,7 @@ const noChildrenProp = defineRule({
|
|
|
29923
29262
|
if (node.name.name !== "children") return;
|
|
29924
29263
|
context.report({
|
|
29925
29264
|
node: node.name,
|
|
29926
|
-
message: MESSAGE$
|
|
29265
|
+
message: MESSAGE$33
|
|
29927
29266
|
});
|
|
29928
29267
|
},
|
|
29929
29268
|
CallExpression(node) {
|
|
@@ -29936,7 +29275,7 @@ const noChildrenProp = defineRule({
|
|
|
29936
29275
|
const propertyKey = property.key;
|
|
29937
29276
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
29938
29277
|
node: propertyKey,
|
|
29939
|
-
message: MESSAGE$
|
|
29278
|
+
message: MESSAGE$33
|
|
29940
29279
|
});
|
|
29941
29280
|
}
|
|
29942
29281
|
}
|
|
@@ -29944,7 +29283,7 @@ const noChildrenProp = defineRule({
|
|
|
29944
29283
|
});
|
|
29945
29284
|
//#endregion
|
|
29946
29285
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
29947
|
-
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.";
|
|
29948
29287
|
const noCloneElement = defineRule({
|
|
29949
29288
|
id: "no-clone-element",
|
|
29950
29289
|
title: "cloneElement makes child props fragile",
|
|
@@ -29957,7 +29296,7 @@ const noCloneElement = defineRule({
|
|
|
29957
29296
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
29958
29297
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
29959
29298
|
node: callee,
|
|
29960
|
-
message: MESSAGE$
|
|
29299
|
+
message: MESSAGE$32
|
|
29961
29300
|
});
|
|
29962
29301
|
return;
|
|
29963
29302
|
}
|
|
@@ -29970,7 +29309,7 @@ const noCloneElement = defineRule({
|
|
|
29970
29309
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
29971
29310
|
context.report({
|
|
29972
29311
|
node: callee,
|
|
29973
|
-
message: MESSAGE$
|
|
29312
|
+
message: MESSAGE$32
|
|
29974
29313
|
});
|
|
29975
29314
|
}
|
|
29976
29315
|
} })
|
|
@@ -29990,7 +29329,7 @@ const getCallMethodName = (callee) => {
|
|
|
29990
29329
|
};
|
|
29991
29330
|
//#endregion
|
|
29992
29331
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
29993
|
-
const MESSAGE$
|
|
29332
|
+
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
29994
29333
|
const CONTEXT_MODULES = [
|
|
29995
29334
|
"react",
|
|
29996
29335
|
"use-context-selector",
|
|
@@ -30038,13 +29377,13 @@ const noCreateContextInRender = defineRule({
|
|
|
30038
29377
|
if (!componentOrHookName) return;
|
|
30039
29378
|
context.report({
|
|
30040
29379
|
node,
|
|
30041
|
-
message: `${MESSAGE$
|
|
29380
|
+
message: `${MESSAGE$31} (called inside "${componentOrHookName}")`
|
|
30042
29381
|
});
|
|
30043
29382
|
} })
|
|
30044
29383
|
});
|
|
30045
29384
|
//#endregion
|
|
30046
29385
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
30047
|
-
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.";
|
|
30048
29387
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
30049
29388
|
const parent = functionNode.parent;
|
|
30050
29389
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -30052,8 +29391,8 @@ const isUseMemoCallbackArgument = (functionNode) => {
|
|
|
30052
29391
|
return isReactFunctionCall(parent, "useMemo");
|
|
30053
29392
|
};
|
|
30054
29393
|
const findEnclosingRenderFunction = (node) => {
|
|
30055
|
-
let enclosingFunction = findEnclosingFunction
|
|
30056
|
-
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction
|
|
29394
|
+
let enclosingFunction = findEnclosingFunction(node);
|
|
29395
|
+
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
30057
29396
|
return enclosingFunction;
|
|
30058
29397
|
};
|
|
30059
29398
|
const noCreateRefInFunctionComponent = defineRule({
|
|
@@ -30074,7 +29413,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
30074
29413
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
|
|
30075
29414
|
context.report({
|
|
30076
29415
|
node,
|
|
30077
|
-
message: MESSAGE$
|
|
29416
|
+
message: MESSAGE$30
|
|
30078
29417
|
});
|
|
30079
29418
|
} })
|
|
30080
29419
|
});
|
|
@@ -30214,7 +29553,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
30214
29553
|
});
|
|
30215
29554
|
//#endregion
|
|
30216
29555
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
30217
|
-
const MESSAGE$
|
|
29556
|
+
const MESSAGE$29 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
30218
29557
|
const noDanger = defineRule({
|
|
30219
29558
|
id: "no-danger",
|
|
30220
29559
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -30228,7 +29567,7 @@ const noDanger = defineRule({
|
|
|
30228
29567
|
if (!propAttribute) return;
|
|
30229
29568
|
context.report({
|
|
30230
29569
|
node: propAttribute.name,
|
|
30231
|
-
message: MESSAGE$
|
|
29570
|
+
message: MESSAGE$29
|
|
30232
29571
|
});
|
|
30233
29572
|
},
|
|
30234
29573
|
CallExpression(node) {
|
|
@@ -30240,7 +29579,7 @@ const noDanger = defineRule({
|
|
|
30240
29579
|
const propertyKey = property.key;
|
|
30241
29580
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
30242
29581
|
node: propertyKey,
|
|
30243
|
-
message: MESSAGE$
|
|
29582
|
+
message: MESSAGE$29
|
|
30244
29583
|
});
|
|
30245
29584
|
}
|
|
30246
29585
|
}
|
|
@@ -30263,7 +29602,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
30263
29602
|
};
|
|
30264
29603
|
//#endregion
|
|
30265
29604
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
30266
|
-
const MESSAGE$
|
|
29605
|
+
const MESSAGE$28 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
30267
29606
|
const mergePropsShape = (target, source) => {
|
|
30268
29607
|
target.hasDangerously ||= source.hasDangerously;
|
|
30269
29608
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -30338,7 +29677,7 @@ const noDangerWithChildren = defineRule({
|
|
|
30338
29677
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
30339
29678
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
30340
29679
|
node: opening,
|
|
30341
|
-
message: MESSAGE$
|
|
29680
|
+
message: MESSAGE$28
|
|
30342
29681
|
});
|
|
30343
29682
|
},
|
|
30344
29683
|
CallExpression(node) {
|
|
@@ -30351,7 +29690,7 @@ const noDangerWithChildren = defineRule({
|
|
|
30351
29690
|
const positionalChildren = node.arguments.slice(2);
|
|
30352
29691
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
30353
29692
|
node,
|
|
30354
|
-
message: MESSAGE$
|
|
29693
|
+
message: MESSAGE$28
|
|
30355
29694
|
});
|
|
30356
29695
|
}
|
|
30357
29696
|
})
|
|
@@ -31104,7 +30443,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
31104
30443
|
const isEffectDrivenResync = (useStateCall) => {
|
|
31105
30444
|
const setterName = getStateSetterName(useStateCall);
|
|
31106
30445
|
if (!setterName) return false;
|
|
31107
|
-
const componentFunction = findEnclosingFunction
|
|
30446
|
+
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31108
30447
|
if (!componentFunction) return false;
|
|
31109
30448
|
let isExempt = false;
|
|
31110
30449
|
walkAst(componentFunction, (child) => {
|
|
@@ -31208,7 +30547,7 @@ const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
|
|
|
31208
30547
|
const setterName = getStateSetterName(useStateCall);
|
|
31209
30548
|
if (!setterName) return false;
|
|
31210
30549
|
const stateValueName = getStateValueName(useStateCall);
|
|
31211
|
-
const componentFunction = findEnclosingFunction
|
|
30550
|
+
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31212
30551
|
if (!componentFunction) return false;
|
|
31213
30552
|
let isExempt = false;
|
|
31214
30553
|
walkAst(componentFunction, (child) => {
|
|
@@ -31254,7 +30593,7 @@ const noDerivedUseState = defineRule({
|
|
|
31254
30593
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
31255
30594
|
if (isEffectDrivenResync(node)) return;
|
|
31256
30595
|
if (isNextjsDataFetchingPage(node)) return;
|
|
31257
|
-
const componentFunction = findEnclosingFunction
|
|
30596
|
+
const componentFunction = findEnclosingFunction(node);
|
|
31258
30597
|
if (componentFunction) {
|
|
31259
30598
|
if (isDefaultedDestructuredProp(componentFunction, propName)) return;
|
|
31260
30599
|
if (isDraftCommittedToParent(componentFunction, getStateValueName(node), propStackTracker.isPropName)) return;
|
|
@@ -31316,7 +30655,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
31316
30655
|
//#endregion
|
|
31317
30656
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
31318
30657
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
31319
|
-
const MESSAGE$
|
|
30658
|
+
const MESSAGE$27 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
31320
30659
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
31321
30660
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
31322
30661
|
let ancestor = setStateCall.parent;
|
|
@@ -31444,7 +30783,7 @@ const noDidMountSetState = defineRule({
|
|
|
31444
30783
|
}
|
|
31445
30784
|
context.report({
|
|
31446
30785
|
node: node.callee,
|
|
31447
|
-
message: MESSAGE$
|
|
30786
|
+
message: MESSAGE$27
|
|
31448
30787
|
});
|
|
31449
30788
|
} };
|
|
31450
30789
|
}
|
|
@@ -31452,7 +30791,7 @@ const noDidMountSetState = defineRule({
|
|
|
31452
30791
|
//#endregion
|
|
31453
30792
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
31454
30793
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
31455
|
-
const MESSAGE$
|
|
30794
|
+
const MESSAGE$26 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
31456
30795
|
const EQUALITY_OPERATORS = new Set([
|
|
31457
30796
|
"==",
|
|
31458
30797
|
"===",
|
|
@@ -31626,7 +30965,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
31626
30965
|
if (isInsideDiffGuard(node)) return;
|
|
31627
30966
|
context.report({
|
|
31628
30967
|
node: node.callee,
|
|
31629
|
-
message: MESSAGE$
|
|
30968
|
+
message: MESSAGE$26
|
|
31630
30969
|
});
|
|
31631
30970
|
} };
|
|
31632
30971
|
}
|
|
@@ -31649,7 +30988,7 @@ const isStateMemberExpression = (node) => {
|
|
|
31649
30988
|
};
|
|
31650
30989
|
//#endregion
|
|
31651
30990
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
31652
|
-
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.";
|
|
31653
30992
|
const shouldIgnoreMutation = (node) => {
|
|
31654
30993
|
let isConstructor = false;
|
|
31655
30994
|
let isInsideCallExpression = false;
|
|
@@ -31671,7 +31010,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
31671
31010
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
31672
31011
|
context.report({
|
|
31673
31012
|
node: reportNode,
|
|
31674
|
-
message: MESSAGE$
|
|
31013
|
+
message: MESSAGE$25
|
|
31675
31014
|
});
|
|
31676
31015
|
};
|
|
31677
31016
|
const noDirectMutationState = defineRule({
|
|
@@ -32022,7 +31361,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
32022
31361
|
});
|
|
32023
31362
|
//#endregion
|
|
32024
31363
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
32025
|
-
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.";
|
|
32026
31365
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
32027
31366
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
32028
31367
|
const noDocumentWrite = defineRule({
|
|
@@ -32039,7 +31378,7 @@ const noDocumentWrite = defineRule({
|
|
|
32039
31378
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
32040
31379
|
context.report({
|
|
32041
31380
|
node,
|
|
32042
|
-
message: MESSAGE$
|
|
31381
|
+
message: MESSAGE$24
|
|
32043
31382
|
});
|
|
32044
31383
|
} })
|
|
32045
31384
|
});
|
|
@@ -33092,7 +32431,7 @@ const isFunctionUsedOutsideHandlers = (analysis, functionNode, componentFunction
|
|
|
33092
32431
|
if (isInsideInlineEventHandler(identifier, componentFunction)) return false;
|
|
33093
32432
|
const parent = identifier.parent;
|
|
33094
32433
|
if (parent && isNodeOfType(parent, "CallExpression") && parent.callee === identifier) {
|
|
33095
|
-
if (findEnclosingFunction
|
|
32434
|
+
if (findEnclosingFunction(parent) === functionNode) return false;
|
|
33096
32435
|
if (isInsideProvenEventHandler(analysis, parent, componentFunction, false)) return false;
|
|
33097
32436
|
}
|
|
33098
32437
|
return true;
|
|
@@ -33138,7 +32477,7 @@ const isStateWrittenOnlyFromEventHandlers = (analysis, stateReference) => {
|
|
|
33138
32477
|
if (!setterVariable) return false;
|
|
33139
32478
|
const stateDeclarator = getUseStateDecl(analysis, stateReference);
|
|
33140
32479
|
if (!stateDeclarator) return false;
|
|
33141
|
-
const componentFunction = findEnclosingFunction
|
|
32480
|
+
const componentFunction = findEnclosingFunction(stateDeclarator);
|
|
33142
32481
|
if (!componentFunction) return false;
|
|
33143
32482
|
let hasWriter = false;
|
|
33144
32483
|
for (const reference of setterVariable.references) {
|
|
@@ -34200,8 +33539,8 @@ const isAwaitedInFunction = (request, functionNode) => {
|
|
|
34200
33539
|
return false;
|
|
34201
33540
|
};
|
|
34202
33541
|
const isCompletionSinkForRequest = (completionSink, request) => {
|
|
34203
|
-
const requestFunction = findEnclosingFunction
|
|
34204
|
-
const completionSinkFunction = findEnclosingFunction
|
|
33542
|
+
const requestFunction = findEnclosingFunction(request);
|
|
33543
|
+
const completionSinkFunction = findEnclosingFunction(completionSink);
|
|
34205
33544
|
if (!requestFunction || !completionSinkFunction) return false;
|
|
34206
33545
|
if (requestFunction === completionSinkFunction) {
|
|
34207
33546
|
if (!isAwaitedInFunction(request, requestFunction)) return false;
|
|
@@ -34257,7 +33596,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
34257
33596
|
"ReactDOM",
|
|
34258
33597
|
"ReactDom"
|
|
34259
33598
|
]);
|
|
34260
|
-
const MESSAGE$
|
|
33599
|
+
const MESSAGE$23 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
34261
33600
|
const noFindDomNode = defineRule({
|
|
34262
33601
|
id: "no-find-dom-node",
|
|
34263
33602
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -34268,7 +33607,7 @@ const noFindDomNode = defineRule({
|
|
|
34268
33607
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
34269
33608
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
34270
33609
|
node: callee,
|
|
34271
|
-
message: MESSAGE$
|
|
33610
|
+
message: MESSAGE$23
|
|
34272
33611
|
});
|
|
34273
33612
|
return;
|
|
34274
33613
|
}
|
|
@@ -34279,7 +33618,7 @@ const noFindDomNode = defineRule({
|
|
|
34279
33618
|
if (callee.property.name !== "findDOMNode") return;
|
|
34280
33619
|
context.report({
|
|
34281
33620
|
node: callee.property,
|
|
34282
|
-
message: MESSAGE$
|
|
33621
|
+
message: MESSAGE$23
|
|
34283
33622
|
});
|
|
34284
33623
|
}
|
|
34285
33624
|
} })
|
|
@@ -34488,6 +33827,13 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
34488
33827
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
34489
33828
|
};
|
|
34490
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
|
|
34491
33837
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
34492
33838
|
const isLibraryDevPage = (filename) => {
|
|
34493
33839
|
if (!filename) return false;
|
|
@@ -35051,7 +34397,7 @@ const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
|
|
|
35051
34397
|
return attribute ? !isEventHandlerAttribute(attribute) : true;
|
|
35052
34398
|
}
|
|
35053
34399
|
if (isNodeOfType(parentNode, "ReturnStatement")) {
|
|
35054
|
-
if (findEnclosingFunction
|
|
34400
|
+
if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
|
|
35055
34401
|
}
|
|
35056
34402
|
if (parentNode === componentOrHookNode) return isFunctionLike$2(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
|
|
35057
34403
|
if (isFunctionLike$2(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
|
|
@@ -35174,7 +34520,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
35174
34520
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
35175
34521
|
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
35176
34522
|
if (!componentOrHookNode) return;
|
|
35177
|
-
const enclosingFunction = findEnclosingFunction
|
|
34523
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
35178
34524
|
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
|
|
35179
34525
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
35180
34526
|
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
@@ -35186,7 +34532,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
35186
34532
|
});
|
|
35187
34533
|
//#endregion
|
|
35188
34534
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
35189
|
-
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.";
|
|
35190
34536
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
35191
34537
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
35192
34538
|
title: "Lazy image with high fetchPriority",
|
|
@@ -35200,7 +34546,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
35200
34546
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
35201
34547
|
context.report({
|
|
35202
34548
|
node: node.name,
|
|
35203
|
-
message: MESSAGE$
|
|
34549
|
+
message: MESSAGE$22
|
|
35204
34550
|
});
|
|
35205
34551
|
} })
|
|
35206
34552
|
});
|
|
@@ -35389,7 +34735,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
35389
34735
|
});
|
|
35390
34736
|
//#endregion
|
|
35391
34737
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
35392
|
-
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.";
|
|
35393
34739
|
const REACT_USE_REF_OPTIONS = {
|
|
35394
34740
|
allowGlobalReactNamespace: false,
|
|
35395
34741
|
allowUnboundBareCalls: false
|
|
@@ -35490,7 +34836,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
35490
34836
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
35491
34837
|
context.report({
|
|
35492
34838
|
node: indeterminateAttribute,
|
|
35493
|
-
message: MESSAGE$
|
|
34839
|
+
message: MESSAGE$21
|
|
35494
34840
|
});
|
|
35495
34841
|
},
|
|
35496
34842
|
CallExpression(node) {
|
|
@@ -35499,7 +34845,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
35499
34845
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
35500
34846
|
context.report({
|
|
35501
34847
|
node,
|
|
35502
|
-
message: MESSAGE$
|
|
34848
|
+
message: MESSAGE$21
|
|
35503
34849
|
});
|
|
35504
34850
|
}
|
|
35505
34851
|
};
|
|
@@ -35622,10 +34968,10 @@ const isInsideInstanceField = (node) => {
|
|
|
35622
34968
|
return false;
|
|
35623
34969
|
};
|
|
35624
34970
|
const isOneShotModuleInitialization = (node, context) => {
|
|
35625
|
-
let functionNode = findEnclosingFunction
|
|
34971
|
+
let functionNode = findEnclosingFunction(node);
|
|
35626
34972
|
while (functionNode) {
|
|
35627
34973
|
if (!executesDuringRender(functionNode, context.scopes)) return false;
|
|
35628
|
-
functionNode = findEnclosingFunction
|
|
34974
|
+
functionNode = findEnclosingFunction(functionNode);
|
|
35629
34975
|
}
|
|
35630
34976
|
return !isInsideInstanceField(node);
|
|
35631
34977
|
};
|
|
@@ -35787,7 +35133,7 @@ const noIsMounted = defineRule({
|
|
|
35787
35133
|
});
|
|
35788
35134
|
//#endregion
|
|
35789
35135
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
35790
|
-
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)`.";
|
|
35791
35137
|
const isJsonMethodCall = (node, method) => {
|
|
35792
35138
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
35793
35139
|
const callee = node.callee;
|
|
@@ -35851,13 +35197,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
35851
35197
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
35852
35198
|
context.report({
|
|
35853
35199
|
node,
|
|
35854
|
-
message: MESSAGE$
|
|
35200
|
+
message: MESSAGE$20
|
|
35855
35201
|
});
|
|
35856
35202
|
} })
|
|
35857
35203
|
});
|
|
35858
35204
|
//#endregion
|
|
35859
35205
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
35860
|
-
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.";
|
|
35861
35207
|
const isJsxElementTypeReference = (node) => {
|
|
35862
35208
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
35863
35209
|
const typeName = node.typeName;
|
|
@@ -35911,7 +35257,7 @@ const noJsxElementType = defineRule({
|
|
|
35911
35257
|
if (isJsxImported) return;
|
|
35912
35258
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
35913
35259
|
node: typeAnnotation,
|
|
35914
|
-
message: MESSAGE$
|
|
35260
|
+
message: MESSAGE$19
|
|
35915
35261
|
});
|
|
35916
35262
|
}
|
|
35917
35263
|
};
|
|
@@ -36143,221 +35489,6 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
36143
35489
|
}
|
|
36144
35490
|
});
|
|
36145
35491
|
//#endregion
|
|
36146
|
-
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
36147
|
-
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
36148
|
-
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
36149
|
-
const expression = stripParenExpression(node);
|
|
36150
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
36151
|
-
const propertyName = getStaticPropertyName(expression);
|
|
36152
|
-
const receiver = stripParenExpression(expression.object);
|
|
36153
|
-
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
36154
|
-
}
|
|
36155
|
-
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36156
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
36157
|
-
const symbol = scopes.symbolFor(expression);
|
|
36158
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
36159
|
-
visitedSymbolIds.add(symbol.id);
|
|
36160
|
-
if (isImportedFromReact(symbol)) {
|
|
36161
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
36162
|
-
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
36163
|
-
}
|
|
36164
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36165
|
-
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
36166
|
-
};
|
|
36167
|
-
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36168
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
36169
|
-
visitedClassNodes.add(classNode);
|
|
36170
|
-
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36171
|
-
};
|
|
36172
|
-
//#endregion
|
|
36173
|
-
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
36174
|
-
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
36175
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
36176
|
-
let containsReactHookCall = false;
|
|
36177
|
-
walkAst(functionNode.body, (node) => {
|
|
36178
|
-
if (containsReactHookCall) return false;
|
|
36179
|
-
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
36180
|
-
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36181
|
-
containsReactHookCall = true;
|
|
36182
|
-
return false;
|
|
36183
|
-
}
|
|
36184
|
-
});
|
|
36185
|
-
return containsReactHookCall;
|
|
36186
|
-
};
|
|
36187
|
-
//#endregion
|
|
36188
|
-
//#region src/plugin/utils/function-returns-props-children.ts
|
|
36189
|
-
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
36190
|
-
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
36191
|
-
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
36192
|
-
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
36193
|
-
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
36194
|
-
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
36195
|
-
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
36196
|
-
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
36197
|
-
const propertyValue = stripParenExpression(property.value);
|
|
36198
|
-
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
36199
|
-
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
36200
|
-
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
36201
|
-
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
36202
|
-
}
|
|
36203
|
-
}
|
|
36204
|
-
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
36205
|
-
const candidate = stripParenExpression(expression);
|
|
36206
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
36207
|
-
const symbol = scopes.symbolFor(candidate);
|
|
36208
|
-
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
36209
|
-
}
|
|
36210
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
36211
|
-
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
36212
|
-
const receiver = stripParenExpression(candidate.object);
|
|
36213
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
36214
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
36215
|
-
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
36216
|
-
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
36217
|
-
}, controlFlow);
|
|
36218
|
-
};
|
|
36219
|
-
//#endregion
|
|
36220
|
-
//#region src/plugin/utils/function-returns-only-null.ts
|
|
36221
|
-
const isNullExpression = (expression) => {
|
|
36222
|
-
const candidate = stripParenExpression(expression);
|
|
36223
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
36224
|
-
};
|
|
36225
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
36226
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
36227
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
36228
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
36229
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
36230
|
-
};
|
|
36231
|
-
//#endregion
|
|
36232
|
-
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
36233
|
-
const findFactoryRoot = (node) => {
|
|
36234
|
-
const candidate = stripParenExpression(node);
|
|
36235
|
-
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
36236
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
36237
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
36238
|
-
return null;
|
|
36239
|
-
};
|
|
36240
|
-
const findFactoryPropertyName = (node) => {
|
|
36241
|
-
const candidate = stripParenExpression(node);
|
|
36242
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
36243
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
36244
|
-
return null;
|
|
36245
|
-
};
|
|
36246
|
-
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
36247
|
-
const candidate = stripParenExpression(expression);
|
|
36248
|
-
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
36249
|
-
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
36250
|
-
if (!factoryRoot) return false;
|
|
36251
|
-
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
36252
|
-
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
36253
|
-
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
36254
|
-
if (!symbol || symbol.kind !== "import") return false;
|
|
36255
|
-
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
36256
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
36257
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
36258
|
-
};
|
|
36259
|
-
//#endregion
|
|
36260
|
-
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
36261
|
-
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
36262
|
-
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
36263
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
36264
|
-
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36265
|
-
const candidate = stripParenExpression(expression);
|
|
36266
|
-
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
36267
|
-
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
36268
|
-
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
36269
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
36270
|
-
const symbol = scopes.symbolFor(candidate);
|
|
36271
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
36272
|
-
visitedSymbolIds.add(symbol.id);
|
|
36273
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
36274
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
36275
|
-
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
36276
|
-
}
|
|
36277
|
-
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
36278
|
-
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
36279
|
-
const factoryCallee = stripParenExpression(candidate.callee);
|
|
36280
|
-
if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
|
|
36281
|
-
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36282
|
-
const wrappedComponent = candidate.arguments[0];
|
|
36283
|
-
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
36284
|
-
}
|
|
36285
|
-
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
36286
|
-
const factory = candidate.arguments[0];
|
|
36287
|
-
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
36288
|
-
const unwrappedFactory = stripParenExpression(factory);
|
|
36289
|
-
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
36290
|
-
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
36291
|
-
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
36292
|
-
const returnedExpression = returnStatements[0]?.argument;
|
|
36293
|
-
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
36294
|
-
};
|
|
36295
|
-
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
36296
|
-
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
36297
|
-
for (const candidateSymbol of candidateSymbols) {
|
|
36298
|
-
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
36299
|
-
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
36300
|
-
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
36301
|
-
continue;
|
|
36302
|
-
}
|
|
36303
|
-
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
36304
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
36305
|
-
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
36306
|
-
continue;
|
|
36307
|
-
}
|
|
36308
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
36309
|
-
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
36310
|
-
continue;
|
|
36311
|
-
}
|
|
36312
|
-
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
36313
|
-
}
|
|
36314
|
-
return false;
|
|
36315
|
-
};
|
|
36316
|
-
//#endregion
|
|
36317
|
-
//#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
|
|
36318
|
-
const REACT_COMPONENT_TYPE_NAMES = new Set([
|
|
36319
|
-
"ComponentClass",
|
|
36320
|
-
"ComponentType",
|
|
36321
|
-
"FC",
|
|
36322
|
-
"FunctionComponent"
|
|
36323
|
-
]);
|
|
36324
|
-
const findVisibleSymbol = (identifier, scopes) => {
|
|
36325
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
36326
|
-
let scope = scopes.scopeFor(identifier);
|
|
36327
|
-
while (true) {
|
|
36328
|
-
const symbol = scope.symbolsByName.get(identifier.name);
|
|
36329
|
-
if (symbol) return symbol;
|
|
36330
|
-
if (!scope.parent) return null;
|
|
36331
|
-
scope = scope.parent;
|
|
36332
|
-
}
|
|
36333
|
-
};
|
|
36334
|
-
const isReactNamespaceType = (identifier, scopes) => {
|
|
36335
|
-
const symbol = findVisibleSymbol(identifier, scopes);
|
|
36336
|
-
return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
36337
|
-
};
|
|
36338
|
-
const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
|
|
36339
|
-
if (!typeNode) return false;
|
|
36340
|
-
if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36341
|
-
if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
|
|
36342
|
-
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
36343
|
-
const typeName = typeNode.typeName;
|
|
36344
|
-
if (isNodeOfType(typeName, "TSQualifiedName")) return isNodeOfType(typeName.left, "Identifier") && isNodeOfType(typeName.right, "Identifier") && REACT_COMPONENT_TYPE_NAMES.has(typeName.right.name) && isReactNamespaceType(typeName.left, scopes);
|
|
36345
|
-
if (!isNodeOfType(typeName, "Identifier")) return false;
|
|
36346
|
-
const typeSymbol = findVisibleSymbol(typeName, scopes);
|
|
36347
|
-
if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
|
|
36348
|
-
if (isImportedFromReact(typeSymbol)) {
|
|
36349
|
-
const importedName = getImportedName(typeSymbol.declarationNode);
|
|
36350
|
-
return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
|
|
36351
|
-
}
|
|
36352
|
-
if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
|
|
36353
|
-
visitedSymbolIds.add(typeSymbol.id);
|
|
36354
|
-
return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36355
|
-
};
|
|
36356
|
-
const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
|
|
36357
|
-
const bindingIdentifier = symbol.bindingIdentifier;
|
|
36358
|
-
return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
|
|
36359
|
-
};
|
|
36360
|
-
//#endregion
|
|
36361
35492
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
36362
35493
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
36363
35494
|
"childContextTypes",
|
|
@@ -36368,6 +35499,15 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
36368
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.`;
|
|
36369
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.";
|
|
36370
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
|
+
};
|
|
36371
35511
|
const noLegacyContextApi = defineRule({
|
|
36372
35512
|
id: "no-legacy-context-api",
|
|
36373
35513
|
title: "Legacy context API",
|
|
@@ -36382,7 +35522,6 @@ const noLegacyContextApi = defineRule({
|
|
|
36382
35522
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
36383
35523
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
36384
35524
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
36385
|
-
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
36386
35525
|
context.report({
|
|
36387
35526
|
node: memberNode.key,
|
|
36388
35527
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -36390,8 +35529,6 @@ const noLegacyContextApi = defineRule({
|
|
|
36390
35529
|
};
|
|
36391
35530
|
return {
|
|
36392
35531
|
ClassBody(node) {
|
|
36393
|
-
const classNode = node.parent;
|
|
36394
|
-
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
36395
35532
|
for (const member of node.body ?? []) checkMember(member);
|
|
36396
35533
|
},
|
|
36397
35534
|
AssignmentExpression(node) {
|
|
@@ -36401,11 +35538,9 @@ const noLegacyContextApi = defineRule({
|
|
|
36401
35538
|
if (left.computed) return;
|
|
36402
35539
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
36403
35540
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
36404
|
-
if (left.
|
|
36405
|
-
|
|
36406
|
-
if (
|
|
36407
|
-
const symbol = context.scopes.symbolFor(component);
|
|
36408
|
-
if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
|
|
35541
|
+
if (!isNodeOfType(left.object, "Identifier")) return;
|
|
35542
|
+
if (!isUppercaseName(left.object.name)) return;
|
|
35543
|
+
if (isInsideClassBody(node)) return;
|
|
36409
35544
|
context.report({
|
|
36410
35545
|
node: left,
|
|
36411
35546
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -36609,10 +35744,10 @@ const getMethodCalls = (functionNode, scopes) => {
|
|
|
36609
35744
|
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
36610
35745
|
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
36611
35746
|
visitedFunctionNodes.add(functionNode);
|
|
36612
|
-
const usageFunction = findEnclosingFunction
|
|
35747
|
+
const usageFunction = findEnclosingFunction(usageNode);
|
|
36613
35748
|
const immediateCall = getDirectCallForExpression(functionNode);
|
|
36614
35749
|
if (immediateCall) {
|
|
36615
|
-
const immediateCallFunction = findEnclosingFunction
|
|
35750
|
+
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
36616
35751
|
if (immediateCallFunction === usageFunction) {
|
|
36617
35752
|
const immediateCallStart = getRangeStart(immediateCall);
|
|
36618
35753
|
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
@@ -36621,7 +35756,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
36621
35756
|
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
36622
35757
|
}
|
|
36623
35758
|
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
36624
|
-
const methodCallFunction = findEnclosingFunction
|
|
35759
|
+
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
36625
35760
|
if (methodCallFunction === usageFunction) {
|
|
36626
35761
|
const methodCallStart = getRangeStart(methodCall);
|
|
36627
35762
|
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
@@ -36644,7 +35779,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
36644
35779
|
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
36645
35780
|
const call = getDirectCallForExpression(child);
|
|
36646
35781
|
if (!call) return false;
|
|
36647
|
-
const callFunction = findEnclosingFunction
|
|
35782
|
+
const callFunction = findEnclosingFunction(call);
|
|
36648
35783
|
if (callFunction === usageFunction) {
|
|
36649
35784
|
const callStart = getRangeStart(call);
|
|
36650
35785
|
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
@@ -36675,14 +35810,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
|
|
|
36675
35810
|
visitedMutationSymbolIds.add(symbol.id);
|
|
36676
35811
|
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
36677
35812
|
if (typeof usageBoundary !== "number") return true;
|
|
36678
|
-
const usageFunction = findEnclosingFunction
|
|
35813
|
+
const usageFunction = findEnclosingFunction(usageNode);
|
|
36679
35814
|
return symbol.references.some((reference) => {
|
|
36680
35815
|
const referenceStart = getRangeStart(reference.identifier);
|
|
36681
35816
|
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
36682
35817
|
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
36683
35818
|
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
36684
35819
|
if (referenceStart === null) return true;
|
|
36685
|
-
const mutationFunction = findEnclosingFunction
|
|
35820
|
+
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
36686
35821
|
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
36687
35822
|
if (!mutationFunction) return usageFunction !== null;
|
|
36688
35823
|
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
@@ -37227,7 +36362,7 @@ const noMoment = defineRule({
|
|
|
37227
36362
|
});
|
|
37228
36363
|
//#endregion
|
|
37229
36364
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
37230
|
-
const MESSAGE$
|
|
36365
|
+
const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
37231
36366
|
const resolveSettings$16 = (settings) => {
|
|
37232
36367
|
const reactDoctor = settings?.["react-doctor"];
|
|
37233
36368
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -37264,6 +36399,13 @@ const isReactNamespaceExpression = (node, scopes) => {
|
|
|
37264
36399
|
return isReactNamespaceImport(node, scopes);
|
|
37265
36400
|
};
|
|
37266
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
|
+
};
|
|
37267
36409
|
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
37268
36410
|
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
37269
36411
|
visitedSymbolIds.add(symbol.id);
|
|
@@ -37273,7 +36415,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
|
37273
36415
|
}
|
|
37274
36416
|
const init = symbol.initializer;
|
|
37275
36417
|
if (!init) return false;
|
|
37276
|
-
const destructuredPropertyName =
|
|
36418
|
+
const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
|
|
37277
36419
|
if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
|
|
37278
36420
|
if (isReactHocMemberReference(init, scopes)) return true;
|
|
37279
36421
|
if (isNodeOfType(init, "Identifier")) {
|
|
@@ -37579,7 +36721,7 @@ const noMultiComp = defineRule({
|
|
|
37579
36721
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
37580
36722
|
for (const component of flagged.slice(1)) context.report({
|
|
37581
36723
|
node: component.reportNode,
|
|
37582
|
-
message: MESSAGE$
|
|
36724
|
+
message: MESSAGE$18
|
|
37583
36725
|
});
|
|
37584
36726
|
} };
|
|
37585
36727
|
}
|
|
@@ -37792,7 +36934,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
37792
36934
|
};
|
|
37793
36935
|
//#endregion
|
|
37794
36936
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
37795
|
-
const MESSAGE$
|
|
36937
|
+
const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
|
|
37796
36938
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
37797
36939
|
"copyWithin",
|
|
37798
36940
|
"fill",
|
|
@@ -38001,7 +37143,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
38001
37143
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
38002
37144
|
context.report({
|
|
38003
37145
|
node: options.crossFileConsumerCallSite,
|
|
38004
|
-
message: `${MESSAGE$
|
|
37146
|
+
message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
38005
37147
|
});
|
|
38006
37148
|
return;
|
|
38007
37149
|
}
|
|
@@ -38010,7 +37152,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
38010
37152
|
reportedNodes.add(mutation.node);
|
|
38011
37153
|
context.report({
|
|
38012
37154
|
node: mutation.node,
|
|
38013
|
-
message: MESSAGE$
|
|
37155
|
+
message: MESSAGE$17
|
|
38014
37156
|
});
|
|
38015
37157
|
}
|
|
38016
37158
|
};
|
|
@@ -38417,7 +37559,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
38417
37559
|
});
|
|
38418
37560
|
//#endregion
|
|
38419
37561
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
38420
|
-
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.";
|
|
38421
37563
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
38422
37564
|
"onKeyDown",
|
|
38423
37565
|
"onKeyUp",
|
|
@@ -38536,7 +37678,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38536
37678
|
if (numeric === null) {
|
|
38537
37679
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
38538
37680
|
node: tabIndex,
|
|
38539
|
-
message: MESSAGE$
|
|
37681
|
+
message: MESSAGE$16
|
|
38540
37682
|
});
|
|
38541
37683
|
return;
|
|
38542
37684
|
}
|
|
@@ -38558,7 +37700,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38558
37700
|
if (!roleAttribute) {
|
|
38559
37701
|
context.report({
|
|
38560
37702
|
node: tabIndex,
|
|
38561
|
-
message: MESSAGE$
|
|
37703
|
+
message: MESSAGE$16
|
|
38562
37704
|
});
|
|
38563
37705
|
return;
|
|
38564
37706
|
}
|
|
@@ -38572,7 +37714,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
38572
37714
|
}
|
|
38573
37715
|
context.report({
|
|
38574
37716
|
node: tabIndex,
|
|
38575
|
-
message: MESSAGE$
|
|
37717
|
+
message: MESSAGE$16
|
|
38576
37718
|
});
|
|
38577
37719
|
} };
|
|
38578
37720
|
}
|
|
@@ -38999,6 +38141,8 @@ const getWrapperHookWrappedFunction = (initializer) => {
|
|
|
38999
38141
|
if (!wrapped || !isFunctionLike$2(wrapped)) return null;
|
|
39000
38142
|
return wrapped;
|
|
39001
38143
|
};
|
|
38144
|
+
const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
|
|
38145
|
+
const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
|
|
39002
38146
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
39003
38147
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
39004
38148
|
if (!isProp(analysis, innerRef)) return false;
|
|
@@ -39027,6 +38171,12 @@ const getDeclarationKind = (declarator) => {
|
|
|
39027
38171
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
39028
38172
|
};
|
|
39029
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
|
+
};
|
|
39030
38180
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
39031
38181
|
const unwrappedExpression = stripParenExpression(expression);
|
|
39032
38182
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -39036,7 +38186,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
39036
38186
|
if (isProp(analysis, callbackReference)) {
|
|
39037
38187
|
if (isWholePropsObjectReference(analysis, callbackReference)) return null;
|
|
39038
38188
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
39039
|
-
return (bindingIdentifier &&
|
|
38189
|
+
return (bindingIdentifier && getDestructuredPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
39040
38190
|
}
|
|
39041
38191
|
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
39042
38192
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -39046,7 +38196,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
39046
38196
|
visitedVariables.add(callbackVariable);
|
|
39047
38197
|
if (isNodeOfType(declarator.id, "ObjectPattern")) {
|
|
39048
38198
|
const bindingIdentifier = callbackVariable.defs[0]?.name;
|
|
39049
|
-
const propertyName = bindingIdentifier ?
|
|
38199
|
+
const propertyName = bindingIdentifier ? getDestructuredPropertyName(bindingIdentifier) : null;
|
|
39050
38200
|
const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
|
|
39051
38201
|
return propertyName && propsReference ? propertyName : null;
|
|
39052
38202
|
}
|
|
@@ -39149,7 +38299,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
39149
38299
|
const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
|
|
39150
38300
|
if (!bindingProvenance) return null;
|
|
39151
38301
|
const { refCall, variables } = bindingProvenance;
|
|
39152
|
-
const componentFunction = findEnclosingFunction
|
|
38302
|
+
const componentFunction = findEnclosingFunction(effectCall);
|
|
39153
38303
|
if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
39154
38304
|
if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
|
|
39155
38305
|
const callbackPropNames = /* @__PURE__ */ new Set();
|
|
@@ -39313,7 +38463,7 @@ const noPassDataToParent = defineRule({
|
|
|
39313
38463
|
if (isConstant(argRef)) return false;
|
|
39314
38464
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
39315
38465
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
39316
|
-
if (
|
|
38466
|
+
if (resolvesToFunctionBinding(argRef)) return false;
|
|
39317
38467
|
const argIdentifier = argRef.identifier;
|
|
39318
38468
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
39319
38469
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
@@ -39985,6 +39135,174 @@ const noPropCallbackInRender = defineRule({
|
|
|
39985
39135
|
} })
|
|
39986
39136
|
});
|
|
39987
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
|
|
39988
39306
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
39989
39307
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
39990
39308
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -40137,7 +39455,7 @@ const isModuleScopedBinding = (identifier) => {
|
|
|
40137
39455
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
40138
39456
|
if (!binding) return false;
|
|
40139
39457
|
if (isNodeOfType(binding.scopeOwner, "Program")) return true;
|
|
40140
|
-
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction
|
|
39458
|
+
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
|
|
40141
39459
|
return false;
|
|
40142
39460
|
};
|
|
40143
39461
|
const looksLikeFreshUpdateExpression = (expression) => {
|
|
@@ -40192,7 +39510,7 @@ const noRandomKey = defineRule({
|
|
|
40192
39510
|
});
|
|
40193
39511
|
//#endregion
|
|
40194
39512
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
40195
|
-
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.";
|
|
40196
39514
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
40197
39515
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
40198
39516
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -40218,13 +39536,13 @@ const noReactChildren = defineRule({
|
|
|
40218
39536
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
40219
39537
|
context.report({
|
|
40220
39538
|
node: calleeOuter,
|
|
40221
|
-
message: MESSAGE$
|
|
39539
|
+
message: MESSAGE$15
|
|
40222
39540
|
});
|
|
40223
39541
|
return;
|
|
40224
39542
|
}
|
|
40225
39543
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
40226
39544
|
node: calleeOuter,
|
|
40227
|
-
message: MESSAGE$
|
|
39545
|
+
message: MESSAGE$15
|
|
40228
39546
|
});
|
|
40229
39547
|
} })
|
|
40230
39548
|
});
|
|
@@ -40905,7 +40223,7 @@ const noRenderPropChildren = defineRule({
|
|
|
40905
40223
|
});
|
|
40906
40224
|
//#endregion
|
|
40907
40225
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
40908
|
-
const MESSAGE$
|
|
40226
|
+
const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
40909
40227
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
40910
40228
|
const isUsedAsReturnValue = (parent) => {
|
|
40911
40229
|
if (!parent) return false;
|
|
@@ -40923,7 +40241,7 @@ const noRenderReturnValue = defineRule({
|
|
|
40923
40241
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
40924
40242
|
context.report({
|
|
40925
40243
|
node: node.callee,
|
|
40926
|
-
message: MESSAGE$
|
|
40244
|
+
message: MESSAGE$14
|
|
40927
40245
|
});
|
|
40928
40246
|
} })
|
|
40929
40247
|
});
|
|
@@ -41732,7 +41050,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
41732
41050
|
});
|
|
41733
41051
|
//#endregion
|
|
41734
41052
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
41735
|
-
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.";
|
|
41736
41054
|
const noSetState = defineRule({
|
|
41737
41055
|
id: "no-set-state",
|
|
41738
41056
|
title: "Local class state forbidden",
|
|
@@ -41747,7 +41065,7 @@ const noSetState = defineRule({
|
|
|
41747
41065
|
if (!getParentComponent(node)) return;
|
|
41748
41066
|
context.report({
|
|
41749
41067
|
node: node.callee,
|
|
41750
|
-
message: MESSAGE$
|
|
41068
|
+
message: MESSAGE$13
|
|
41751
41069
|
});
|
|
41752
41070
|
} })
|
|
41753
41071
|
});
|
|
@@ -42054,9 +41372,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
|
42054
41372
|
return isReturnedFromEffect;
|
|
42055
41373
|
};
|
|
42056
41374
|
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
42057
|
-
let functionNode = findEnclosingFunction
|
|
41375
|
+
let functionNode = findEnclosingFunction(node);
|
|
42058
41376
|
while (functionNode) {
|
|
42059
|
-
const outerFunction = findEnclosingFunction
|
|
41377
|
+
const outerFunction = findEnclosingFunction(functionNode);
|
|
42060
41378
|
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
42061
41379
|
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
42062
41380
|
functionNode = outerFunction;
|
|
@@ -42064,7 +41382,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
|
42064
41382
|
return false;
|
|
42065
41383
|
};
|
|
42066
41384
|
const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
|
|
42067
|
-
const enclosingFunction = findEnclosingFunction
|
|
41385
|
+
const enclosingFunction = findEnclosingFunction(clearCall);
|
|
42068
41386
|
if (!isFunctionLike$2(enclosingFunction)) return false;
|
|
42069
41387
|
if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
42070
41388
|
const clearStart = getRangeStart(clearCall);
|
|
@@ -42117,7 +41435,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
42117
41435
|
};
|
|
42118
41436
|
//#endregion
|
|
42119
41437
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
42120
|
-
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.";
|
|
42121
41439
|
const DEFAULT_HANDLERS = [
|
|
42122
41440
|
"onClick",
|
|
42123
41441
|
"onMouseDown",
|
|
@@ -42232,7 +41550,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42232
41550
|
if (!roleAttribute || !roleAttribute.value) {
|
|
42233
41551
|
context.report({
|
|
42234
41552
|
node: node.name,
|
|
42235
|
-
message: MESSAGE$
|
|
41553
|
+
message: MESSAGE$12
|
|
42236
41554
|
});
|
|
42237
41555
|
return;
|
|
42238
41556
|
}
|
|
@@ -42242,7 +41560,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42242
41560
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
42243
41561
|
context.report({
|
|
42244
41562
|
node: node.name,
|
|
42245
|
-
message: MESSAGE$
|
|
41563
|
+
message: MESSAGE$12
|
|
42246
41564
|
});
|
|
42247
41565
|
return;
|
|
42248
41566
|
}
|
|
@@ -42250,7 +41568,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42250
41568
|
if (!settings.allowExpressionValues) {
|
|
42251
41569
|
context.report({
|
|
42252
41570
|
node: node.name,
|
|
42253
|
-
message: MESSAGE$
|
|
41571
|
+
message: MESSAGE$12
|
|
42254
41572
|
});
|
|
42255
41573
|
return;
|
|
42256
41574
|
}
|
|
@@ -42258,7 +41576,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42258
41576
|
if (isStaticNullishExpression(expression)) {
|
|
42259
41577
|
context.report({
|
|
42260
41578
|
node: node.name,
|
|
42261
|
-
message: MESSAGE$
|
|
41579
|
+
message: MESSAGE$12
|
|
42262
41580
|
});
|
|
42263
41581
|
return;
|
|
42264
41582
|
}
|
|
@@ -42268,7 +41586,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42268
41586
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
42269
41587
|
context.report({
|
|
42270
41588
|
node: node.name,
|
|
42271
|
-
message: MESSAGE$
|
|
41589
|
+
message: MESSAGE$12
|
|
42272
41590
|
});
|
|
42273
41591
|
return;
|
|
42274
41592
|
}
|
|
@@ -42276,7 +41594,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
42276
41594
|
}
|
|
42277
41595
|
context.report({
|
|
42278
41596
|
node: node.name,
|
|
42279
|
-
message: MESSAGE$
|
|
41597
|
+
message: MESSAGE$12
|
|
42280
41598
|
});
|
|
42281
41599
|
} };
|
|
42282
41600
|
}
|
|
@@ -42382,7 +41700,7 @@ const noStringRefs = defineRule({
|
|
|
42382
41700
|
});
|
|
42383
41701
|
//#endregion
|
|
42384
41702
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
42385
|
-
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)`).";
|
|
42386
41704
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
42387
41705
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
42388
41706
|
const noSyncXhr = defineRule({
|
|
@@ -42403,7 +41721,7 @@ const noSyncXhr = defineRule({
|
|
|
42403
41721
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
42404
41722
|
context.report({
|
|
42405
41723
|
node,
|
|
42406
|
-
message: MESSAGE$
|
|
41724
|
+
message: MESSAGE$11
|
|
42407
41725
|
});
|
|
42408
41726
|
};
|
|
42409
41727
|
return {
|
|
@@ -42428,7 +41746,7 @@ const noSyncXhr = defineRule({
|
|
|
42428
41746
|
});
|
|
42429
41747
|
//#endregion
|
|
42430
41748
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
42431
|
-
const MESSAGE$
|
|
41749
|
+
const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
|
|
42432
41750
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
42433
41751
|
let ancestor = node.parent;
|
|
42434
41752
|
while (ancestor) {
|
|
@@ -42516,7 +41834,7 @@ const noThisInSfc = defineRule({
|
|
|
42516
41834
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
42517
41835
|
context.report({
|
|
42518
41836
|
node,
|
|
42519
|
-
message: MESSAGE$
|
|
41837
|
+
message: MESSAGE$10
|
|
42520
41838
|
});
|
|
42521
41839
|
} };
|
|
42522
41840
|
}
|
|
@@ -42894,7 +42212,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
|
|
|
42894
42212
|
return false;
|
|
42895
42213
|
};
|
|
42896
42214
|
const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
|
|
42897
|
-
const enclosingFunction = findEnclosingFunction
|
|
42215
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
42898
42216
|
if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
42899
42217
|
let currentNode = node;
|
|
42900
42218
|
while (currentNode !== enclosingFunction) {
|
|
@@ -44161,11 +43479,7 @@ const resolveSettings$8 = (settings) => {
|
|
|
44161
43479
|
propNamePattern: ruleSettings.propNamePattern ?? "render*"
|
|
44162
43480
|
};
|
|
44163
43481
|
};
|
|
44164
|
-
const
|
|
44165
|
-
allowGlobalReactNamespace: true,
|
|
44166
|
-
resolveNamedAliases: true
|
|
44167
|
-
});
|
|
44168
|
-
const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
43482
|
+
const expressionContainsJsxOrCreateElement = (root) => {
|
|
44169
43483
|
let found = false;
|
|
44170
43484
|
walkAst(root, (node) => {
|
|
44171
43485
|
if (found) return false;
|
|
@@ -44174,17 +43488,17 @@ const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
|
44174
43488
|
found = true;
|
|
44175
43489
|
return false;
|
|
44176
43490
|
}
|
|
44177
|
-
if (isNodeOfType(node, "CallExpression") &&
|
|
43491
|
+
if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
|
|
44178
43492
|
found = true;
|
|
44179
43493
|
return false;
|
|
44180
43494
|
}
|
|
44181
43495
|
});
|
|
44182
43496
|
return found;
|
|
44183
43497
|
};
|
|
44184
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode
|
|
44185
|
-
const isReactClassComponent = (classNode
|
|
43498
|
+
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
|
|
43499
|
+
const isReactClassComponent = (classNode) => {
|
|
44186
43500
|
if (isEs6Component(classNode)) return true;
|
|
44187
|
-
return expressionContainsJsxOrCreateElement(classNode
|
|
43501
|
+
return expressionContainsJsxOrCreateElement(classNode);
|
|
44188
43502
|
};
|
|
44189
43503
|
const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
44190
43504
|
let walker = node.parent;
|
|
@@ -44201,7 +43515,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
|
44201
43515
|
};
|
|
44202
43516
|
}
|
|
44203
43517
|
if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
|
|
44204
|
-
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker
|
|
43518
|
+
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
|
|
44205
43519
|
component: walker,
|
|
44206
43520
|
name: walker.id.name
|
|
44207
43521
|
};
|
|
@@ -44290,12 +43604,12 @@ const isObjectCallbackCandidate = (node) => {
|
|
|
44290
43604
|
}
|
|
44291
43605
|
return false;
|
|
44292
43606
|
};
|
|
44293
|
-
const hocCallContainsComponent = (call
|
|
43607
|
+
const hocCallContainsComponent = (call) => {
|
|
44294
43608
|
const firstArgument = call.arguments[0];
|
|
44295
43609
|
if (!firstArgument) return false;
|
|
44296
|
-
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument
|
|
44297
|
-
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument
|
|
44298
|
-
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);
|
|
44299
43613
|
};
|
|
44300
43614
|
const isFirstArgumentOfHocCall = (node) => {
|
|
44301
43615
|
const parent = node.parent;
|
|
@@ -44328,35 +43642,19 @@ const isReturnOfMapCallback = (node) => {
|
|
|
44328
43642
|
}
|
|
44329
43643
|
return false;
|
|
44330
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
|
+
};
|
|
44331
43651
|
const TS_VALUE_PASSTHROUGH_TYPES = new Set([
|
|
44332
43652
|
"TSAsExpression",
|
|
44333
43653
|
"TSNonNullExpression",
|
|
44334
43654
|
"TSSatisfiesExpression",
|
|
44335
43655
|
"TSTypeAssertion"
|
|
44336
43656
|
]);
|
|
44337
|
-
const
|
|
44338
|
-
"as",
|
|
44339
|
-
"body",
|
|
44340
|
-
"calendarcontainer",
|
|
44341
|
-
"component",
|
|
44342
|
-
"fallback",
|
|
44343
|
-
"tooltip"
|
|
44344
|
-
]);
|
|
44345
|
-
const isElementTypeJsxAttribute = (node) => {
|
|
44346
|
-
if (!isNodeOfType(node, "JSXAttribute")) return false;
|
|
44347
|
-
if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
|
|
44348
|
-
const attributeName = node.name.name;
|
|
44349
|
-
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
44350
|
-
};
|
|
44351
|
-
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
44352
|
-
allowGlobalReactNamespace: true,
|
|
44353
|
-
resolveNamedAliases: true
|
|
44354
|
-
});
|
|
44355
|
-
const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
|
|
44356
|
-
allowGlobalReactNamespace: true,
|
|
44357
|
-
resolveNamedAliases: true
|
|
44358
|
-
});
|
|
44359
|
-
const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
43657
|
+
const isRenderFlowingReadReference = (identifier) => {
|
|
44360
43658
|
let valueNode = identifier;
|
|
44361
43659
|
let parent = valueNode.parent;
|
|
44362
43660
|
while (parent) {
|
|
@@ -44366,26 +43664,20 @@ const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @_
|
|
|
44366
43664
|
continue;
|
|
44367
43665
|
}
|
|
44368
43666
|
switch (parent.type) {
|
|
44369
|
-
case "
|
|
44370
|
-
case "
|
|
44371
|
-
case "
|
|
44372
|
-
case "ArrowFunctionExpression": return false;
|
|
43667
|
+
case "JSXExpressionContainer":
|
|
43668
|
+
case "ReturnStatement": return true;
|
|
43669
|
+
case "ArrowFunctionExpression": return parent.body === valueNode;
|
|
44373
43670
|
case "CallExpression":
|
|
44374
43671
|
if (parent.callee === valueNode) return false;
|
|
44375
|
-
if (
|
|
44376
|
-
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
43672
|
+
if (isCreateElementCall(parent) || isCloneElementCall(parent)) return true;
|
|
44377
43673
|
valueNode = parent;
|
|
44378
43674
|
parent = parent.parent;
|
|
44379
43675
|
continue;
|
|
44380
|
-
case "VariableDeclarator":
|
|
44381
|
-
|
|
44382
|
-
const
|
|
44383
|
-
|
|
44384
|
-
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
44385
|
-
nextVisitedSymbols.add(aliasSymbol.id);
|
|
44386
|
-
return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
|
|
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);
|
|
44387
43680
|
}
|
|
44388
|
-
case "AssignmentExpression": return false;
|
|
44389
43681
|
case "Property":
|
|
44390
43682
|
if (parent.value !== valueNode) return false;
|
|
44391
43683
|
valueNode = parent;
|
|
@@ -44423,7 +43715,6 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44423
43715
|
severity: "warn",
|
|
44424
43716
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
44425
43717
|
category: "Performance",
|
|
44426
|
-
tags: ["react-jsx-only"],
|
|
44427
43718
|
create: (context) => {
|
|
44428
43719
|
const settings = resolveSettings$8(context.settings);
|
|
44429
43720
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -44486,7 +43777,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44486
43777
|
},
|
|
44487
43778
|
Identifier(node) {
|
|
44488
43779
|
if (!isReactComponentName(node.name)) return;
|
|
44489
|
-
if (!isRenderFlowingReadReference(node
|
|
43780
|
+
if (!isRenderFlowingReadReference(node)) return;
|
|
44490
43781
|
recordInstantiation(node, node.name);
|
|
44491
43782
|
},
|
|
44492
43783
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -44495,33 +43786,28 @@ const noUnstableNestedComponents = defineRule({
|
|
|
44495
43786
|
ClassDeclaration(node) {
|
|
44496
43787
|
if (!node.id) return;
|
|
44497
43788
|
if (!isReactComponentName(node.id.name)) return;
|
|
44498
|
-
if (!isReactClassComponent(node
|
|
43789
|
+
if (!isReactClassComponent(node)) return;
|
|
44499
43790
|
enqueueCandidate(node, null);
|
|
44500
43791
|
},
|
|
44501
43792
|
ClassExpression(node) {
|
|
44502
43793
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
44503
43794
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
44504
|
-
if (!isReactClassComponent(node
|
|
43795
|
+
if (!isReactClassComponent(node)) return;
|
|
44505
43796
|
enqueueCandidate(node, null);
|
|
44506
43797
|
},
|
|
44507
43798
|
CallExpression(node) {
|
|
44508
|
-
if (
|
|
43799
|
+
if (isCreateElementCall(node)) {
|
|
44509
43800
|
const firstArgument = node.arguments[0];
|
|
44510
43801
|
if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
|
|
44511
43802
|
else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
|
|
44512
43803
|
}
|
|
44513
|
-
|
|
44514
|
-
if (!
|
|
44515
|
-
|
|
44516
|
-
const inferredName = inferFunctionLikeName(node);
|
|
44517
|
-
const propInfo = isComponentDeclaredInProp(node);
|
|
44518
|
-
if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
|
|
44519
|
-
enqueueCandidate(node, propInfo === null ? inferredName : null);
|
|
43804
|
+
if (!isHocCallee$1(node)) return;
|
|
43805
|
+
if (!hocCallContainsComponent(node)) return;
|
|
43806
|
+
enqueueCandidate(node, null);
|
|
44520
43807
|
},
|
|
44521
43808
|
"Program:exit"() {
|
|
44522
43809
|
for (const report of queuedReports) {
|
|
44523
43810
|
if (report.requiredInstantiationName !== null) {
|
|
44524
|
-
if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
|
|
44525
43811
|
if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
|
|
44526
43812
|
}
|
|
44527
43813
|
context.report({
|
|
@@ -44695,7 +43981,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
44695
43981
|
//#endregion
|
|
44696
43982
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
44697
43983
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
44698
|
-
const MESSAGE$
|
|
43984
|
+
const MESSAGE$9 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
44699
43985
|
const resolveSettings$7 = (settings) => {
|
|
44700
43986
|
const reactDoctor = settings?.["react-doctor"];
|
|
44701
43987
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -44729,7 +44015,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
44729
44015
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
44730
44016
|
context.report({
|
|
44731
44017
|
node: node.callee,
|
|
44732
|
-
message: MESSAGE$
|
|
44018
|
+
message: MESSAGE$9
|
|
44733
44019
|
});
|
|
44734
44020
|
} };
|
|
44735
44021
|
}
|
|
@@ -45554,7 +44840,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
|
|
|
45554
44840
|
const symbol = scopes.symbolFor(identifier);
|
|
45555
44841
|
if (symbol) {
|
|
45556
44842
|
if (symbol.kind !== "parameter") return null;
|
|
45557
|
-
const declaringFunction = findEnclosingFunction
|
|
44843
|
+
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45558
44844
|
if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
|
|
45559
44845
|
return null;
|
|
45560
44846
|
}
|
|
@@ -45600,7 +44886,7 @@ const isChildrenMemberExpression = (node, scopes) => {
|
|
|
45600
44886
|
const symbol = scopes.symbolFor(propsObject);
|
|
45601
44887
|
if (symbol) {
|
|
45602
44888
|
if (symbol.kind !== "parameter") return false;
|
|
45603
|
-
const declaringFunction = findEnclosingFunction
|
|
44889
|
+
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45604
44890
|
return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
|
|
45605
44891
|
}
|
|
45606
44892
|
return hasComponentLikeAncestorFunction(node);
|
|
@@ -45725,7 +45011,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
45725
45011
|
});
|
|
45726
45012
|
//#endregion
|
|
45727
45013
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
45728
|
-
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.";
|
|
45729
45015
|
const preactPreferOndblclick = defineRule({
|
|
45730
45016
|
id: "preact-prefer-ondblclick",
|
|
45731
45017
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -45740,7 +45026,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
45740
45026
|
if (!onDoubleClickAttribute) return;
|
|
45741
45027
|
context.report({
|
|
45742
45028
|
node: onDoubleClickAttribute,
|
|
45743
|
-
message: MESSAGE$
|
|
45029
|
+
message: MESSAGE$8
|
|
45744
45030
|
});
|
|
45745
45031
|
} })
|
|
45746
45032
|
});
|
|
@@ -45991,7 +45277,7 @@ const preferExplicitVariants = defineRule({
|
|
|
45991
45277
|
});
|
|
45992
45278
|
//#endregion
|
|
45993
45279
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
45994
|
-
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.";
|
|
45995
45281
|
const resolveSettings$4 = (settings) => {
|
|
45996
45282
|
const reactDoctor = settings?.["react-doctor"];
|
|
45997
45283
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -46030,7 +45316,7 @@ const preferFunctionComponent = defineRule({
|
|
|
46030
45316
|
const reportNode = node.id ?? node;
|
|
46031
45317
|
context.report({
|
|
46032
45318
|
node: reportNode,
|
|
46033
|
-
message: MESSAGE$
|
|
45319
|
+
message: MESSAGE$7
|
|
46034
45320
|
});
|
|
46035
45321
|
};
|
|
46036
45322
|
return {
|
|
@@ -46252,7 +45538,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
|
|
|
46252
45538
|
//#endregion
|
|
46253
45539
|
//#region src/plugin/utils/enclosing-component-or-hook-scope.ts
|
|
46254
45540
|
const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
|
|
46255
|
-
const functionNode = findEnclosingFunction
|
|
45541
|
+
const functionNode = findEnclosingFunction(startNode);
|
|
46256
45542
|
if (!functionNode) return null;
|
|
46257
45543
|
const displayName = componentOrHookDisplayNameForFunction(functionNode);
|
|
46258
45544
|
if (!displayName) return null;
|
|
@@ -47314,7 +46600,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
|
|
|
47314
46600
|
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
47315
46601
|
const isHookReturnForwardingSpread = (objectExpression) => {
|
|
47316
46602
|
const objectParent = objectExpression.parent;
|
|
47317
|
-
const enclosingFunction = findEnclosingFunction
|
|
46603
|
+
const enclosingFunction = findEnclosingFunction(objectExpression);
|
|
47318
46604
|
if (!enclosingFunction) return false;
|
|
47319
46605
|
if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
|
|
47320
46606
|
const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
|
|
@@ -47608,258 +46894,7 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
47608
46894
|
}
|
|
47609
46895
|
});
|
|
47610
46896
|
//#endregion
|
|
47611
|
-
//#region src/plugin/utils/is-node-conditionally-executed.ts
|
|
47612
|
-
const isNodeConditionallyExecuted = (node, boundary) => {
|
|
47613
|
-
let child = node;
|
|
47614
|
-
let parent = child.parent ?? null;
|
|
47615
|
-
while (parent && parent !== boundary) {
|
|
47616
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) return true;
|
|
47617
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) return true;
|
|
47618
|
-
if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) return true;
|
|
47619
|
-
child = parent;
|
|
47620
|
-
parent = child.parent ?? null;
|
|
47621
|
-
}
|
|
47622
|
-
return false;
|
|
47623
|
-
};
|
|
47624
|
-
//#endregion
|
|
47625
|
-
//#region src/plugin/rules/tanstack-query/utils/resolve-tanstack-query-hook-name.ts
|
|
47626
|
-
const resolveTanstackNamespaceHookName = (memberExpression, contextNode, scopes) => {
|
|
47627
|
-
const hookName = getStaticPropertyKeyName(memberExpression, { allowComputedString: true });
|
|
47628
|
-
const namespaceObject = stripParenExpression(memberExpression.object);
|
|
47629
|
-
if (!hookName || !TANSTACK_QUERY_HOOKS.has(hookName)) return null;
|
|
47630
|
-
if (!isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
47631
|
-
const resolvedNamespaceSymbol = resolveConstIdentifierAlias(namespaceObject, scopes);
|
|
47632
|
-
if (resolvedNamespaceSymbol?.kind !== "import") return null;
|
|
47633
|
-
const namespaceBinding = getImportBindingForName(contextNode, resolvedNamespaceSymbol.name);
|
|
47634
|
-
return namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source) ? hookName : null;
|
|
47635
|
-
};
|
|
47636
|
-
const resolveTanstackQueryHookName = (callExpression, scopes) => {
|
|
47637
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
47638
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
47639
|
-
const resolvedSymbol = resolveConstIdentifierAlias(callee, scopes);
|
|
47640
|
-
if (!resolvedSymbol) return null;
|
|
47641
|
-
if (resolvedSymbol.kind === "const" && resolvedSymbol.initializer) {
|
|
47642
|
-
const initializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47643
|
-
return isNodeOfType(initializer, "MemberExpression") ? resolveTanstackNamespaceHookName(initializer, callExpression, scopes) : null;
|
|
47644
|
-
}
|
|
47645
|
-
if (resolvedSymbol.kind !== "import") return null;
|
|
47646
|
-
const importBinding = getImportBindingForName(callExpression, resolvedSymbol.name);
|
|
47647
|
-
if (importBinding === null) return null;
|
|
47648
|
-
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
47649
|
-
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
47650
|
-
}
|
|
47651
|
-
if (isNodeOfType(callee, "MemberExpression")) return resolveTanstackNamespaceHookName(callee, callExpression, scopes);
|
|
47652
|
-
return null;
|
|
47653
|
-
};
|
|
47654
|
-
const resolveTanstackQueryHookNameFromInitializer = (initializer, scopes) => {
|
|
47655
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
47656
|
-
if (isNodeOfType(unwrappedInitializer, "CallExpression")) return resolveTanstackQueryHookName(unwrappedInitializer, scopes);
|
|
47657
|
-
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return null;
|
|
47658
|
-
const resolvedSymbol = resolveConstIdentifierAlias(unwrappedInitializer, scopes);
|
|
47659
|
-
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
47660
|
-
const resolvedInitializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47661
|
-
if (!isNodeOfType(resolvedInitializer, "CallExpression")) return null;
|
|
47662
|
-
return resolveTanstackQueryHookName(resolvedInitializer, scopes);
|
|
47663
|
-
};
|
|
47664
|
-
//#endregion
|
|
47665
46897
|
//#region src/plugin/rules/tanstack-query/query-no-query-in-effect.ts
|
|
47666
|
-
const isTanstackQueryResult = (expression, context) => Boolean(resolveTanstackQueryHookNameFromInitializer(expression, context.scopes));
|
|
47667
|
-
const isStaticRefetchMember = (memberExpression) => getStaticPropertyKeyName(memberExpression, { allowComputedString: true }) === "refetch";
|
|
47668
|
-
const resolveCalledFunction = (callee, context) => {
|
|
47669
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
47670
|
-
if (isFunctionLike$2(unwrappedCallee)) return unwrappedCallee;
|
|
47671
|
-
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
47672
|
-
const symbol = resolveConstIdentifierAlias(unwrappedCallee, context.scopes);
|
|
47673
|
-
if (!symbol) return null;
|
|
47674
|
-
const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
|
|
47675
|
-
return candidate && isFunctionLike$2(candidate) ? candidate : null;
|
|
47676
|
-
};
|
|
47677
|
-
const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
47678
|
-
if (!isFunctionLike$2(functionNode)) return true;
|
|
47679
|
-
if (functionNode.generator) return true;
|
|
47680
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47681
|
-
if (boundaryStart === null) return true;
|
|
47682
|
-
let hasSuspension = false;
|
|
47683
|
-
walkAst(functionNode, (node) => {
|
|
47684
|
-
if (node !== functionNode && isFunctionLike$2(node)) return false;
|
|
47685
|
-
if (!isNodeOfType(node, "AwaitExpression") || !isNodeReachableWithinFunction(node, context)) return;
|
|
47686
|
-
const suspensionStart = getRangeStart(node);
|
|
47687
|
-
if (suspensionStart !== null && suspensionStart < boundaryStart) {
|
|
47688
|
-
hasSuspension = true;
|
|
47689
|
-
return false;
|
|
47690
|
-
}
|
|
47691
|
-
});
|
|
47692
|
-
return hasSuspension;
|
|
47693
|
-
};
|
|
47694
|
-
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47695
|
-
let enclosingFunction = findEnclosingFunction$1(functionNode);
|
|
47696
|
-
while (enclosingFunction) {
|
|
47697
|
-
if (enclosingFunction === ancestor) return true;
|
|
47698
|
-
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47699
|
-
}
|
|
47700
|
-
return false;
|
|
47701
|
-
};
|
|
47702
|
-
const isUnconditionallyExecuted = (node, functionNode, context) => context.cfg.isUnconditionalFromEntry(node) && !isNodeConditionallyExecuted(node, functionNode) && !(isNodeOfType(node, "MemberExpression") && isNodeOfType(node.parent, "AssignmentExpression") && node.parent.left === node && (node.parent.operator === "&&=" || node.parent.operator === "||=" || node.parent.operator === "??="));
|
|
47703
|
-
const functionInvokesTarget = (callerFunction, targetFunction, context, visitedFunctions, canCrossSuspension = false) => {
|
|
47704
|
-
if (visitedFunctions.has(callerFunction)) return false;
|
|
47705
|
-
visitedFunctions.add(callerFunction);
|
|
47706
|
-
let invokesTarget = false;
|
|
47707
|
-
walkAst(callerFunction, (node) => {
|
|
47708
|
-
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47709
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47710
|
-
if (!isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context) || !canCrossSuspension && hasSuspensionBefore(callerFunction, node, context)) return;
|
|
47711
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47712
|
-
if (calledFunction === targetFunction || calledFunction && functionInvokesTarget(calledFunction, targetFunction, context, visitedFunctions, canCrossSuspension)) {
|
|
47713
|
-
invokesTarget = true;
|
|
47714
|
-
return false;
|
|
47715
|
-
}
|
|
47716
|
-
});
|
|
47717
|
-
return invokesTarget;
|
|
47718
|
-
};
|
|
47719
|
-
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47720
|
-
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47721
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47722
|
-
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47723
|
-
let isInvokedBefore = false;
|
|
47724
|
-
walkAst(boundaryFunction, (node) => {
|
|
47725
|
-
if (node !== boundaryFunction && isFunctionLike$2(node)) return false;
|
|
47726
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47727
|
-
const callStart = getRangeStart(node);
|
|
47728
|
-
if (callStart === null || callStart >= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, boundaryFunction, context) || hasSuspensionBefore(boundaryFunction, node, context)) return;
|
|
47729
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47730
|
-
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set())) {
|
|
47731
|
-
isInvokedBefore = true;
|
|
47732
|
-
return false;
|
|
47733
|
-
}
|
|
47734
|
-
});
|
|
47735
|
-
return isInvokedBefore;
|
|
47736
|
-
};
|
|
47737
|
-
const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, context) => {
|
|
47738
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47739
|
-
if (boundaryStart === null) return false;
|
|
47740
|
-
let isInvokedAfter = false;
|
|
47741
|
-
walkAst(callerFunction, (node) => {
|
|
47742
|
-
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47743
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47744
|
-
const callStart = getRangeStart(node);
|
|
47745
|
-
if (callStart === null || callStart <= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context)) return;
|
|
47746
|
-
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47747
|
-
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set(), true)) {
|
|
47748
|
-
isInvokedAfter = true;
|
|
47749
|
-
return false;
|
|
47750
|
-
}
|
|
47751
|
-
});
|
|
47752
|
-
return isInvokedAfter;
|
|
47753
|
-
};
|
|
47754
|
-
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47755
|
-
const writeStart = getRangeStart(writeNode);
|
|
47756
|
-
const boundaryStart = getRangeStart(boundary);
|
|
47757
|
-
const writeFunction = findEnclosingFunction$1(writeNode);
|
|
47758
|
-
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47759
|
-
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47760
|
-
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47761
|
-
const renderFunction = deferredExecutionFunction ? findEnclosingFunction$1(deferredExecutionFunction) : null;
|
|
47762
|
-
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47763
|
-
if (!writeFunction) return writeStart < boundaryStart;
|
|
47764
|
-
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
47765
|
-
if (Boolean(renderFunction && (writeFunction === renderFunction || isFunctionAncestor(writeFunction, renderFunction)))) return true;
|
|
47766
|
-
if (deferredExecutionFunction && boundaryFunction !== deferredExecutionFunction) {
|
|
47767
|
-
if (hasSuspensionBefore(boundaryFunction, boundary, context) && isFunctionInvokedBefore(boundaryFunction, writeNode, context)) return true;
|
|
47768
|
-
return isFunctionInvokedAfter(boundaryFunction, writeNode, writeFunction, context);
|
|
47769
|
-
}
|
|
47770
|
-
return writeStart < boundaryStart;
|
|
47771
|
-
}
|
|
47772
|
-
if (renderFunction && isFunctionAncestor(renderFunction, writeFunction) && !hasSuspensionBefore(writeFunction, writeNode, context) && functionInvokesTarget(renderFunction, writeFunction, context, /* @__PURE__ */ new Set())) return true;
|
|
47773
|
-
return !hasSuspensionBefore(writeFunction, writeNode, context) && isFunctionInvokedBefore(writeFunction, boundary, context);
|
|
47774
|
-
};
|
|
47775
|
-
const getStaticStringValue = (node) => {
|
|
47776
|
-
const unwrappedNode = stripParenExpression(node);
|
|
47777
|
-
if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "string") return unwrappedNode.value;
|
|
47778
|
-
if (isNodeOfType(unwrappedNode, "TemplateLiteral") && unwrappedNode.expressions.length === 0) return getStaticTemplateLiteralValue(unwrappedNode);
|
|
47779
|
-
return null;
|
|
47780
|
-
};
|
|
47781
|
-
const isSameRefetchMember = (target, candidate, context) => {
|
|
47782
|
-
const unwrappedTarget = stripParenExpression(target);
|
|
47783
|
-
const unwrappedCandidate = stripParenExpression(candidate);
|
|
47784
|
-
if (!isNodeOfType(unwrappedTarget, "Identifier") || !isNodeOfType(unwrappedCandidate, "MemberExpression") || !isStaticRefetchMember(unwrappedCandidate)) return false;
|
|
47785
|
-
const candidateTarget = stripParenExpression(unwrappedCandidate.object);
|
|
47786
|
-
if (!isNodeOfType(candidateTarget, "Identifier")) return false;
|
|
47787
|
-
const targetSymbol = resolveConstIdentifierAlias(unwrappedTarget, context.scopes);
|
|
47788
|
-
const candidateSymbol = resolveConstIdentifierAlias(candidateTarget, context.scopes);
|
|
47789
|
-
return Boolean(targetSymbol && candidateSymbol?.id === targetSymbol.id);
|
|
47790
|
-
};
|
|
47791
|
-
const getRefetchMutationTarget = (node, context) => {
|
|
47792
|
-
if (isNodeOfType(node, "MemberExpression") && isStaticRefetchMember(node)) {
|
|
47793
|
-
const parent = node.parent;
|
|
47794
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.left === node && isSameRefetchMember(node.object, parent.right, context)) return null;
|
|
47795
|
-
return isNodeOfType(parent, "AssignmentExpression") && parent.left === node || isNodeOfType(parent, "UpdateExpression") && parent.argument === node || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" ? node.object : null;
|
|
47796
|
-
}
|
|
47797
|
-
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
47798
|
-
const callee = stripParenExpression(node.callee);
|
|
47799
|
-
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47800
|
-
const receiver = stripParenExpression(callee.object);
|
|
47801
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || context.scopes.symbolFor(receiver)) return null;
|
|
47802
|
-
const methodName = getStaticPropertyKeyName(callee, { allowComputedString: true });
|
|
47803
|
-
const target = node.arguments[0];
|
|
47804
|
-
if (!target || isNodeOfType(target, "SpreadElement")) return null;
|
|
47805
|
-
if (methodName === "defineProperty") {
|
|
47806
|
-
const propertyKey = node.arguments[1];
|
|
47807
|
-
if (!propertyKey || getStaticStringValue(propertyKey) !== "refetch") return null;
|
|
47808
|
-
const descriptor = node.arguments[2];
|
|
47809
|
-
if (isNodeOfType(descriptor, "ObjectExpression")) {
|
|
47810
|
-
const valueProperty = descriptor.properties.find((property) => isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "value");
|
|
47811
|
-
if (isNodeOfType(valueProperty, "Property") && isSameRefetchMember(target, valueProperty.value, context)) return null;
|
|
47812
|
-
}
|
|
47813
|
-
return target;
|
|
47814
|
-
}
|
|
47815
|
-
if (methodName !== "assign") return null;
|
|
47816
|
-
let finalRefetchValue = null;
|
|
47817
|
-
for (const source of node.arguments.slice(1)) {
|
|
47818
|
-
if (!isNodeOfType(source, "ObjectExpression")) continue;
|
|
47819
|
-
for (const property of source.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "refetch") finalRefetchValue = property.value;
|
|
47820
|
-
}
|
|
47821
|
-
if (!finalRefetchValue || isSameRefetchMember(target, finalRefetchValue, context)) return null;
|
|
47822
|
-
return target;
|
|
47823
|
-
};
|
|
47824
|
-
const hasRefetchMemberWriteBefore = (expression, boundary, context, deferredExecutionFunction) => {
|
|
47825
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
47826
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47827
|
-
const resultSymbol = resolveConstIdentifierAlias(unwrappedExpression, context.scopes);
|
|
47828
|
-
if (!resultSymbol) return false;
|
|
47829
|
-
const program = findProgramRoot(expression);
|
|
47830
|
-
if (!program) return true;
|
|
47831
|
-
if (getRangeStart(boundary) === null) return true;
|
|
47832
|
-
let hasWrite = false;
|
|
47833
|
-
walkAst(program, (node) => {
|
|
47834
|
-
const mutationTarget = getRefetchMutationTarget(node, context);
|
|
47835
|
-
if (!mutationTarget) return;
|
|
47836
|
-
if (!isWriteExecutedBefore(node, boundary, context, deferredExecutionFunction)) return;
|
|
47837
|
-
const object = stripParenExpression(mutationTarget);
|
|
47838
|
-
if (!isNodeOfType(object, "Identifier")) return;
|
|
47839
|
-
if (resolveConstIdentifierAlias(object, context.scopes)?.id === resultSymbol.id) {
|
|
47840
|
-
hasWrite = true;
|
|
47841
|
-
return false;
|
|
47842
|
-
}
|
|
47843
|
-
});
|
|
47844
|
-
return hasWrite;
|
|
47845
|
-
};
|
|
47846
|
-
const isTanstackRefetchExpression = (expression, context, deferredExecutionFunction, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47847
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
47848
|
-
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return isStaticRefetchMember(unwrappedExpression) && isTanstackQueryResult(unwrappedExpression.object, context) && !hasRefetchMemberWriteBefore(unwrappedExpression.object, unwrappedExpression, context, deferredExecutionFunction);
|
|
47849
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47850
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
47851
|
-
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read") || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
47852
|
-
visitedSymbolIds.add(symbol.id);
|
|
47853
|
-
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
47854
|
-
if (isNodeOfType(bindingProperty, "Property") && getStaticPropertyKeyName(bindingProperty, { allowComputedString: true }) === "refetch") {
|
|
47855
|
-
const initializer = symbol.declarationNode.init;
|
|
47856
|
-
return Boolean(initializer && isTanstackQueryResult(initializer, context) && !hasRefetchMemberWriteBefore(initializer, symbol.declarationNode, context, null));
|
|
47857
|
-
}
|
|
47858
|
-
return Boolean(symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer && isTanstackRefetchExpression(symbol.initializer, context, null, visitedSymbolIds));
|
|
47859
|
-
};
|
|
47860
|
-
const isTanstackRefetchCall = (callExpression, context, effectCallback) => {
|
|
47861
|
-
return isTanstackRefetchExpression(callExpression.callee, context, effectCallback);
|
|
47862
|
-
};
|
|
47863
46898
|
const queryNoQueryInEffect = defineRule({
|
|
47864
46899
|
id: "query-no-query-in-effect",
|
|
47865
46900
|
title: "Query refetch inside useEffect",
|
|
@@ -47875,7 +46910,8 @@ const queryNoQueryInEffect = defineRule({
|
|
|
47875
46910
|
walkAst(callback, (child) => {
|
|
47876
46911
|
if (child !== callback && isFunctionLike$2(child) && !effectInvokedFunctions.has(child)) return false;
|
|
47877
46912
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
47878
|
-
|
|
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({
|
|
47879
46915
|
node: child,
|
|
47880
46916
|
message: "refetch() inside useEffect duplicates work React Query already does, causing extra fetches."
|
|
47881
46917
|
});
|
|
@@ -47884,6 +46920,28 @@ const queryNoQueryInEffect = defineRule({
|
|
|
47884
46920
|
});
|
|
47885
46921
|
//#endregion
|
|
47886
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
|
+
};
|
|
47887
46945
|
const queryNoRestDestructuring = defineRule({
|
|
47888
46946
|
id: "query-no-rest-destructuring",
|
|
47889
46947
|
title: "Rest destructuring on query result",
|
|
@@ -47895,7 +46953,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
47895
46953
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
47896
46954
|
if (!node.init) return;
|
|
47897
46955
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
47898
|
-
const hookName =
|
|
46956
|
+
const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
|
|
47899
46957
|
if (!hookName) return;
|
|
47900
46958
|
context.report({
|
|
47901
46959
|
node: node.id,
|
|
@@ -48030,8 +47088,8 @@ const queryStableQueryClient = defineRule({
|
|
|
48030
47088
|
create: (context) => ({ NewExpression(node) {
|
|
48031
47089
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
|
|
48032
47090
|
if (isStableHookWrapperArgument(node)) return;
|
|
48033
|
-
let enclosingFunction = findEnclosingFunction
|
|
48034
|
-
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction
|
|
47091
|
+
let enclosingFunction = findEnclosingFunction(node);
|
|
47092
|
+
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
48035
47093
|
if (!enclosingFunction) return;
|
|
48036
47094
|
if (!isComponentFunction(enclosingFunction)) return;
|
|
48037
47095
|
context.report({
|
|
@@ -48126,7 +47184,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
48126
47184
|
const comparatorArgument = node.arguments?.[1];
|
|
48127
47185
|
if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
|
|
48128
47186
|
} else {
|
|
48129
|
-
const enclosingFunction = findEnclosingFunction
|
|
47187
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
48130
47188
|
if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
|
|
48131
47189
|
}
|
|
48132
47190
|
const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
|
|
@@ -48139,7 +47197,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
48139
47197
|
});
|
|
48140
47198
|
//#endregion
|
|
48141
47199
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
48142
|
-
const MESSAGE$
|
|
47200
|
+
const MESSAGE$6 = "This JSX crashes because `React` isn't in scope.";
|
|
48143
47201
|
const reactInJsxScope = defineRule({
|
|
48144
47202
|
id: "react-in-jsx-scope",
|
|
48145
47203
|
title: "React not in scope for JSX",
|
|
@@ -48151,190 +47209,19 @@ const reactInJsxScope = defineRule({
|
|
|
48151
47209
|
if (findVariableInitializer(node, "React")) return;
|
|
48152
47210
|
context.report({
|
|
48153
47211
|
node: node.name,
|
|
48154
|
-
message: MESSAGE$
|
|
47212
|
+
message: MESSAGE$6
|
|
48155
47213
|
});
|
|
48156
47214
|
},
|
|
48157
47215
|
JSXFragment(node) {
|
|
48158
47216
|
if (findVariableInitializer(node, "React")) return;
|
|
48159
47217
|
context.report({
|
|
48160
47218
|
node: node.openingFragment,
|
|
48161
|
-
message: MESSAGE$
|
|
47219
|
+
message: MESSAGE$6
|
|
48162
47220
|
});
|
|
48163
47221
|
}
|
|
48164
47222
|
})
|
|
48165
47223
|
});
|
|
48166
47224
|
//#endregion
|
|
48167
|
-
//#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
|
|
48168
|
-
const MESSAGE$6 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
|
|
48169
|
-
const REACT_MARKDOWN_MODULE = "react-markdown";
|
|
48170
|
-
const REHYPE_RAW_MODULE = "rehype-raw";
|
|
48171
|
-
const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
|
|
48172
|
-
const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
|
|
48173
|
-
const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
|
|
48174
|
-
const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
|
|
48175
|
-
const DEFAULT_EXPORT_NAMES = new Set(["default"]);
|
|
48176
|
-
const getImportDeclaration = (symbol) => {
|
|
48177
|
-
if (symbol.kind !== "import") return null;
|
|
48178
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
48179
|
-
return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
|
|
48180
|
-
};
|
|
48181
|
-
const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
|
|
48182
|
-
const isDefaultImportSymbol = (symbol, moduleName) => {
|
|
48183
|
-
if (!isImportFromModule(symbol, moduleName)) return false;
|
|
48184
|
-
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
48185
|
-
};
|
|
48186
|
-
const resolveImportedIdentifier = (node, scopes) => {
|
|
48187
|
-
if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
|
|
48188
|
-
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
48189
|
-
return symbol?.kind === "import" ? symbol : null;
|
|
48190
|
-
};
|
|
48191
|
-
const getStaticMemberParts = (node) => {
|
|
48192
|
-
if (isNodeOfType(node, "MemberExpression")) {
|
|
48193
|
-
if (node.computed || !isNodeOfType(node.property, "Identifier")) return null;
|
|
48194
|
-
return {
|
|
48195
|
-
object: node.object,
|
|
48196
|
-
propertyName: node.property.name
|
|
48197
|
-
};
|
|
48198
|
-
}
|
|
48199
|
-
if (isNodeOfType(node, "JSXMemberExpression")) {
|
|
48200
|
-
if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
|
|
48201
|
-
return {
|
|
48202
|
-
object: node.object,
|
|
48203
|
-
propertyName: node.property.name
|
|
48204
|
-
};
|
|
48205
|
-
}
|
|
48206
|
-
return null;
|
|
48207
|
-
};
|
|
48208
|
-
const isNamespaceMemberFromModule = (node, moduleName, memberNames, scopes) => {
|
|
48209
|
-
const memberParts = getStaticMemberParts(node);
|
|
48210
|
-
if (!memberParts || !memberNames.has(memberParts.propertyName)) return false;
|
|
48211
|
-
const namespaceSymbol = resolveImportedIdentifier(memberParts.object, scopes);
|
|
48212
|
-
return Boolean(namespaceSymbol && isImportFromModule(namespaceSymbol, moduleName) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
|
|
48213
|
-
};
|
|
48214
|
-
const isReactMarkdownComponent = (node, scopes) => {
|
|
48215
|
-
if (isNodeOfType(node, "JSXIdentifier")) {
|
|
48216
|
-
const symbol = resolveImportedIdentifier(node, scopes);
|
|
48217
|
-
if (!symbol || !isImportFromModule(symbol, REACT_MARKDOWN_MODULE)) return false;
|
|
48218
|
-
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return true;
|
|
48219
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
48220
|
-
return importedName === "default" || Boolean(importedName && REACT_MARKDOWN_NAMED_EXPORTS.has(importedName));
|
|
48221
|
-
}
|
|
48222
|
-
return isNamespaceMemberFromModule(node, REACT_MARKDOWN_MODULE, REACT_MARKDOWN_NAMESPACE_EXPORTS, scopes);
|
|
48223
|
-
};
|
|
48224
|
-
const isPluginFromModule = (rawNode, moduleName, scopes, visitedSymbolIds) => {
|
|
48225
|
-
const node = stripParenExpression(rawNode);
|
|
48226
|
-
if (isNodeOfType(node, "Identifier")) {
|
|
48227
|
-
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
48228
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
48229
|
-
if (isDefaultImportSymbol(symbol, moduleName)) return true;
|
|
48230
|
-
if (symbol.kind !== "const" || !symbol.initializer) return false;
|
|
48231
|
-
visitedSymbolIds.add(symbol.id);
|
|
48232
|
-
return isPluginFromModule(symbol.initializer, moduleName, scopes, visitedSymbolIds);
|
|
48233
|
-
}
|
|
48234
|
-
if (isNamespaceMemberFromModule(node, moduleName, DEFAULT_EXPORT_NAMES, scopes)) return true;
|
|
48235
|
-
if (!isNodeOfType(node, "ArrayExpression")) return false;
|
|
48236
|
-
for (const element of node.elements) {
|
|
48237
|
-
if (!element || isNodeOfType(element, "SpreadElement")) continue;
|
|
48238
|
-
return isPluginFromModule(element, moduleName, scopes, visitedSymbolIds);
|
|
48239
|
-
}
|
|
48240
|
-
return false;
|
|
48241
|
-
};
|
|
48242
|
-
const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
|
|
48243
|
-
const node = stripParenExpression(rawNode);
|
|
48244
|
-
if (isNodeOfType(node, "Identifier")) {
|
|
48245
|
-
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
48246
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
48247
|
-
visitedSymbolIds.add(symbol.id);
|
|
48248
|
-
return collectPluginEntries(symbol.initializer, scopes, visitedSymbolIds);
|
|
48249
|
-
}
|
|
48250
|
-
if (!isNodeOfType(node, "ArrayExpression")) return null;
|
|
48251
|
-
const entries = [];
|
|
48252
|
-
for (const element of node.elements) {
|
|
48253
|
-
if (!element) continue;
|
|
48254
|
-
if (isNodeOfType(element, "SpreadElement")) {
|
|
48255
|
-
const spreadEntries = collectPluginEntries(element.argument, scopes, new Set(visitedSymbolIds));
|
|
48256
|
-
if (spreadEntries === null) return null;
|
|
48257
|
-
entries.push(...spreadEntries);
|
|
48258
|
-
continue;
|
|
48259
|
-
}
|
|
48260
|
-
entries.push(element);
|
|
48261
|
-
}
|
|
48262
|
-
return entries;
|
|
48263
|
-
};
|
|
48264
|
-
const findEffectiveExplicitAttribute = (attributes, attributeName) => {
|
|
48265
|
-
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
48266
|
-
const attribute = attributes[attributeIndex];
|
|
48267
|
-
if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
|
|
48268
|
-
if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
|
|
48269
|
-
}
|
|
48270
|
-
return null;
|
|
48271
|
-
};
|
|
48272
|
-
const getAttributeExpression = (attribute) => {
|
|
48273
|
-
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
48274
|
-
return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
|
|
48275
|
-
};
|
|
48276
|
-
const isDomPurifyNamespace = (node, scopes) => {
|
|
48277
|
-
const symbol = resolveImportedIdentifier(node, scopes);
|
|
48278
|
-
if (!symbol) return false;
|
|
48279
|
-
const importDeclaration = getImportDeclaration(symbol);
|
|
48280
|
-
if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
|
|
48281
|
-
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
48282
|
-
};
|
|
48283
|
-
const isDomPurifySanitizeCall = (node, scopes) => {
|
|
48284
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
48285
|
-
const memberParts = getStaticMemberParts(stripParenExpression(node.callee));
|
|
48286
|
-
return Boolean(memberParts && memberParts.propertyName === "sanitize" && isDomPurifyNamespace(memberParts.object, scopes));
|
|
48287
|
-
};
|
|
48288
|
-
const isStaticOrSanitizedMarkdownExpression = (rawNode, scopes, visitedSymbolIds) => {
|
|
48289
|
-
const node = stripParenExpression(rawNode);
|
|
48290
|
-
if (isNodeOfType(node, "Literal")) return true;
|
|
48291
|
-
if (isNodeOfType(node, "TemplateLiteral")) return node.expressions.every((expression) => isStaticOrSanitizedMarkdownExpression(expression, scopes, new Set(visitedSymbolIds)));
|
|
48292
|
-
if (isDomPurifySanitizeCall(node, scopes)) return true;
|
|
48293
|
-
if (isNodeOfType(node, "ConditionalExpression")) return isStaticOrSanitizedMarkdownExpression(node.consequent, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.alternate, scopes, new Set(visitedSymbolIds));
|
|
48294
|
-
if (isNodeOfType(node, "BinaryExpression") && node.operator === "+") return isStaticOrSanitizedMarkdownExpression(node.left, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.right, scopes, new Set(visitedSymbolIds));
|
|
48295
|
-
if (!isNodeOfType(node, "Identifier")) return false;
|
|
48296
|
-
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
48297
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
48298
|
-
visitedSymbolIds.add(symbol.id);
|
|
48299
|
-
return isStaticOrSanitizedMarkdownExpression(symbol.initializer, scopes, visitedSymbolIds);
|
|
48300
|
-
};
|
|
48301
|
-
const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
|
|
48302
|
-
const jsxElement = openingElement.parent;
|
|
48303
|
-
if (!isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
48304
|
-
const meaningfulChildren = jsxElement.children.filter((child) => !isNodeOfType(child, "JSXText") || child.value.trim().length > 0);
|
|
48305
|
-
if (meaningfulChildren.length > 0) return meaningfulChildren.some((child) => {
|
|
48306
|
-
if (isNodeOfType(child, "JSXText")) return false;
|
|
48307
|
-
if (!isNodeOfType(child, "JSXExpressionContainer")) return true;
|
|
48308
|
-
if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
|
|
48309
|
-
return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
|
|
48310
|
-
});
|
|
48311
|
-
const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
|
|
48312
|
-
if (!childrenAttribute) return false;
|
|
48313
|
-
const childrenExpression = getAttributeExpression(childrenAttribute);
|
|
48314
|
-
return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
|
|
48315
|
-
};
|
|
48316
|
-
const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
48317
|
-
id: "react-markdown-unsanitized-raw-html",
|
|
48318
|
-
title: "Unsanitized raw HTML in React Markdown",
|
|
48319
|
-
severity: "warn",
|
|
48320
|
-
recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
|
|
48321
|
-
create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
|
|
48322
|
-
if (!isReactMarkdownComponent(node.name, context.scopes)) return;
|
|
48323
|
-
const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
|
|
48324
|
-
if (!pluginsAttribute) return;
|
|
48325
|
-
const pluginsExpression = getAttributeExpression(pluginsAttribute);
|
|
48326
|
-
if (!pluginsExpression) return;
|
|
48327
|
-
const pluginEntries = collectPluginEntries(pluginsExpression, context.scopes, /* @__PURE__ */ new Set());
|
|
48328
|
-
if (pluginEntries === null) return;
|
|
48329
|
-
if (!pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_RAW_MODULE, context.scopes, /* @__PURE__ */ new Set()))) return;
|
|
48330
|
-
if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
|
|
48331
|
-
context.report({
|
|
48332
|
-
node,
|
|
48333
|
-
message: MESSAGE$6
|
|
48334
|
-
});
|
|
48335
|
-
} }))
|
|
48336
|
-
});
|
|
48337
|
-
//#endregion
|
|
48338
47225
|
//#region src/plugin/utils/collect-react-redux-selector-aliases.ts
|
|
48339
47226
|
const REACT_REDUX_MODULE = "react-redux";
|
|
48340
47227
|
const collectReactReduxSelectorAliases = (programRoot) => {
|
|
@@ -51231,7 +50118,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
|
|
|
51231
50118
|
};
|
|
51232
50119
|
const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
|
|
51233
50120
|
const isInsideStyleFactoryCallback = (node) => {
|
|
51234
|
-
const enclosingFunction = findEnclosingFunction
|
|
50121
|
+
const enclosingFunction = findEnclosingFunction(node);
|
|
51235
50122
|
if (!enclosingFunction) return false;
|
|
51236
50123
|
const callExpression = enclosingFunction.parent;
|
|
51237
50124
|
if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -56762,6 +55649,22 @@ const isInsideClassComponent = (node) => {
|
|
|
56762
55649
|
}
|
|
56763
55650
|
return false;
|
|
56764
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
|
+
};
|
|
56765
55668
|
const isInsideTry = (descendant, ancestor) => {
|
|
56766
55669
|
let current = descendant.parent;
|
|
56767
55670
|
while (current && current !== ancestor) {
|
|
@@ -56953,7 +55856,7 @@ const rulesOfHooks = defineRule({
|
|
|
56953
55856
|
});
|
|
56954
55857
|
return;
|
|
56955
55858
|
}
|
|
56956
|
-
if (
|
|
55859
|
+
if (hasShortCircuitAncestor(node, enclosing.node)) {
|
|
56957
55860
|
context.report({
|
|
56958
55861
|
node: node.callee,
|
|
56959
55862
|
message: buildConditionalMessage(hookName)
|
|
@@ -58087,14 +56990,12 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
|
|
|
58087
56990
|
}
|
|
58088
56991
|
return false;
|
|
58089
56992
|
};
|
|
58090
|
-
const declarationAwaitsGate = (declaration
|
|
56993
|
+
const declarationAwaitsGate = (declaration) => {
|
|
58091
56994
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
58092
56995
|
for (const declarator of declaration.declarations ?? []) {
|
|
58093
56996
|
if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
|
|
58094
56997
|
const argument = declarator.init.argument;
|
|
58095
56998
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
58096
|
-
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
58097
|
-
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
58098
56999
|
const calleeName = getCalleeName$2(argument);
|
|
58099
57000
|
if (!calleeName) continue;
|
|
58100
57001
|
if (isAuthGuardName(calleeName)) return true;
|
|
@@ -58122,7 +57023,7 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
58122
57023
|
if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
|
|
58123
57024
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
58124
57025
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
58125
|
-
if (declarationAwaitsGate(currentStatement
|
|
57026
|
+
if (declarationAwaitsGate(currentStatement)) continue;
|
|
58126
57027
|
context.report({
|
|
58127
57028
|
node: nextStatement,
|
|
58128
57029
|
message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
|
|
@@ -59108,7 +58009,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
59108
58009
|
const isReturnedFromCustomHook = (functionNode) => {
|
|
59109
58010
|
const parent = functionNode.parent;
|
|
59110
58011
|
if (isNodeOfType(parent, "ReturnStatement")) {
|
|
59111
|
-
const outerFunction = findEnclosingFunction
|
|
58012
|
+
const outerFunction = findEnclosingFunction(parent);
|
|
59112
58013
|
const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
|
|
59113
58014
|
return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
|
|
59114
58015
|
}
|
|
@@ -59125,13 +58026,13 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
59125
58026
|
return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
|
|
59126
58027
|
};
|
|
59127
58028
|
const isDeferredNavigateCall = (callNode) => {
|
|
59128
|
-
let enclosingFunction = findEnclosingFunction
|
|
58029
|
+
let enclosingFunction = findEnclosingFunction(callNode);
|
|
59129
58030
|
while (enclosingFunction) {
|
|
59130
58031
|
if (isDeferredCallbackPosition(enclosingFunction)) return true;
|
|
59131
58032
|
if (isWiredAsEventHandler(enclosingFunction)) return true;
|
|
59132
58033
|
if (isReturnedFromCustomHook(enclosingFunction)) return true;
|
|
59133
58034
|
if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
|
|
59134
|
-
enclosingFunction = findEnclosingFunction
|
|
58035
|
+
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
59135
58036
|
}
|
|
59136
58037
|
return false;
|
|
59137
58038
|
};
|
|
@@ -63570,17 +62471,6 @@ const reactDoctorRules = [
|
|
|
63570
62471
|
requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
|
|
63571
62472
|
}
|
|
63572
62473
|
},
|
|
63573
|
-
{
|
|
63574
|
-
key: "react-doctor/react-markdown-unsanitized-raw-html",
|
|
63575
|
-
id: "react-markdown-unsanitized-raw-html",
|
|
63576
|
-
source: "react-doctor",
|
|
63577
|
-
originallyExternal: false,
|
|
63578
|
-
rule: {
|
|
63579
|
-
...reactMarkdownUnsanitizedRawHtml,
|
|
63580
|
-
framework: "global",
|
|
63581
|
-
category: "Security"
|
|
63582
|
-
}
|
|
63583
|
-
},
|
|
63584
62474
|
{
|
|
63585
62475
|
key: "react-doctor/redux-useselector-inline-derivation",
|
|
63586
62476
|
id: "redux-useselector-inline-derivation",
|