oxlint-plugin-react-doctor 0.7.7-dev.71e6da6 → 0.7.7-dev.79abd71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +46 -0
- package/dist/index.js +1519 -686
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,13 @@ import { analyze } from "eslint-scope";
|
|
|
7
7
|
//#region src/plugin/utils/is-node-of-type.ts
|
|
8
8
|
const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
|
|
9
9
|
//#endregion
|
|
10
|
+
//#region src/plugin/utils/is-type-only-import.ts
|
|
11
|
+
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
12
|
+
if (!specifiers || specifiers.length === 0) return false;
|
|
13
|
+
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
14
|
+
};
|
|
15
|
+
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
16
|
+
//#endregion
|
|
10
17
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
11
18
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
12
19
|
"solid-js",
|
|
@@ -21,17 +28,33 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
|
21
28
|
"vidode"
|
|
22
29
|
]);
|
|
23
30
|
const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
|
|
31
|
+
const REACT_JSX_DIALECT_PACKAGE_PREFIXES = [
|
|
32
|
+
"react",
|
|
33
|
+
"react-dom",
|
|
34
|
+
"preact"
|
|
35
|
+
];
|
|
24
36
|
const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
|
|
25
|
-
const
|
|
37
|
+
const collectJsxRuntimeImports = (program) => {
|
|
38
|
+
let hasNonReactRuntime = false;
|
|
39
|
+
let hasReactRuntime = false;
|
|
26
40
|
for (const statement of program.body) {
|
|
27
41
|
if (!isNodeOfType(statement, "ImportDeclaration")) continue;
|
|
28
|
-
const
|
|
42
|
+
const importDeclaration = statement;
|
|
43
|
+
if (isTypeOnlyImport(importDeclaration)) continue;
|
|
44
|
+
const source = importDeclaration.source;
|
|
29
45
|
const value = source && typeof source.value === "string" ? source.value : null;
|
|
30
46
|
if (!value) continue;
|
|
31
|
-
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value))
|
|
32
|
-
if (startsWithAny(value,
|
|
47
|
+
if (NON_REACT_JSX_DIALECT_PACKAGES.has(value) || startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasNonReactRuntime = true;
|
|
48
|
+
if (startsWithAny(value, REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasReactRuntime = true;
|
|
33
49
|
}
|
|
34
|
-
return
|
|
50
|
+
return {
|
|
51
|
+
hasNonReactRuntime,
|
|
52
|
+
hasReactRuntime
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const fileImportsNonReactJsxDialect = (program) => {
|
|
56
|
+
const runtimeImports = collectJsxRuntimeImports(program);
|
|
57
|
+
return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
35
58
|
};
|
|
36
59
|
const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
|
|
37
60
|
for (const attribute of openingNode.attributes) {
|
|
@@ -244,6 +267,7 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
|
|
|
244
267
|
const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
245
268
|
const innerVisitors = create(context);
|
|
246
269
|
let fileIsNonReactJsx = false;
|
|
270
|
+
let fileImportsReactRuntime = false;
|
|
247
271
|
const wrappedVisitors = {};
|
|
248
272
|
for (const [key, visitor] of Object.entries(innerVisitors)) {
|
|
249
273
|
if (typeof visitor !== "function") {
|
|
@@ -256,14 +280,16 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
256
280
|
}
|
|
257
281
|
if (key === "Program") {
|
|
258
282
|
wrappedVisitors.Program = (node) => {
|
|
259
|
-
|
|
283
|
+
const runtimeImports = collectJsxRuntimeImports(node);
|
|
284
|
+
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
285
|
+
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
260
286
|
visitor(node);
|
|
261
287
|
};
|
|
262
288
|
continue;
|
|
263
289
|
}
|
|
264
290
|
if (key === "JSXOpeningElement") {
|
|
265
291
|
wrappedVisitors.JSXOpeningElement = (node) => {
|
|
266
|
-
if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
292
|
+
if (!fileImportsReactRuntime && !fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
|
|
267
293
|
if (fileIsNonReactJsx) return;
|
|
268
294
|
visitor(node);
|
|
269
295
|
};
|
|
@@ -275,7 +301,9 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
|
|
|
275
301
|
};
|
|
276
302
|
}
|
|
277
303
|
if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
|
|
278
|
-
|
|
304
|
+
const runtimeImports = collectJsxRuntimeImports(node);
|
|
305
|
+
fileImportsReactRuntime = runtimeImports.hasReactRuntime;
|
|
306
|
+
fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
|
|
279
307
|
};
|
|
280
308
|
return wrappedVisitors;
|
|
281
309
|
});
|
|
@@ -2312,7 +2340,7 @@ const anchorAmbiguousText = defineRule({
|
|
|
2312
2340
|
});
|
|
2313
2341
|
//#endregion
|
|
2314
2342
|
//#region src/plugin/rules/a11y/anchor-has-content.ts
|
|
2315
|
-
const MESSAGE$
|
|
2343
|
+
const MESSAGE$62 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
2316
2344
|
const isTransComponentsTemplate = (node) => {
|
|
2317
2345
|
let current = node.parent;
|
|
2318
2346
|
while (current) {
|
|
@@ -2348,7 +2376,7 @@ const anchorHasContent = defineRule({
|
|
|
2348
2376
|
if (isTransComponentsTemplate(node)) return;
|
|
2349
2377
|
context.report({
|
|
2350
2378
|
node: opening.name,
|
|
2351
|
-
message: MESSAGE$
|
|
2379
|
+
message: MESSAGE$62
|
|
2352
2380
|
});
|
|
2353
2381
|
} };
|
|
2354
2382
|
}
|
|
@@ -2780,7 +2808,7 @@ const parseJsxValue = (value) => {
|
|
|
2780
2808
|
};
|
|
2781
2809
|
//#endregion
|
|
2782
2810
|
//#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
|
|
2783
|
-
const MESSAGE$
|
|
2811
|
+
const MESSAGE$61 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
|
|
2784
2812
|
const mayBeContentEditable = (node) => {
|
|
2785
2813
|
const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
|
|
2786
2814
|
if (!attribute) return false;
|
|
@@ -2811,7 +2839,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2811
2839
|
if (tabIndexValue === null || tabIndexValue >= -1) return;
|
|
2812
2840
|
context.report({
|
|
2813
2841
|
node: node.name,
|
|
2814
|
-
message: MESSAGE$
|
|
2842
|
+
message: MESSAGE$61
|
|
2815
2843
|
});
|
|
2816
2844
|
return;
|
|
2817
2845
|
}
|
|
@@ -2819,7 +2847,7 @@ const ariaActivedescendantHasTabindex = defineRule({
|
|
|
2819
2847
|
if (mayBeContentEditable(node)) return;
|
|
2820
2848
|
context.report({
|
|
2821
2849
|
node: node.name,
|
|
2822
|
-
message: MESSAGE$
|
|
2850
|
+
message: MESSAGE$61
|
|
2823
2851
|
});
|
|
2824
2852
|
} })
|
|
2825
2853
|
});
|
|
@@ -4314,6 +4342,397 @@ const findTransparentExpressionRoot = (node) => {
|
|
|
4314
4342
|
return current;
|
|
4315
4343
|
};
|
|
4316
4344
|
//#endregion
|
|
4345
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
4346
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
4347
|
+
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
|
|
4348
|
+
const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
|
|
4349
|
+
if (node.computed) {
|
|
4350
|
+
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
4351
|
+
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
4352
|
+
return null;
|
|
4353
|
+
}
|
|
4354
|
+
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
4355
|
+
if (isNodeOfType(key, "Literal")) {
|
|
4356
|
+
if (typeof key.value === "string") return key.value;
|
|
4357
|
+
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
4358
|
+
}
|
|
4359
|
+
return null;
|
|
4360
|
+
};
|
|
4361
|
+
//#endregion
|
|
4362
|
+
//#region src/plugin/utils/get-destructured-binding-property-name.ts
|
|
4363
|
+
const getDestructuredBindingPropertyName = (bindingIdentifier) => {
|
|
4364
|
+
let bindingNode = bindingIdentifier;
|
|
4365
|
+
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
4366
|
+
const property = bindingNode.parent;
|
|
4367
|
+
if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
4368
|
+
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
4369
|
+
};
|
|
4370
|
+
//#endregion
|
|
4371
|
+
//#region src/plugin/utils/get-static-property-name.ts
|
|
4372
|
+
const getStaticPropertyName = (memberExpression) => {
|
|
4373
|
+
const property = memberExpression.property;
|
|
4374
|
+
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
4375
|
+
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
4376
|
+
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
4377
|
+
return null;
|
|
4378
|
+
};
|
|
4379
|
+
//#endregion
|
|
4380
|
+
//#region src/plugin/utils/has-static-property-write-before.ts
|
|
4381
|
+
const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
4382
|
+
const potentiallyAliasedSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
4383
|
+
const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
|
|
4384
|
+
"CatchClause",
|
|
4385
|
+
"ConditionalExpression",
|
|
4386
|
+
"DoWhileStatement",
|
|
4387
|
+
"ForInStatement",
|
|
4388
|
+
"ForOfStatement",
|
|
4389
|
+
"ForStatement",
|
|
4390
|
+
"IfStatement",
|
|
4391
|
+
"LogicalExpression",
|
|
4392
|
+
"SwitchCase",
|
|
4393
|
+
"SwitchStatement",
|
|
4394
|
+
"TryStatement",
|
|
4395
|
+
"WhileStatement"
|
|
4396
|
+
]);
|
|
4397
|
+
const getResolvedStaticPropertyName = (memberExpression, scopes) => {
|
|
4398
|
+
if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
|
|
4399
|
+
const directPropertyName = getStaticPropertyName(memberExpression);
|
|
4400
|
+
if (directPropertyName || !memberExpression.computed) return directPropertyName;
|
|
4401
|
+
const property = stripParenExpression(memberExpression.property);
|
|
4402
|
+
if (!isNodeOfType(property, "Identifier")) return null;
|
|
4403
|
+
const propertySymbol = resolveConstIdentifierAlias(property, scopes);
|
|
4404
|
+
const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
|
|
4405
|
+
return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
|
|
4406
|
+
};
|
|
4407
|
+
const collectScopeSymbols = (scope, symbols) => {
|
|
4408
|
+
symbols.push(...scope.symbols);
|
|
4409
|
+
for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
|
|
4410
|
+
};
|
|
4411
|
+
const getEquivalentSymbols = (identifier, scopes) => {
|
|
4412
|
+
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
4413
|
+
if (!rootSymbol) return [];
|
|
4414
|
+
let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
|
|
4415
|
+
if (!symbolsByRootId) {
|
|
4416
|
+
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
4417
|
+
equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
4418
|
+
}
|
|
4419
|
+
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
4420
|
+
if (cachedSymbols) return cachedSymbols;
|
|
4421
|
+
const allSymbols = [];
|
|
4422
|
+
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
4423
|
+
const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
|
|
4424
|
+
symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
|
|
4425
|
+
return equivalentSymbols;
|
|
4426
|
+
};
|
|
4427
|
+
const getDirectAliasSourceSymbol = (expression, scopes) => {
|
|
4428
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
4429
|
+
return isNodeOfType(unwrappedExpression, "Identifier") ? scopes.symbolFor(unwrappedExpression) : null;
|
|
4430
|
+
};
|
|
4431
|
+
const getDirectAssignmentSourceSymbol = (identifier, scopes) => {
|
|
4432
|
+
const assignmentTarget = findTransparentExpressionRoot(identifier);
|
|
4433
|
+
const parent = assignmentTarget.parent;
|
|
4434
|
+
if (!parent || !isNodeOfType(parent, "AssignmentExpression") || parent.operator !== "=" || parent.left !== assignmentTarget) return null;
|
|
4435
|
+
return getDirectAliasSourceSymbol(parent.right, scopes);
|
|
4436
|
+
};
|
|
4437
|
+
const isDirectAliasSourceReference = (identifier) => {
|
|
4438
|
+
const aliasSource = findTransparentExpressionRoot(identifier);
|
|
4439
|
+
const parent = aliasSource.parent;
|
|
4440
|
+
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === aliasSource && (isNodeOfType(parent.id, "Identifier") || isNodeOfType(parent.id, "ObjectPattern"))) return true;
|
|
4441
|
+
return Boolean(parent && isNodeOfType(parent, "AssignmentExpression") && parent.operator === "=" && parent.right === aliasSource && isNodeOfType(stripParenExpression(parent.left), "Identifier"));
|
|
4442
|
+
};
|
|
4443
|
+
const isDirectAliasOfKnownSymbol = (symbol, knownSymbolIds, scopes) => {
|
|
4444
|
+
if (symbol.initializer && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && symbol.declarationNode.id === symbol.bindingIdentifier) {
|
|
4445
|
+
const initializerSymbol = getDirectAliasSourceSymbol(symbol.initializer, scopes);
|
|
4446
|
+
if (initializerSymbol && knownSymbolIds.has(initializerSymbol.id)) return true;
|
|
4447
|
+
}
|
|
4448
|
+
return symbol.references.some((reference) => {
|
|
4449
|
+
const assignmentSourceSymbol = getDirectAssignmentSourceSymbol(reference.identifier, scopes);
|
|
4450
|
+
return Boolean(assignmentSourceSymbol && knownSymbolIds.has(assignmentSourceSymbol.id));
|
|
4451
|
+
});
|
|
4452
|
+
};
|
|
4453
|
+
const getPotentiallyAliasedSymbols = (identifier, scopes) => {
|
|
4454
|
+
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
4455
|
+
if (!rootSymbol) return [];
|
|
4456
|
+
let symbolsByRootId = potentiallyAliasedSymbolsByAnalysis.get(scopes);
|
|
4457
|
+
if (!symbolsByRootId) {
|
|
4458
|
+
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
4459
|
+
potentiallyAliasedSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
4460
|
+
}
|
|
4461
|
+
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
4462
|
+
if (cachedSymbols) return cachedSymbols;
|
|
4463
|
+
const allSymbols = [];
|
|
4464
|
+
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
4465
|
+
const aliasedSymbolIds = new Set([rootSymbol.id]);
|
|
4466
|
+
let didAddAlias = true;
|
|
4467
|
+
while (didAddAlias) {
|
|
4468
|
+
didAddAlias = false;
|
|
4469
|
+
for (const symbol of allSymbols) {
|
|
4470
|
+
if (aliasedSymbolIds.has(symbol.id)) continue;
|
|
4471
|
+
if (!isDirectAliasOfKnownSymbol(symbol, aliasedSymbolIds, scopes)) continue;
|
|
4472
|
+
aliasedSymbolIds.add(symbol.id);
|
|
4473
|
+
didAddAlias = true;
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
const aliasedSymbols = allSymbols.filter((symbol) => aliasedSymbolIds.has(symbol.id));
|
|
4477
|
+
symbolsByRootId.set(rootSymbol.id, aliasedSymbols);
|
|
4478
|
+
return aliasedSymbols;
|
|
4479
|
+
};
|
|
4480
|
+
const findExecutionBoundary = (node) => {
|
|
4481
|
+
let current = node;
|
|
4482
|
+
while (current) {
|
|
4483
|
+
if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
|
|
4484
|
+
current = current.parent ?? null;
|
|
4485
|
+
}
|
|
4486
|
+
return null;
|
|
4487
|
+
};
|
|
4488
|
+
const isOnUnconditionalPath = (node, boundary) => {
|
|
4489
|
+
let current = node.parent ?? null;
|
|
4490
|
+
while (current && current !== boundary) {
|
|
4491
|
+
if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
|
|
4492
|
+
current = current.parent ?? null;
|
|
4493
|
+
}
|
|
4494
|
+
return current === boundary;
|
|
4495
|
+
};
|
|
4496
|
+
const findFunctionBindingIdentifier = (functionNode) => {
|
|
4497
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
|
|
4498
|
+
const parent = functionNode.parent;
|
|
4499
|
+
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
4500
|
+
return null;
|
|
4501
|
+
};
|
|
4502
|
+
const findDirectCall = (identifier) => {
|
|
4503
|
+
let callee = identifier;
|
|
4504
|
+
let parent = callee.parent;
|
|
4505
|
+
while (parent && stripParenExpression(parent) === identifier) {
|
|
4506
|
+
callee = parent;
|
|
4507
|
+
parent = callee.parent;
|
|
4508
|
+
}
|
|
4509
|
+
return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
4510
|
+
};
|
|
4511
|
+
const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
4512
|
+
if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
|
|
4513
|
+
visitedFunctionNodes.add(functionNode);
|
|
4514
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
4515
|
+
if (!referenceBoundary) return false;
|
|
4516
|
+
const invocationCalls = [];
|
|
4517
|
+
const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
|
|
4518
|
+
if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
|
|
4519
|
+
const call = findDirectCall(reference.identifier);
|
|
4520
|
+
if (call) invocationCalls.push(call);
|
|
4521
|
+
}
|
|
4522
|
+
else {
|
|
4523
|
+
const call = findDirectCall(functionNode);
|
|
4524
|
+
if (call) invocationCalls.push(call);
|
|
4525
|
+
}
|
|
4526
|
+
return invocationCalls.some((call) => {
|
|
4527
|
+
if (call.range[0] >= referenceNode.range[0]) return false;
|
|
4528
|
+
const callBoundary = findExecutionBoundary(call);
|
|
4529
|
+
if (!callBoundary) return false;
|
|
4530
|
+
if (callBoundary === referenceBoundary) return true;
|
|
4531
|
+
if (!isFunctionLike$2(callBoundary)) return false;
|
|
4532
|
+
return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
|
|
4533
|
+
});
|
|
4534
|
+
};
|
|
4535
|
+
const isMemberWriteTarget = (memberExpression) => {
|
|
4536
|
+
const parent = memberExpression.parent;
|
|
4537
|
+
if (!parent) return false;
|
|
4538
|
+
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
|
|
4539
|
+
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
|
|
4540
|
+
return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
|
|
4541
|
+
};
|
|
4542
|
+
const getMemberWriteTarget = (identifier) => {
|
|
4543
|
+
let parent = identifier.parent;
|
|
4544
|
+
while (parent && stripParenExpression(parent) === identifier) parent = parent.parent;
|
|
4545
|
+
if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== identifier || !isMemberWriteTarget(parent)) return null;
|
|
4546
|
+
return parent;
|
|
4547
|
+
};
|
|
4548
|
+
const getStaticPropertyWriteTarget = (identifier, propertyName, scopes) => {
|
|
4549
|
+
const writeTarget = getMemberWriteTarget(identifier);
|
|
4550
|
+
return writeTarget && getResolvedStaticPropertyName(writeTarget, scopes) === propertyName ? writeTarget : null;
|
|
4551
|
+
};
|
|
4552
|
+
const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
4553
|
+
const writeTarget = getStaticPropertyWriteTarget(reference.identifier, propertyName, scopes);
|
|
4554
|
+
if (!writeTarget) return false;
|
|
4555
|
+
const writeBoundary = findExecutionBoundary(writeTarget);
|
|
4556
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
4557
|
+
if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(writeTarget, writeBoundary)) return false;
|
|
4558
|
+
if (writeBoundary === referenceBoundary) return writeTarget.range[0] < referenceNode.range[0];
|
|
4559
|
+
return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
|
|
4560
|
+
});
|
|
4561
|
+
const isStableStaticPropertyReference = (identifier) => {
|
|
4562
|
+
if (isDirectAliasSourceReference(identifier)) return true;
|
|
4563
|
+
const identifierRoot = findTransparentExpressionRoot(identifier);
|
|
4564
|
+
const memberExpression = identifierRoot.parent;
|
|
4565
|
+
return Boolean(memberExpression && isNodeOfType(memberExpression, "MemberExpression") && stripParenExpression(memberExpression.object) === identifierRoot);
|
|
4566
|
+
};
|
|
4567
|
+
const hasPossibleStaticPropertyWrite = (identifier, propertyName, scopes) => {
|
|
4568
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4569
|
+
return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => {
|
|
4570
|
+
const writeTarget = getMemberWriteTarget(reference.identifier);
|
|
4571
|
+
if (!writeTarget) return false;
|
|
4572
|
+
const writtenPropertyName = getResolvedStaticPropertyName(writeTarget, scopes);
|
|
4573
|
+
return writtenPropertyName === null || writtenPropertyName === propertyName;
|
|
4574
|
+
}));
|
|
4575
|
+
};
|
|
4576
|
+
const hasPossibleStaticPropertyMutationOrEscape = (identifier, propertyName, scopes) => {
|
|
4577
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4578
|
+
if (hasPossibleStaticPropertyWrite(identifier, propertyName, scopes)) return true;
|
|
4579
|
+
return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => !isStableStaticPropertyReference(reference.identifier)));
|
|
4580
|
+
};
|
|
4581
|
+
const hasPossibleStaticMemberCallWrite = (callExpression, scopes) => {
|
|
4582
|
+
const unwrappedCallExpression = stripParenExpression(callExpression);
|
|
4583
|
+
if (!isNodeOfType(unwrappedCallExpression, "CallExpression")) return false;
|
|
4584
|
+
const callee = stripParenExpression(unwrappedCallExpression.callee);
|
|
4585
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
4586
|
+
const propertyName = getStaticPropertyName(callee);
|
|
4587
|
+
if (propertyName === null) return false;
|
|
4588
|
+
const receiver = stripParenExpression(callee.object);
|
|
4589
|
+
return isNodeOfType(receiver, "Identifier") && hasPossibleStaticPropertyWrite(receiver, propertyName, scopes);
|
|
4590
|
+
};
|
|
4591
|
+
const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
4592
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4593
|
+
return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
|
|
4594
|
+
};
|
|
4595
|
+
//#endregion
|
|
4596
|
+
//#region src/plugin/utils/find-enclosing-function.ts
|
|
4597
|
+
const findEnclosingFunction$1 = (node) => {
|
|
4598
|
+
let cursor = node.parent;
|
|
4599
|
+
while (cursor) {
|
|
4600
|
+
if (isFunctionLike$2(cursor)) return cursor;
|
|
4601
|
+
cursor = cursor.parent ?? null;
|
|
4602
|
+
}
|
|
4603
|
+
return null;
|
|
4604
|
+
};
|
|
4605
|
+
//#endregion
|
|
4606
|
+
//#region src/plugin/utils/has-symbol-write-before.ts
|
|
4607
|
+
const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
4608
|
+
if (reference.flag === "read") return false;
|
|
4609
|
+
const writeFunction = findEnclosingFunction$1(reference.identifier);
|
|
4610
|
+
if (writeFunction === findEnclosingFunction$1(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
|
|
4611
|
+
if (!writeFunction) return true;
|
|
4612
|
+
return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
|
|
4613
|
+
});
|
|
4614
|
+
//#endregion
|
|
4615
|
+
//#region src/plugin/utils/get-order-independent-local-function.ts
|
|
4616
|
+
const COMMUTATIVE_COMPOUND_ASSIGNMENT_OPERATORS = new Set([
|
|
4617
|
+
"+=",
|
|
4618
|
+
"-=",
|
|
4619
|
+
"*=",
|
|
4620
|
+
"/=",
|
|
4621
|
+
"%=",
|
|
4622
|
+
"&=",
|
|
4623
|
+
"^=",
|
|
4624
|
+
"|="
|
|
4625
|
+
]);
|
|
4626
|
+
const isPureParameterExpression = (expression, parameterNames) => {
|
|
4627
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
4628
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) return true;
|
|
4629
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) return parameterNames.has(unwrappedExpression.name);
|
|
4630
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression") || isNodeOfType(unwrappedExpression, "LogicalExpression")) return isPureParameterExpression(unwrappedExpression.left, parameterNames) && isPureParameterExpression(unwrappedExpression.right, parameterNames);
|
|
4631
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression")) return unwrappedExpression.operator !== "delete" && isPureParameterExpression(unwrappedExpression.argument, parameterNames);
|
|
4632
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isPureParameterExpression(unwrappedExpression.test, parameterNames) && isPureParameterExpression(unwrappedExpression.consequent, parameterNames) && isPureParameterExpression(unwrappedExpression.alternate, parameterNames);
|
|
4633
|
+
if (isNodeOfType(unwrappedExpression, "TemplateLiteral")) return unwrappedExpression.expressions.every((nestedExpression) => isPureParameterExpression(nestedExpression, parameterNames));
|
|
4634
|
+
return false;
|
|
4635
|
+
};
|
|
4636
|
+
const isOrderIndependentPromiseResolveCall = (expression, parameterNames, scopes) => {
|
|
4637
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
4638
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
|
|
4639
|
+
if (!unwrappedExpression.arguments.every((argument) => !isNodeOfType(argument, "SpreadElement") && isPureParameterExpression(argument, parameterNames))) return false;
|
|
4640
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
4641
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
4642
|
+
if (getStaticPropertyName(callee) !== "resolve") return false;
|
|
4643
|
+
const receiver = stripParenExpression(callee.object);
|
|
4644
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && scopes.isGlobalReference(receiver);
|
|
4645
|
+
};
|
|
4646
|
+
const isHarmlessPromiseResolveAwait = (statement, parameterNames, scopes) => {
|
|
4647
|
+
if (!isNodeOfType(statement, "ExpressionStatement") || !isNodeOfType(statement.expression, "AwaitExpression")) return false;
|
|
4648
|
+
return isOrderIndependentPromiseResolveCall(statement.expression.argument, parameterNames, scopes);
|
|
4649
|
+
};
|
|
4650
|
+
const isCommutativeParameterMutation = (statement, parameterNames) => {
|
|
4651
|
+
if (!isNodeOfType(statement, "ExpressionStatement") || !isNodeOfType(statement.expression, "AssignmentExpression") || !COMMUTATIVE_COMPOUND_ASSIGNMENT_OPERATORS.has(statement.expression.operator)) return false;
|
|
4652
|
+
const mutationTarget = stripParenExpression(statement.expression.left);
|
|
4653
|
+
if (!isNodeOfType(mutationTarget, "MemberExpression")) return false;
|
|
4654
|
+
if (getStaticPropertyName(mutationTarget) === null) return false;
|
|
4655
|
+
const receiver = stripParenExpression(mutationTarget.object);
|
|
4656
|
+
return isNodeOfType(receiver, "Identifier") && parameterNames.has(receiver.name) && isNodeOfType(stripParenExpression(statement.expression.right), "Literal");
|
|
4657
|
+
};
|
|
4658
|
+
const isOrderIndependentFunction = (functionNode, scopes) => {
|
|
4659
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
4660
|
+
const parameterNames = /* @__PURE__ */ new Set();
|
|
4661
|
+
for (const parameter of functionNode.params) {
|
|
4662
|
+
if (!isNodeOfType(parameter, "Identifier")) return false;
|
|
4663
|
+
parameterNames.add(parameter.name);
|
|
4664
|
+
}
|
|
4665
|
+
if (!functionNode.async) {
|
|
4666
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isOrderIndependentPromiseResolveCall(functionNode.body, parameterNames, scopes);
|
|
4667
|
+
if (functionNode.body.body.length !== 1) return false;
|
|
4668
|
+
const [returnStatement] = functionNode.body.body;
|
|
4669
|
+
return Boolean(isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument && isOrderIndependentPromiseResolveCall(returnStatement.argument, parameterNames, scopes));
|
|
4670
|
+
}
|
|
4671
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isPureParameterExpression(functionNode.body, parameterNames);
|
|
4672
|
+
const statements = functionNode.body.body;
|
|
4673
|
+
for (let statementIndex = 0; statementIndex < statements.length; statementIndex++) {
|
|
4674
|
+
const statement = statements[statementIndex];
|
|
4675
|
+
const isTerminalStatement = statementIndex === statements.length - 1;
|
|
4676
|
+
if (isHarmlessPromiseResolveAwait(statement, parameterNames, scopes)) continue;
|
|
4677
|
+
if (isNodeOfType(statement, "ExpressionStatement") && isPureParameterExpression(statement.expression, parameterNames)) continue;
|
|
4678
|
+
if (isCommutativeParameterMutation(statement, parameterNames)) return isTerminalStatement;
|
|
4679
|
+
if (!isNodeOfType(statement, "ReturnStatement") || !isTerminalStatement) return false;
|
|
4680
|
+
return !statement.argument || isPureParameterExpression(statement.argument, parameterNames) || isOrderIndependentPromiseResolveCall(statement.argument, parameterNames, scopes);
|
|
4681
|
+
}
|
|
4682
|
+
return true;
|
|
4683
|
+
};
|
|
4684
|
+
const getObjectPropertyName = (property) => {
|
|
4685
|
+
if (!isNodeOfType(property, "Property")) return null;
|
|
4686
|
+
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
4687
|
+
};
|
|
4688
|
+
const resolveOrderIndependentObjectPropertyFunction = (objectExpression, propertyName, callExpression, scopes, visitedSymbolIds) => {
|
|
4689
|
+
const unwrappedObject = stripParenExpression(objectExpression);
|
|
4690
|
+
if (isNodeOfType(unwrappedObject, "Identifier")) {
|
|
4691
|
+
const symbol = scopes.symbolFor(unwrappedObject);
|
|
4692
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, callExpression, scopes) || hasPossibleStaticPropertyMutationOrEscape(unwrappedObject, propertyName, scopes)) return null;
|
|
4693
|
+
visitedSymbolIds.add(symbol.id);
|
|
4694
|
+
return resolveOrderIndependentObjectPropertyFunction(symbol.initializer, propertyName, callExpression, scopes, visitedSymbolIds);
|
|
4695
|
+
}
|
|
4696
|
+
if (!isNodeOfType(unwrappedObject, "ObjectExpression")) return null;
|
|
4697
|
+
let matchingProperty = null;
|
|
4698
|
+
for (const property of unwrappedObject.properties) {
|
|
4699
|
+
if (!isNodeOfType(property, "Property")) return null;
|
|
4700
|
+
if (property.kind !== "init") return null;
|
|
4701
|
+
const candidatePropertyName = getObjectPropertyName(property);
|
|
4702
|
+
if (candidatePropertyName === null) return null;
|
|
4703
|
+
if (candidatePropertyName === propertyName) matchingProperty = property;
|
|
4704
|
+
}
|
|
4705
|
+
if (!matchingProperty || !isNodeOfType(matchingProperty, "Property")) return null;
|
|
4706
|
+
const propertyValue = stripParenExpression(matchingProperty.value);
|
|
4707
|
+
if (isFunctionLike$2(propertyValue)) return isOrderIndependentFunction(propertyValue, scopes) ? propertyValue : null;
|
|
4708
|
+
return resolveOrderIndependentLocalFunction(propertyValue, callExpression, scopes, visitedSymbolIds);
|
|
4709
|
+
};
|
|
4710
|
+
const resolveOrderIndependentLocalFunction = (callee, callExpression, scopes, visitedSymbolIds) => {
|
|
4711
|
+
const unwrappedCallee = stripParenExpression(callee);
|
|
4712
|
+
if (isNodeOfType(unwrappedCallee, "MemberExpression")) {
|
|
4713
|
+
const propertyName = getStaticPropertyName(unwrappedCallee);
|
|
4714
|
+
if (propertyName === null) return null;
|
|
4715
|
+
return resolveOrderIndependentObjectPropertyFunction(stripParenExpression(unwrappedCallee.object), propertyName, callExpression, scopes, visitedSymbolIds);
|
|
4716
|
+
}
|
|
4717
|
+
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
4718
|
+
const symbol = scopes.symbolFor(unwrappedCallee);
|
|
4719
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, callExpression, scopes)) return null;
|
|
4720
|
+
visitedSymbolIds.add(symbol.id);
|
|
4721
|
+
if (!symbol.initializer) return null;
|
|
4722
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
4723
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
4724
|
+
if (destructuredPropertyName !== null) return resolveOrderIndependentObjectPropertyFunction(initializer, destructuredPropertyName, callExpression, scopes, visitedSymbolIds);
|
|
4725
|
+
if (isFunctionLike$2(initializer)) return isOrderIndependentFunction(initializer, scopes) ? initializer : null;
|
|
4726
|
+
if (symbol.kind !== "const") return null;
|
|
4727
|
+
return resolveOrderIndependentLocalFunction(initializer, callExpression, scopes, visitedSymbolIds);
|
|
4728
|
+
};
|
|
4729
|
+
const getOrderIndependentLocalFunction = (callExpression, scopes) => {
|
|
4730
|
+
const unwrappedCall = stripParenExpression(callExpression);
|
|
4731
|
+
if (!isNodeOfType(unwrappedCall, "CallExpression")) return null;
|
|
4732
|
+
if (hasPossibleStaticMemberCallWrite(unwrappedCall, scopes)) return null;
|
|
4733
|
+
return resolveOrderIndependentLocalFunction(unwrappedCall.callee, unwrappedCall, scopes, /* @__PURE__ */ new Set());
|
|
4734
|
+
};
|
|
4735
|
+
//#endregion
|
|
4317
4736
|
//#region src/plugin/utils/is-inline-function-expression.ts
|
|
4318
4737
|
/**
|
|
4319
4738
|
* Type-guard for the two "inline function expression" ESTree forms:
|
|
@@ -4347,13 +4766,15 @@ const isIntentionalSequencingCallee = (callee) => {
|
|
|
4347
4766
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(callee.property.name);
|
|
4348
4767
|
return false;
|
|
4349
4768
|
};
|
|
4350
|
-
const isAwaitingSleepLikeCall = (awaitNode) => {
|
|
4769
|
+
const isAwaitingSleepLikeCall = (awaitNode, context) => {
|
|
4351
4770
|
if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
|
|
4352
4771
|
const argument = awaitNode.argument;
|
|
4353
4772
|
if (!argument) return false;
|
|
4354
4773
|
if (!isNodeOfType(argument, "CallExpression")) return false;
|
|
4774
|
+
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) return false;
|
|
4355
4775
|
return isIntentionalSequencingCallee(argument.callee);
|
|
4356
4776
|
};
|
|
4777
|
+
const isAwaitingPossiblyMutatedMemberCall = (awaitNode, context) => isNodeOfType(awaitNode, "AwaitExpression") && Boolean(awaitNode.argument) && hasPossibleStaticMemberCallWrite(awaitNode.argument, context.scopes);
|
|
4357
4778
|
const PROMISE_CONCURRENCY_METHODS = new Set([
|
|
4358
4779
|
"all",
|
|
4359
4780
|
"allSettled",
|
|
@@ -4396,7 +4817,7 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
|
|
|
4396
4817
|
});
|
|
4397
4818
|
return isWaitLike;
|
|
4398
4819
|
};
|
|
4399
|
-
const isIntentionallySequentialAwait = (awaitNode) => isAwaitingSleepLikeCall(awaitNode) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
|
|
4820
|
+
const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
|
|
4400
4821
|
const collectPatternIdentifiers = (pattern, target) => {
|
|
4401
4822
|
if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
|
|
4402
4823
|
else if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
@@ -4587,12 +5008,12 @@ const getLoopLabelName = (loopNode) => {
|
|
|
4587
5008
|
if (isNodeOfType(parent, "LabeledStatement") && isNodeOfType(parent.label, "Identifier")) return parent.label.name;
|
|
4588
5009
|
return null;
|
|
4589
5010
|
};
|
|
4590
|
-
const loopBodyHasIntentionallySequentialAwait = (block) => {
|
|
5011
|
+
const loopBodyHasIntentionallySequentialAwait = (block, context) => {
|
|
4591
5012
|
let foundIntentional = false;
|
|
4592
5013
|
walkAst(block, (child) => {
|
|
4593
5014
|
if (foundIntentional) return false;
|
|
4594
5015
|
if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
|
|
4595
|
-
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child)) {
|
|
5016
|
+
if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child, context)) {
|
|
4596
5017
|
foundIntentional = true;
|
|
4597
5018
|
return false;
|
|
4598
5019
|
}
|
|
@@ -4697,7 +5118,7 @@ const asyncAwaitInLoop = defineRule({
|
|
|
4697
5118
|
const inspectLoop = (loopNode, label) => {
|
|
4698
5119
|
const loopBody = loopNode.body;
|
|
4699
5120
|
if (!loopBody) return;
|
|
4700
|
-
if (loopBodyHasIntentionallySequentialAwait(loopBody)) return;
|
|
5121
|
+
if (loopBodyHasIntentionallySequentialAwait(loopBody, context)) return;
|
|
4701
5122
|
if ((isNodeOfType(loopNode, "WhileStatement") || isNodeOfType(loopNode, "DoWhileStatement")) && isLoopTestDependentOnBodyState(loopNode.test, loopBody)) return;
|
|
4702
5123
|
if (hasLoopCarriedDependency(loopBody)) return;
|
|
4703
5124
|
if (loopBodyHasAwaitDependentEarlyExit(loopBody, getLoopLabelName(loopNode))) return;
|
|
@@ -4957,7 +5378,7 @@ const guardConsequentPerformsSideEffects = (consequent) => {
|
|
|
4957
5378
|
});
|
|
4958
5379
|
return performsSideEffects;
|
|
4959
5380
|
};
|
|
4960
|
-
const findEnclosingFunction
|
|
5381
|
+
const findEnclosingFunction = (node) => {
|
|
4961
5382
|
let ancestor = node.parent;
|
|
4962
5383
|
while (ancestor) {
|
|
4963
5384
|
if (isFunctionLike$2(ancestor)) return ancestor;
|
|
@@ -4970,7 +5391,7 @@ const guardTestReadsReassignedLocal = (test, guardStatement) => {
|
|
|
4970
5391
|
const testIdentifierNames = /* @__PURE__ */ new Set();
|
|
4971
5392
|
collectReferenceIdentifierNames(test, testIdentifierNames);
|
|
4972
5393
|
if (testIdentifierNames.size === 0) return false;
|
|
4973
|
-
const enclosingFunction = findEnclosingFunction
|
|
5394
|
+
const enclosingFunction = findEnclosingFunction(guardStatement);
|
|
4974
5395
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction) || !enclosingFunction.body) return false;
|
|
4975
5396
|
let readsReassignedLocal = false;
|
|
4976
5397
|
walkAst(enclosingFunction.body, (child) => {
|
|
@@ -5141,19 +5562,36 @@ const isIntentionalSequencingAwait = (awaitedCall) => {
|
|
|
5141
5562
|
return getCalleeIdentifierTrail(awaitedCall).some((name) => INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(name));
|
|
5142
5563
|
};
|
|
5143
5564
|
const isBareExpressionAwait = (statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "AwaitExpression");
|
|
5565
|
+
const hasOrderIndependentBareAwaitArguments = (callExpression) => {
|
|
5566
|
+
const unwrappedCallExpression = stripParenExpression(callExpression);
|
|
5567
|
+
if (!isNodeOfType(unwrappedCallExpression, "CallExpression")) return false;
|
|
5568
|
+
return unwrappedCallExpression.arguments.every((argument) => {
|
|
5569
|
+
if (isNodeOfType(argument, "SpreadElement")) return false;
|
|
5570
|
+
const unwrappedArgument = stripParenExpression(argument);
|
|
5571
|
+
return isNodeOfType(unwrappedArgument, "Identifier") || isNodeOfType(unwrappedArgument, "Literal");
|
|
5572
|
+
});
|
|
5573
|
+
};
|
|
5144
5574
|
const isNonCallAwait = (statement) => {
|
|
5145
5575
|
const awaitedExpression = getAwaitedCall(statement);
|
|
5146
5576
|
if (!awaitedExpression) return false;
|
|
5147
5577
|
const stripped = stripParenExpression(awaitedExpression);
|
|
5148
5578
|
return !isNodeOfType(stripped, "CallExpression") && !isNodeOfType(stripped, "NewExpression") && !isNodeOfType(stripped, "ImportExpression");
|
|
5149
5579
|
};
|
|
5150
|
-
const sequenceContainsSerializationSignal = (statements) => {
|
|
5580
|
+
const sequenceContainsSerializationSignal = (statements, context) => {
|
|
5581
|
+
let bareAwaitFunction = null;
|
|
5151
5582
|
for (const statement of statements) {
|
|
5152
|
-
if (isBareExpressionAwait(statement)) return true;
|
|
5153
5583
|
if (isNonCallAwait(statement)) return true;
|
|
5154
5584
|
const awaitedCall = getAwaitedCall(statement);
|
|
5585
|
+
if (awaitedCall && hasPossibleStaticMemberCallWrite(awaitedCall, context.scopes)) return true;
|
|
5586
|
+
const orderIndependentFunction = awaitedCall ? getOrderIndependentLocalFunction(awaitedCall, context.scopes) : null;
|
|
5587
|
+
if (isBareExpressionAwait(statement)) {
|
|
5588
|
+
if (orderIndependentFunction === null) return true;
|
|
5589
|
+
if (!awaitedCall || !hasOrderIndependentBareAwaitArguments(awaitedCall)) return true;
|
|
5590
|
+
if (bareAwaitFunction !== null && bareAwaitFunction !== orderIndependentFunction) return true;
|
|
5591
|
+
bareAwaitFunction = orderIndependentFunction;
|
|
5592
|
+
}
|
|
5155
5593
|
if (isOrderedUiFlowAwait(awaitedCall)) return true;
|
|
5156
|
-
if (isIntentionalSequencingAwait(awaitedCall)) return true;
|
|
5594
|
+
if (isIntentionalSequencingAwait(awaitedCall) && orderIndependentFunction === null) return true;
|
|
5157
5595
|
}
|
|
5158
5596
|
return false;
|
|
5159
5597
|
};
|
|
@@ -5211,7 +5649,7 @@ const asyncParallel = defineRule({
|
|
|
5211
5649
|
const consecutiveAwaitStatements = [];
|
|
5212
5650
|
const flushConsecutiveAwaits = () => {
|
|
5213
5651
|
if (consecutiveAwaitStatements.length >= 3) {
|
|
5214
|
-
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements)) reportIfIndependent(consecutiveAwaitStatements, context);
|
|
5652
|
+
if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements, context)) reportIfIndependent(consecutiveAwaitStatements, context);
|
|
5215
5653
|
}
|
|
5216
5654
|
consecutiveAwaitStatements.length = 0;
|
|
5217
5655
|
};
|
|
@@ -5224,7 +5662,7 @@ const asyncParallel = defineRule({
|
|
|
5224
5662
|
});
|
|
5225
5663
|
//#endregion
|
|
5226
5664
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
5227
|
-
const MESSAGE$
|
|
5665
|
+
const MESSAGE$60 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
|
|
5228
5666
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
5229
5667
|
const STORAGE_GLOBALS = new Set([
|
|
5230
5668
|
"window",
|
|
@@ -5289,7 +5727,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5289
5727
|
if (keyString === null || !isAuthCredentialKey(keyString)) return;
|
|
5290
5728
|
context.report({
|
|
5291
5729
|
node,
|
|
5292
|
-
message: MESSAGE$
|
|
5730
|
+
message: MESSAGE$60
|
|
5293
5731
|
});
|
|
5294
5732
|
},
|
|
5295
5733
|
AssignmentExpression(node) {
|
|
@@ -5300,7 +5738,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5300
5738
|
if (!propertyName || !isAuthCredentialKey(propertyName)) return;
|
|
5301
5739
|
context.report({
|
|
5302
5740
|
node: target,
|
|
5303
|
-
message: MESSAGE$
|
|
5741
|
+
message: MESSAGE$60
|
|
5304
5742
|
});
|
|
5305
5743
|
}
|
|
5306
5744
|
}))
|
|
@@ -5439,7 +5877,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
|
|
|
5439
5877
|
const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
|
|
5440
5878
|
const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
|
|
5441
5879
|
const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
|
|
5442
|
-
const MESSAGE$
|
|
5880
|
+
const MESSAGE$59 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
|
|
5443
5881
|
const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
|
|
5444
5882
|
const scanWorkflowContent = (content) => {
|
|
5445
5883
|
const lines = content.split("\n");
|
|
@@ -5484,7 +5922,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5484
5922
|
const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
|
|
5485
5923
|
const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
|
|
5486
5924
|
return [{
|
|
5487
|
-
message: MESSAGE$
|
|
5925
|
+
message: MESSAGE$59,
|
|
5488
5926
|
line: step.startLineIndex + installLineOffset + 1,
|
|
5489
5927
|
column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
|
|
5490
5928
|
}];
|
|
@@ -5494,7 +5932,7 @@ const scanWorkflowContent = (content) => {
|
|
|
5494
5932
|
const scanNonWorkflowConfig = scanByPattern({
|
|
5495
5933
|
shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
|
|
5496
5934
|
pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
|
|
5497
|
-
message: MESSAGE$
|
|
5935
|
+
message: MESSAGE$59
|
|
5498
5936
|
});
|
|
5499
5937
|
const scan = (file) => {
|
|
5500
5938
|
if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
|
|
@@ -5989,23 +6427,6 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5989
6427
|
}
|
|
5990
6428
|
});
|
|
5991
6429
|
//#endregion
|
|
5992
|
-
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5993
|
-
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5994
|
-
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
|
|
5995
|
-
const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
|
|
5996
|
-
if (node.computed) {
|
|
5997
|
-
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
5998
|
-
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
5999
|
-
return null;
|
|
6000
|
-
}
|
|
6001
|
-
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
6002
|
-
if (isNodeOfType(key, "Literal")) {
|
|
6003
|
-
if (typeof key.value === "string") return key.value;
|
|
6004
|
-
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
6005
|
-
}
|
|
6006
|
-
return null;
|
|
6007
|
-
};
|
|
6008
|
-
//#endregion
|
|
6009
6430
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
6010
6431
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
6011
6432
|
if (!a || !b) return a === b;
|
|
@@ -6263,7 +6684,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
6263
6684
|
};
|
|
6264
6685
|
//#endregion
|
|
6265
6686
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
6266
|
-
const MESSAGE$
|
|
6687
|
+
const MESSAGE$58 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
6267
6688
|
const KEY_HANDLERS = [
|
|
6268
6689
|
"onKeyUp",
|
|
6269
6690
|
"onKeyDown",
|
|
@@ -6474,7 +6895,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6474
6895
|
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
|
|
6475
6896
|
context.report({
|
|
6476
6897
|
node: node.name,
|
|
6477
|
-
message: MESSAGE$
|
|
6898
|
+
message: MESSAGE$58
|
|
6478
6899
|
});
|
|
6479
6900
|
} };
|
|
6480
6901
|
}
|
|
@@ -6689,15 +7110,6 @@ const getDirectConstInitializer = (symbol) => {
|
|
|
6689
7110
|
return symbol.initializer;
|
|
6690
7111
|
};
|
|
6691
7112
|
//#endregion
|
|
6692
|
-
//#region src/plugin/utils/get-static-property-name.ts
|
|
6693
|
-
const getStaticPropertyName = (memberExpression) => {
|
|
6694
|
-
const property = memberExpression.property;
|
|
6695
|
-
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
6696
|
-
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
6697
|
-
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
6698
|
-
return null;
|
|
6699
|
-
};
|
|
6700
|
-
//#endregion
|
|
6701
7113
|
//#region src/plugin/utils/get-range-start.ts
|
|
6702
7114
|
const getRangeStart = (node) => {
|
|
6703
7115
|
const rangeStart = node.range?.[0];
|
|
@@ -7093,16 +7505,6 @@ const collectFunctionReturnStatements = (functionNode) => {
|
|
|
7093
7505
|
return returnStatements;
|
|
7094
7506
|
};
|
|
7095
7507
|
//#endregion
|
|
7096
|
-
//#region src/plugin/utils/find-enclosing-function.ts
|
|
7097
|
-
const findEnclosingFunction = (node) => {
|
|
7098
|
-
let cursor = node.parent;
|
|
7099
|
-
while (cursor) {
|
|
7100
|
-
if (isFunctionLike$2(cursor)) return cursor;
|
|
7101
|
-
cursor = cursor.parent ?? null;
|
|
7102
|
-
}
|
|
7103
|
-
return null;
|
|
7104
|
-
};
|
|
7105
|
-
//#endregion
|
|
7106
7508
|
//#region src/plugin/utils/statement-always-exits.ts
|
|
7107
7509
|
const statementAlwaysExits = (statement) => {
|
|
7108
7510
|
if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
|
|
@@ -7148,8 +7550,8 @@ const applyDefinitions = (incomingDefinitions, definitions) => {
|
|
|
7148
7550
|
const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow) => {
|
|
7149
7551
|
if (!REASSIGNABLE_BINDING_KINDS.has(symbol.kind)) return symbol.initializer ? [symbol.initializer] : [];
|
|
7150
7552
|
if (!controlFlow) return [];
|
|
7151
|
-
const referenceFunction = findEnclosingFunction(referenceNode);
|
|
7152
|
-
if (findEnclosingFunction(symbol.bindingIdentifier) !== referenceFunction) return [];
|
|
7553
|
+
const referenceFunction = findEnclosingFunction$1(referenceNode);
|
|
7554
|
+
if (findEnclosingFunction$1(symbol.bindingIdentifier) !== referenceFunction) return [];
|
|
7153
7555
|
if (!referenceFunction) return [];
|
|
7154
7556
|
const functionControlFlow = controlFlow.cfgFor(referenceFunction);
|
|
7155
7557
|
if (!functionControlFlow) return [];
|
|
@@ -7173,7 +7575,7 @@ const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow)
|
|
|
7173
7575
|
if (symbol.initializer) addDefinition(symbol.initializer, symbol.bindingIdentifier, bindingPosition);
|
|
7174
7576
|
for (const reference of symbol.references) {
|
|
7175
7577
|
const writePosition = getRangeStart(reference.identifier);
|
|
7176
|
-
if (reference.flag === "read" || findEnclosingFunction(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
|
|
7578
|
+
if (reference.flag === "read" || findEnclosingFunction$1(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
|
|
7177
7579
|
const assignedExpression = getAssignedExpressionForWrite(reference.identifier);
|
|
7178
7580
|
if (!assignedExpression) continue;
|
|
7179
7581
|
addDefinition(assignedExpression, reference.identifier, writePosition);
|
|
@@ -8727,7 +9129,7 @@ const getClassNameLiteral = (classAttribute) => {
|
|
|
8727
9129
|
};
|
|
8728
9130
|
//#endregion
|
|
8729
9131
|
//#region src/plugin/rules/a11y/control-has-associated-label.ts
|
|
8730
|
-
const MESSAGE$
|
|
9132
|
+
const MESSAGE$57 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
|
|
8731
9133
|
const NON_OPERABLE_ELEMENTS = new Set([
|
|
8732
9134
|
"td",
|
|
8733
9135
|
"th",
|
|
@@ -8845,6 +9247,7 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
|
|
|
8845
9247
|
const LABEL_ELEMENT = "label";
|
|
8846
9248
|
const LABEL_COMPONENT_NAME = "Label";
|
|
8847
9249
|
const POLYMORPHIC_COMPONENT_PROP = "component";
|
|
9250
|
+
const TITLE_ATTRIBUTE = "title";
|
|
8848
9251
|
const WRAPPER_LABEL_PROP = "label";
|
|
8849
9252
|
const SELECT_ELEMENT = "select";
|
|
8850
9253
|
const DEFAULT_DEPTH = 5;
|
|
@@ -8890,6 +9293,96 @@ const hasNonEmptyPropValue = (attribute) => {
|
|
|
8890
9293
|
}
|
|
8891
9294
|
return true;
|
|
8892
9295
|
};
|
|
9296
|
+
const getLastJsxPropIgnoreCase = (attributes, targetProp) => {
|
|
9297
|
+
const targetPropLower = targetProp.toLowerCase();
|
|
9298
|
+
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
9299
|
+
const attribute = attributes[attributeIndex];
|
|
9300
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
9301
|
+
if (getJsxAttributeName(attribute.name)?.toLowerCase() === targetPropLower) return attribute;
|
|
9302
|
+
}
|
|
9303
|
+
};
|
|
9304
|
+
const getStaticNativeTitleArrayValue = (expression, scopes) => {
|
|
9305
|
+
const elementValues = [];
|
|
9306
|
+
for (const rawElement of expression.elements) {
|
|
9307
|
+
if (rawElement === null) {
|
|
9308
|
+
elementValues.push("");
|
|
9309
|
+
continue;
|
|
9310
|
+
}
|
|
9311
|
+
if (isNodeOfType(rawElement, "SpreadElement")) return null;
|
|
9312
|
+
const element = stripParenExpression(rawElement);
|
|
9313
|
+
if (isNodeOfType(element, "Literal")) {
|
|
9314
|
+
elementValues.push(element.value === null ? "" : String(element.value));
|
|
9315
|
+
continue;
|
|
9316
|
+
}
|
|
9317
|
+
if (isNodeOfType(element, "TemplateLiteral")) {
|
|
9318
|
+
const staticValue = getStaticTemplateLiteralValue(element);
|
|
9319
|
+
if (staticValue === null) return null;
|
|
9320
|
+
elementValues.push(staticValue);
|
|
9321
|
+
continue;
|
|
9322
|
+
}
|
|
9323
|
+
if (isNodeOfType(element, "Identifier") && element.name === "undefined" && scopes.isGlobalReference(element) || isNodeOfType(element, "UnaryExpression") && element.operator === "void") {
|
|
9324
|
+
elementValues.push("");
|
|
9325
|
+
continue;
|
|
9326
|
+
}
|
|
9327
|
+
if (isNodeOfType(element, "ArrayExpression")) {
|
|
9328
|
+
const nestedValue = getStaticNativeTitleArrayValue(element, scopes);
|
|
9329
|
+
if (nestedValue === null) return null;
|
|
9330
|
+
elementValues.push(nestedValue);
|
|
9331
|
+
continue;
|
|
9332
|
+
}
|
|
9333
|
+
return null;
|
|
9334
|
+
}
|
|
9335
|
+
return elementValues.join(",");
|
|
9336
|
+
};
|
|
9337
|
+
const isGlobalSymbolExpression = (expression, scopes) => {
|
|
9338
|
+
if (isNodeOfType(expression, "Identifier")) return expression.name === "Symbol" && scopes.isGlobalReference(expression);
|
|
9339
|
+
if (!isNodeOfType(expression, "MemberExpression")) return false;
|
|
9340
|
+
const object = stripParenExpression(expression.object);
|
|
9341
|
+
return isNodeOfType(object, "Identifier") && object.name === "Symbol" && scopes.isGlobalReference(object);
|
|
9342
|
+
};
|
|
9343
|
+
const hasNonEmptyNativeTitleExpression = (rawExpression, scopes) => {
|
|
9344
|
+
const expression = stripParenExpression(rawExpression);
|
|
9345
|
+
if (isNodeOfType(expression, "Literal")) {
|
|
9346
|
+
if (typeof expression.value === "string") return expression.value.trim().length > 0;
|
|
9347
|
+
return expression.value !== null && typeof expression.value !== "boolean";
|
|
9348
|
+
}
|
|
9349
|
+
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
9350
|
+
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
9351
|
+
return staticValue === null || staticValue.trim().length > 0;
|
|
9352
|
+
}
|
|
9353
|
+
if (isNodeOfType(expression, "Identifier")) return expression.name !== "undefined" || !scopes.isGlobalReference(expression);
|
|
9354
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return false;
|
|
9355
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") return false;
|
|
9356
|
+
if (isNodeOfType(expression, "ArrowFunctionExpression")) return false;
|
|
9357
|
+
if (isNodeOfType(expression, "FunctionExpression")) return false;
|
|
9358
|
+
if (isNodeOfType(expression, "ClassExpression")) return false;
|
|
9359
|
+
if (isNodeOfType(expression, "ArrayExpression")) {
|
|
9360
|
+
const staticValue = getStaticNativeTitleArrayValue(expression, scopes);
|
|
9361
|
+
return staticValue === null || staticValue.trim().length > 0;
|
|
9362
|
+
}
|
|
9363
|
+
if (isGlobalSymbolExpression(expression, scopes)) return false;
|
|
9364
|
+
if (isNodeOfType(expression, "CallExpression") && isGlobalSymbolExpression(expression.callee, scopes)) return false;
|
|
9365
|
+
if (isNodeOfType(expression, "ConditionalExpression")) return hasNonEmptyNativeTitleExpression(expression.consequent, scopes) && hasNonEmptyNativeTitleExpression(expression.alternate, scopes);
|
|
9366
|
+
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
9367
|
+
const finalExpression = expression.expressions.at(-1);
|
|
9368
|
+
return finalExpression ? hasNonEmptyNativeTitleExpression(finalExpression, scopes) : false;
|
|
9369
|
+
}
|
|
9370
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
9371
|
+
const leftExpression = stripParenExpression(expression.left);
|
|
9372
|
+
if (!isNodeOfType(leftExpression, "Literal")) return expression.operator !== "&&";
|
|
9373
|
+
if (expression.operator === "??") return hasNonEmptyNativeTitleExpression(leftExpression.value === null ? expression.right : leftExpression, scopes);
|
|
9374
|
+
const leftValueIsTruthy = Boolean(leftExpression.value);
|
|
9375
|
+
if (expression.operator === "&&") return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? expression.right : leftExpression, scopes);
|
|
9376
|
+
return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? leftExpression : expression.right, scopes);
|
|
9377
|
+
}
|
|
9378
|
+
return true;
|
|
9379
|
+
};
|
|
9380
|
+
const hasNonEmptyNativeTitle = (attribute, scopes) => {
|
|
9381
|
+
if (!attribute?.value) return false;
|
|
9382
|
+
if (isNodeOfType(attribute.value, "Literal")) return typeof attribute.value.value === "string" && attribute.value.value.trim().length > 0;
|
|
9383
|
+
if (isNodeOfType(attribute.value, "JSXExpressionContainer")) return hasNonEmptyNativeTitleExpression(attribute.value.expression, scopes);
|
|
9384
|
+
return true;
|
|
9385
|
+
};
|
|
8893
9386
|
const toAttributeMatchKey = (kind, value) => {
|
|
8894
9387
|
const trimmedValue = value.trim();
|
|
8895
9388
|
return trimmedValue.length > 0 ? `${kind}:${trimmedValue}` : null;
|
|
@@ -9146,6 +9639,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9146
9639
|
if (typeValue === "submit" || typeValue === "reset") return;
|
|
9147
9640
|
if (typeValue === "button" && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "value"))) return;
|
|
9148
9641
|
}
|
|
9642
|
+
if (isDomElement && hasNonEmptyNativeTitle(getLastJsxPropIgnoreCase(opening.attributes, TITLE_ATTRIBUTE), context.scopes)) return;
|
|
9149
9643
|
if (supportsPlaceholderNameFallback(tagName, opening) && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "placeholder"))) return;
|
|
9150
9644
|
if (hasLabellingProp(opening.attributes, settings.labelAttributes)) return;
|
|
9151
9645
|
if (isInsideJsxAttribute(node)) return;
|
|
@@ -9165,7 +9659,7 @@ const controlHasAssociatedLabel = defineRule({
|
|
|
9165
9659
|
if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
|
|
9166
9660
|
context.report({
|
|
9167
9661
|
node: candidate.opening,
|
|
9168
|
-
message: MESSAGE$
|
|
9662
|
+
message: MESSAGE$57
|
|
9169
9663
|
});
|
|
9170
9664
|
}
|
|
9171
9665
|
}
|
|
@@ -9926,7 +10420,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
9926
10420
|
});
|
|
9927
10421
|
//#endregion
|
|
9928
10422
|
//#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
|
|
9929
|
-
const MESSAGE$
|
|
10423
|
+
const MESSAGE$56 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
|
|
9930
10424
|
const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
|
|
9931
10425
|
const NAME_PROVIDING_ATTRIBUTES = [
|
|
9932
10426
|
"aria-label",
|
|
@@ -9951,7 +10445,7 @@ const dialogHasAccessibleName = defineRule({
|
|
|
9951
10445
|
if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
|
|
9952
10446
|
context.report({
|
|
9953
10447
|
node: node.name,
|
|
9954
|
-
message: MESSAGE$
|
|
10448
|
+
message: MESSAGE$56
|
|
9955
10449
|
});
|
|
9956
10450
|
} };
|
|
9957
10451
|
}
|
|
@@ -9991,7 +10485,7 @@ const isEs6Component = (node) => {
|
|
|
9991
10485
|
};
|
|
9992
10486
|
//#endregion
|
|
9993
10487
|
//#region src/plugin/rules/react-builtins/display-name.ts
|
|
9994
|
-
const MESSAGE$
|
|
10488
|
+
const MESSAGE$55 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
|
|
9995
10489
|
const DEFAULT_ADDITIONAL_HOCS = [
|
|
9996
10490
|
"observer",
|
|
9997
10491
|
"lazy",
|
|
@@ -10184,7 +10678,7 @@ const displayName = defineRule({
|
|
|
10184
10678
|
const reportAt = (node) => {
|
|
10185
10679
|
context.report({
|
|
10186
10680
|
node,
|
|
10187
|
-
message: MESSAGE$
|
|
10681
|
+
message: MESSAGE$55
|
|
10188
10682
|
});
|
|
10189
10683
|
};
|
|
10190
10684
|
return {
|
|
@@ -11095,7 +11589,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
|
|
|
11095
11589
|
//#endregion
|
|
11096
11590
|
//#region src/plugin/utils/enclosing-component-or-hook-name.ts
|
|
11097
11591
|
const enclosingComponentOrHookName = (node) => {
|
|
11098
|
-
const functionNode = findEnclosingFunction(node);
|
|
11592
|
+
const functionNode = findEnclosingFunction$1(node);
|
|
11099
11593
|
return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
|
|
11100
11594
|
};
|
|
11101
11595
|
//#endregion
|
|
@@ -11152,11 +11646,11 @@ const executesDuringRender = (functionNode, scopes) => {
|
|
|
11152
11646
|
//#endregion
|
|
11153
11647
|
//#region src/plugin/utils/find-render-phase-component-or-hook.ts
|
|
11154
11648
|
const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
11155
|
-
let functionNode = findEnclosingFunction(node);
|
|
11649
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
11156
11650
|
while (functionNode) {
|
|
11157
11651
|
if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
|
|
11158
11652
|
if (!executesDuringRender(functionNode, scopes)) return null;
|
|
11159
|
-
functionNode = findEnclosingFunction(functionNode);
|
|
11653
|
+
functionNode = findEnclosingFunction$1(functionNode);
|
|
11160
11654
|
}
|
|
11161
11655
|
return null;
|
|
11162
11656
|
};
|
|
@@ -11405,13 +11899,13 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
11405
11899
|
};
|
|
11406
11900
|
const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
|
|
11407
11901
|
let pathAnchor = usageNode;
|
|
11408
|
-
let pathOwner = findEnclosingFunction(pathAnchor);
|
|
11902
|
+
let pathOwner = findEnclosingFunction$1(pathAnchor);
|
|
11409
11903
|
while (pathOwner && isSynchronousIteratorCallback(pathOwner)) {
|
|
11410
11904
|
if (matchingNodes.length > 0 && matchingNodes.every((matchingNode) => context.cfg.enclosingFunction(matchingNode) === pathOwner)) break;
|
|
11411
11905
|
const iteratorCall = pathOwner.parent;
|
|
11412
11906
|
if (!isNodeOfType(iteratorCall, "CallExpression")) break;
|
|
11413
11907
|
pathAnchor = iteratorCall;
|
|
11414
|
-
pathOwner = findEnclosingFunction(pathAnchor);
|
|
11908
|
+
pathOwner = findEnclosingFunction$1(pathAnchor);
|
|
11415
11909
|
}
|
|
11416
11910
|
const owner = context.cfg.enclosingFunction(pathAnchor);
|
|
11417
11911
|
if (!owner) return false;
|
|
@@ -11640,7 +12134,7 @@ const findCollectionMappingCall = (callbackNode) => {
|
|
|
11640
12134
|
return callee.property.name === "map" && callNode.arguments?.[0] === callbackNode ? callNode : null;
|
|
11641
12135
|
};
|
|
11642
12136
|
const findMappedResourceCollectionKey = (resourceNode, context) => {
|
|
11643
|
-
const callbackNode = findEnclosingFunction(resourceNode);
|
|
12137
|
+
const callbackNode = findEnclosingFunction$1(resourceNode);
|
|
11644
12138
|
if (!callbackNode || !isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression")) return null;
|
|
11645
12139
|
const mappingCall = findCollectionMappingCall(callbackNode);
|
|
11646
12140
|
if (!mappingCall) return null;
|
|
@@ -11751,10 +12245,10 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
11751
12245
|
});
|
|
11752
12246
|
if (invocationCalls.length !== 1) return null;
|
|
11753
12247
|
const invocationCall = invocationCalls[0];
|
|
11754
|
-
return findEnclosingFunction(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
12248
|
+
return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
11755
12249
|
};
|
|
11756
12250
|
const resolveCleanupPathAnchor = (usageNode, effectCallback, context) => {
|
|
11757
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
12251
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
11758
12252
|
if (!usageFunction || usageFunction === effectCallback) return usageNode;
|
|
11759
12253
|
return findSingleDirectInvocation(usageFunction, effectCallback, context) ?? usageNode;
|
|
11760
12254
|
};
|
|
@@ -11769,7 +12263,7 @@ const resolveSingleAssignedCleanupFunction = (expression, usage, context) => {
|
|
|
11769
12263
|
const assignmentReference = assignmentReferences[0];
|
|
11770
12264
|
const assignmentTarget = findTransparentExpressionRoot(assignmentReference.identifier);
|
|
11771
12265
|
const assignmentNode = assignmentTarget.parent;
|
|
11772
|
-
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction(assignmentNode) !== findEnclosingFunction(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
|
|
12266
|
+
if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction$1(assignmentNode) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
|
|
11773
12267
|
const assignedValue = stripParenExpression(assignmentNode.right);
|
|
11774
12268
|
return isFunctionLike$2(assignedValue) ? assignedValue : null;
|
|
11775
12269
|
};
|
|
@@ -11858,12 +12352,12 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
11858
12352
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
11859
12353
|
};
|
|
11860
12354
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
11861
|
-
const componentFunction = findEnclosingFunction(callback);
|
|
12355
|
+
const componentFunction = findEnclosingFunction$1(callback);
|
|
11862
12356
|
if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
|
|
11863
12357
|
let didFindUnmountCleanup = false;
|
|
11864
12358
|
walkAst(componentFunction.body, (child) => {
|
|
11865
12359
|
if (didFindUnmountCleanup) return false;
|
|
11866
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
|
|
12360
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
|
|
11867
12361
|
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
11868
12362
|
const dependencyList = child.arguments?.[1];
|
|
11869
12363
|
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
@@ -11959,7 +12453,7 @@ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, co
|
|
|
11959
12453
|
return didWriteGuard;
|
|
11960
12454
|
};
|
|
11961
12455
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
11962
|
-
const usageFunction = findEnclosingFunction(usage.node);
|
|
12456
|
+
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
11963
12457
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
11964
12458
|
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
11965
12459
|
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
@@ -11967,7 +12461,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
|
|
|
11967
12461
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
11968
12462
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
11969
12463
|
if (callback.async) return false;
|
|
11970
|
-
if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
12464
|
+
if (usage.kind === "subscribe" && findEnclosingFunction$1(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
11971
12465
|
if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
|
|
11972
12466
|
const matchingCleanupReturns = [];
|
|
11973
12467
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
@@ -12136,7 +12630,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
|
12136
12630
|
parentNode = currentNode.parent;
|
|
12137
12631
|
}
|
|
12138
12632
|
if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
|
|
12139
|
-
const effectCallback = findEnclosingFunction(parentNode);
|
|
12633
|
+
const effectCallback = findEnclosingFunction$1(parentNode);
|
|
12140
12634
|
const effectCall = effectCallback?.parent;
|
|
12141
12635
|
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
12142
12636
|
};
|
|
@@ -12146,13 +12640,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
12146
12640
|
if (!bindingIdentifier) return false;
|
|
12147
12641
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
12148
12642
|
if (!symbol) return false;
|
|
12149
|
-
return symbol.references.some((reference) => findEnclosingFunction(reference.identifier) !== functionNode);
|
|
12643
|
+
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
12150
12644
|
};
|
|
12151
12645
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
12152
12646
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
12153
|
-
const releaseFunction = findEnclosingFunction(releaseNode);
|
|
12647
|
+
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
12154
12648
|
if (!releaseFunction) return true;
|
|
12155
|
-
if (releaseFunction === findEnclosingFunction(usage.node)) return true;
|
|
12649
|
+
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
12156
12650
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
12157
12651
|
};
|
|
12158
12652
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -12335,10 +12829,10 @@ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction
|
|
|
12335
12829
|
}
|
|
12336
12830
|
if (assignedKey !== storage.refCurrentKey) return;
|
|
12337
12831
|
const assignedValue = stripParenExpression(child.right);
|
|
12338
|
-
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction(child) === cleanupFunction) return;
|
|
12832
|
+
if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction$1(child) === cleanupFunction) return;
|
|
12339
12833
|
const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
|
|
12340
12834
|
const storesMatchingHandler = assignedSessionProperties.every((property) => isNodeOfType(property, "Property")) && assignedSessionProperties.some((property) => isNodeOfType(property, "Property") && `${storage.refCurrentKey}.${getStaticPropertyKeyName(property) ?? ""}` === storage.handlerKey && resolveExpressionKey(property.value, context) === usage.handlerKey);
|
|
12341
|
-
if (findEnclosingFunction(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12835
|
+
if (findEnclosingFunction$1(child) !== retainedFunction || !storesMatchingHandler) {
|
|
12342
12836
|
hasUnsafeRefWrite = true;
|
|
12343
12837
|
return false;
|
|
12344
12838
|
}
|
|
@@ -12359,12 +12853,12 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
12359
12853
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
12360
12854
|
};
|
|
12361
12855
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
12362
|
-
const componentFunction = findEnclosingFunction(retainedFunction);
|
|
12856
|
+
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
12363
12857
|
if (!componentFunction || !isFunctionLike$2(componentFunction)) return false;
|
|
12364
12858
|
let didFindCleanupEffect = false;
|
|
12365
12859
|
walkAst(componentFunction.body, (child) => {
|
|
12366
12860
|
if (didFindCleanupEffect) return false;
|
|
12367
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
12861
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
12368
12862
|
const effectStatement = findTransparentExpressionRoot(child).parent;
|
|
12369
12863
|
if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
|
|
12370
12864
|
const effectCallback = getEffectCallback(child);
|
|
@@ -13218,6 +13712,202 @@ const resolveExhaustiveDepsSettings = (settings) => {
|
|
|
13218
13712
|
};
|
|
13219
13713
|
};
|
|
13220
13714
|
//#endregion
|
|
13715
|
+
//#region src/plugin/utils/is-ast-descendant.ts
|
|
13716
|
+
/**
|
|
13717
|
+
* True when `inner` is `outer` itself or any descendant in the AST
|
|
13718
|
+
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
13719
|
+
* match (`true`) or the chain's root (`false`).
|
|
13720
|
+
*
|
|
13721
|
+
* Was duplicated byte-identical in:
|
|
13722
|
+
* - exhaustive-deps-symbol-stability.ts
|
|
13723
|
+
* - semantic/closure-captures.ts
|
|
13724
|
+
*/
|
|
13725
|
+
const isAstDescendant = (inner, outer) => {
|
|
13726
|
+
let current = inner;
|
|
13727
|
+
while (current) {
|
|
13728
|
+
if (current === outer) return true;
|
|
13729
|
+
current = current.parent ?? null;
|
|
13730
|
+
}
|
|
13731
|
+
return false;
|
|
13732
|
+
};
|
|
13733
|
+
//#endregion
|
|
13734
|
+
//#region src/plugin/rules/react-builtins/exhaustive-deps-sole-writer-guard.ts
|
|
13735
|
+
const EQUALITY_BINARY_OPERATORS = new Set(["===", "!=="]);
|
|
13736
|
+
const getUseStateSetterSymbol = (stateSymbol, scopes) => {
|
|
13737
|
+
const declaration = stateSymbol.declarationNode;
|
|
13738
|
+
if (!isNodeOfType(declaration, "VariableDeclarator")) return null;
|
|
13739
|
+
if (!isNodeOfType(declaration.id, "ArrayPattern")) return null;
|
|
13740
|
+
const stateBinding = declaration.id.elements[0];
|
|
13741
|
+
const setterBinding = declaration.id.elements[1];
|
|
13742
|
+
if (stateBinding !== stateSymbol.bindingIdentifier || !isNodeOfType(setterBinding, "Identifier")) return null;
|
|
13743
|
+
const initializer = declaration.init ? stripParenExpression(declaration.init) : null;
|
|
13744
|
+
if (!initializer || !isReactApiCall(initializer, "useState", scopes, {
|
|
13745
|
+
allowGlobalReactNamespace: true,
|
|
13746
|
+
allowUnboundBareCalls: true,
|
|
13747
|
+
resolveNamedAliases: true
|
|
13748
|
+
})) return null;
|
|
13749
|
+
return scopes.symbolFor(setterBinding);
|
|
13750
|
+
};
|
|
13751
|
+
const getDirectSetterCall = (setterSymbol, callback) => {
|
|
13752
|
+
if (setterSymbol.references.length !== 1) return null;
|
|
13753
|
+
const setterReference = setterSymbol.references[0];
|
|
13754
|
+
if (setterReference.flag !== "read") return null;
|
|
13755
|
+
const referenceRoot = findTransparentExpressionRoot(setterReference.identifier);
|
|
13756
|
+
const callExpression = referenceRoot.parent;
|
|
13757
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot || callExpression.arguments.length !== 1 || findEnclosingFunction$1(callExpression) !== callback) return null;
|
|
13758
|
+
const writtenValue = stripParenExpression(callExpression.arguments[0]);
|
|
13759
|
+
if (isNodeOfType(writtenValue, "ArrowFunctionExpression") || isNodeOfType(writtenValue, "FunctionExpression") || isNodeOfType(writtenValue, "SpreadElement")) return null;
|
|
13760
|
+
return {
|
|
13761
|
+
callExpression,
|
|
13762
|
+
writtenValue
|
|
13763
|
+
};
|
|
13764
|
+
};
|
|
13765
|
+
const isGlobalObjectIsCall = (callExpression, scopes) => {
|
|
13766
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
13767
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
13768
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
13769
|
+
const propertyName = getStaticPropertyName(callee);
|
|
13770
|
+
const receiver = stripParenExpression(callee.object);
|
|
13771
|
+
return Boolean(propertyName === "is" && isNodeOfType(receiver, "Identifier") && receiver.name === "Object" && scopes.isGlobalReference(receiver));
|
|
13772
|
+
};
|
|
13773
|
+
const isGlobalNaNReference = (expression, scopes) => {
|
|
13774
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
13775
|
+
return isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression);
|
|
13776
|
+
};
|
|
13777
|
+
const getEqualityComparison = (stateReference, candidate, scopes) => {
|
|
13778
|
+
const unwrappedStateReference = stripParenExpression(stateReference);
|
|
13779
|
+
if (isNodeOfType(candidate, "BinaryExpression") && EQUALITY_BINARY_OPERATORS.has(candidate.operator)) {
|
|
13780
|
+
if (stripParenExpression(candidate.left) === unwrappedStateReference) return isGlobalNaNReference(candidate.right, scopes) ? null : {
|
|
13781
|
+
comparison: candidate,
|
|
13782
|
+
counterpart: candidate.right,
|
|
13783
|
+
areValuesEqualWhenTruthy: candidate.operator === "==="
|
|
13784
|
+
};
|
|
13785
|
+
if (stripParenExpression(candidate.right) === unwrappedStateReference) return isGlobalNaNReference(candidate.left, scopes) ? null : {
|
|
13786
|
+
comparison: candidate,
|
|
13787
|
+
counterpart: candidate.left,
|
|
13788
|
+
areValuesEqualWhenTruthy: candidate.operator === "==="
|
|
13789
|
+
};
|
|
13790
|
+
return null;
|
|
13791
|
+
}
|
|
13792
|
+
if (!isNodeOfType(candidate, "CallExpression") || !isGlobalObjectIsCall(candidate, scopes) || candidate.arguments.length !== 2) return null;
|
|
13793
|
+
const firstArgument = candidate.arguments[0];
|
|
13794
|
+
const secondArgument = candidate.arguments[1];
|
|
13795
|
+
if (isNodeOfType(firstArgument, "SpreadElement") || isNodeOfType(secondArgument, "SpreadElement")) return null;
|
|
13796
|
+
if (stripParenExpression(firstArgument) === unwrappedStateReference) return {
|
|
13797
|
+
comparison: candidate,
|
|
13798
|
+
counterpart: secondArgument,
|
|
13799
|
+
areValuesEqualWhenTruthy: true
|
|
13800
|
+
};
|
|
13801
|
+
if (stripParenExpression(secondArgument) === unwrappedStateReference) return {
|
|
13802
|
+
comparison: candidate,
|
|
13803
|
+
counterpart: firstArgument,
|
|
13804
|
+
areValuesEqualWhenTruthy: true
|
|
13805
|
+
};
|
|
13806
|
+
return null;
|
|
13807
|
+
};
|
|
13808
|
+
const findEqualityComparison = (stateReference, test, scopes) => {
|
|
13809
|
+
let current = stateReference.parent;
|
|
13810
|
+
while (current && isAstDescendant(current, test)) {
|
|
13811
|
+
const comparison = getEqualityComparison(stateReference, current, scopes);
|
|
13812
|
+
if (comparison) return comparison;
|
|
13813
|
+
if (current === test) break;
|
|
13814
|
+
current = current.parent;
|
|
13815
|
+
}
|
|
13816
|
+
return null;
|
|
13817
|
+
};
|
|
13818
|
+
const doesTestOutcomeRequireComparisonOutcome = (comparison, test, testOutcome, comparisonOutcome) => {
|
|
13819
|
+
let requiredChildOutcome = testOutcome;
|
|
13820
|
+
let current = comparison;
|
|
13821
|
+
while (current !== test) {
|
|
13822
|
+
const parent = current.parent;
|
|
13823
|
+
if (!parent || !isAstDescendant(parent, test)) return false;
|
|
13824
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") requiredChildOutcome = !requiredChildOutcome;
|
|
13825
|
+
else if (isNodeOfType(parent, "LogicalExpression")) {
|
|
13826
|
+
if (parent.operator === "&&" && !requiredChildOutcome) return false;
|
|
13827
|
+
if (parent.operator === "||" && requiredChildOutcome) return false;
|
|
13828
|
+
if (parent.operator !== "&&" && parent.operator !== "||") return false;
|
|
13829
|
+
} else if (!TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type)) return false;
|
|
13830
|
+
current = parent;
|
|
13831
|
+
}
|
|
13832
|
+
return requiredChildOutcome === comparisonOutcome;
|
|
13833
|
+
};
|
|
13834
|
+
const resolveImmutableAliasExpression = (expression, scopes) => {
|
|
13835
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
13836
|
+
let current = stripParenExpression(expression);
|
|
13837
|
+
while (isNodeOfType(current, "Identifier")) {
|
|
13838
|
+
const symbol = scopes.symbolFor(current);
|
|
13839
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) break;
|
|
13840
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
13841
|
+
if (!initializer || !isNodeOfType(stripParenExpression(initializer), "Identifier")) break;
|
|
13842
|
+
visitedSymbolIds.add(symbol.id);
|
|
13843
|
+
current = stripParenExpression(initializer);
|
|
13844
|
+
}
|
|
13845
|
+
return current;
|
|
13846
|
+
};
|
|
13847
|
+
const referencesSameValue = (leftExpression, rightExpression, scopes) => {
|
|
13848
|
+
const left = resolveImmutableAliasExpression(leftExpression, scopes);
|
|
13849
|
+
const right = resolveImmutableAliasExpression(rightExpression, scopes);
|
|
13850
|
+
if (left === right) return true;
|
|
13851
|
+
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
13852
|
+
const leftSymbol = scopes.symbolFor(left);
|
|
13853
|
+
const rightSymbol = scopes.symbolFor(right);
|
|
13854
|
+
if (leftSymbol || rightSymbol) return leftSymbol?.id === rightSymbol?.id;
|
|
13855
|
+
return left.name === right.name;
|
|
13856
|
+
}
|
|
13857
|
+
if (isNodeOfType(left, "Literal") && isNodeOfType(right, "Literal")) return Object.is(left.value, right.value);
|
|
13858
|
+
return false;
|
|
13859
|
+
};
|
|
13860
|
+
const hasDeclaredTriggerDependency = (callback) => {
|
|
13861
|
+
const hookCall = findTransparentExpressionRoot(callback).parent;
|
|
13862
|
+
if (!isNodeOfType(hookCall, "CallExpression")) return false;
|
|
13863
|
+
const dependencyArray = hookCall.arguments[1];
|
|
13864
|
+
return Boolean(isNodeOfType(dependencyArray, "ArrayExpression") && dependencyArray.elements.length);
|
|
13865
|
+
};
|
|
13866
|
+
const doesBranchExitEffect = (branch) => {
|
|
13867
|
+
if (isNodeOfType(branch, "ReturnStatement") || isNodeOfType(branch, "ThrowStatement")) return true;
|
|
13868
|
+
if (!isNodeOfType(branch, "BlockStatement")) return false;
|
|
13869
|
+
const terminalStatement = branch.body.at(-1);
|
|
13870
|
+
return Boolean(terminalStatement && doesBranchExitEffect(terminalStatement));
|
|
13871
|
+
};
|
|
13872
|
+
const doesGuardDominateLaterSetter = (guard, setterCall) => {
|
|
13873
|
+
if (!isNodeOfType(guard, "IfStatement") || guard.alternate) return false;
|
|
13874
|
+
if (!doesBranchExitEffect(guard.consequent)) return false;
|
|
13875
|
+
const block = guard.parent;
|
|
13876
|
+
if (!isNodeOfType(block, "BlockStatement")) return false;
|
|
13877
|
+
const guardIndex = block.body.findIndex((statement) => statement === guard);
|
|
13878
|
+
return block.body.some((statement, statementIndex) => statementIndex > guardIndex && isAstDescendant(setterCall, statement));
|
|
13879
|
+
};
|
|
13880
|
+
const findDominatingGuardCounterpart = (stateReference, setterCall, callback, scopes) => {
|
|
13881
|
+
let current = stateReference.parent;
|
|
13882
|
+
while (current && current !== callback) {
|
|
13883
|
+
if (isNodeOfType(current, "IfStatement") && isAstDescendant(stateReference, current.test) && (isAstDescendant(setterCall, current.consequent) || Boolean(current.alternate && isAstDescendant(setterCall, current.alternate)) || doesGuardDominateLaterSetter(current, setterCall))) {
|
|
13884
|
+
const setterRunsWhenTestTruthy = isAstDescendant(setterCall, current.consequent);
|
|
13885
|
+
if (setterRunsWhenTestTruthy === (Boolean(current.alternate && isAstDescendant(setterCall, current.alternate)) || doesGuardDominateLaterSetter(current, setterCall))) return null;
|
|
13886
|
+
const equalityComparison = findEqualityComparison(stateReference, current.test, scopes);
|
|
13887
|
+
if (!equalityComparison) return null;
|
|
13888
|
+
const comparisonOutcomeForDifferentValues = !equalityComparison.areValuesEqualWhenTruthy;
|
|
13889
|
+
if (!doesTestOutcomeRequireComparisonOutcome(equalityComparison.comparison, current.test, setterRunsWhenTestTruthy, comparisonOutcomeForDifferentValues)) return null;
|
|
13890
|
+
return equalityComparison.counterpart;
|
|
13891
|
+
}
|
|
13892
|
+
current = current.parent;
|
|
13893
|
+
}
|
|
13894
|
+
return null;
|
|
13895
|
+
};
|
|
13896
|
+
const isSoleWriterEffectGuardCapture = (stateSymbol, callback, scopes) => {
|
|
13897
|
+
if (!hasDeclaredTriggerDependency(callback)) return false;
|
|
13898
|
+
if (stateSymbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
13899
|
+
const setterSymbol = getUseStateSetterSymbol(stateSymbol, scopes);
|
|
13900
|
+
if (!setterSymbol) return false;
|
|
13901
|
+
const setterWrite = getDirectSetterCall(setterSymbol, callback);
|
|
13902
|
+
if (!setterWrite) return false;
|
|
13903
|
+
const callbackStateReferences = stateSymbol.references.filter((reference) => isAstDescendant(reference.identifier, callback));
|
|
13904
|
+
if (callbackStateReferences.length !== 1) return false;
|
|
13905
|
+
const stateReferenceRoot = findTransparentExpressionRoot(callbackStateReferences[0].identifier);
|
|
13906
|
+
if (isNodeOfType(stateReferenceRoot.parent, "MemberExpression") && stateReferenceRoot.parent.object === stateReferenceRoot) return false;
|
|
13907
|
+
const counterpart = findDominatingGuardCounterpart(callbackStateReferences[0].identifier, setterWrite.callExpression, callback, scopes);
|
|
13908
|
+
return Boolean(counterpart && referencesSameValue(counterpart, setterWrite.writtenValue, scopes));
|
|
13909
|
+
};
|
|
13910
|
+
//#endregion
|
|
13221
13911
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-suppression.ts
|
|
13222
13912
|
const DISABLE_COMMENT_RULE_NAME_PATTERN$1 = /(?:^|[\s,/])exhaustive-deps(?:$|[\s,:])/;
|
|
13223
13913
|
const DISABLE_NEXT_LINE_PATTERN$1 = /\b(?:eslint|oxlint)-disable-next-line\b([^\n]*)/;
|
|
@@ -13289,25 +13979,6 @@ const isExhaustiveDepsSuppressedAt = (filename, nodeStartOffset) => {
|
|
|
13289
13979
|
return index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf16NewlineOffsets)) || index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf8NewlineOffsets));
|
|
13290
13980
|
};
|
|
13291
13981
|
//#endregion
|
|
13292
|
-
//#region src/plugin/utils/is-ast-descendant.ts
|
|
13293
|
-
/**
|
|
13294
|
-
* True when `inner` is `outer` itself or any descendant in the AST
|
|
13295
|
-
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
13296
|
-
* match (`true`) or the chain's root (`false`).
|
|
13297
|
-
*
|
|
13298
|
-
* Was duplicated byte-identical in:
|
|
13299
|
-
* - exhaustive-deps-symbol-stability.ts
|
|
13300
|
-
* - semantic/closure-captures.ts
|
|
13301
|
-
*/
|
|
13302
|
-
const isAstDescendant = (inner, outer) => {
|
|
13303
|
-
let current = inner;
|
|
13304
|
-
while (current) {
|
|
13305
|
-
if (current === outer) return true;
|
|
13306
|
-
current = current.parent ?? null;
|
|
13307
|
-
}
|
|
13308
|
-
return false;
|
|
13309
|
-
};
|
|
13310
|
-
//#endregion
|
|
13311
13982
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
|
|
13312
13983
|
/**
|
|
13313
13984
|
* Symbol-stability helpers consumed by the `exhaustive-deps` rule.
|
|
@@ -13488,6 +14159,7 @@ const EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS = new Set([
|
|
|
13488
14159
|
"useLayoutEffect",
|
|
13489
14160
|
"useInsertionEffect"
|
|
13490
14161
|
]);
|
|
14162
|
+
const SOLE_WRITER_GUARD_HOOKS = new Set(["useEffect", "useLayoutEffect"]);
|
|
13491
14163
|
const buildAdditionalHooksRegex = (additional) => {
|
|
13492
14164
|
if (!additional) return null;
|
|
13493
14165
|
try {
|
|
@@ -13624,7 +14296,7 @@ const stringifyMemberChain = (node) => {
|
|
|
13624
14296
|
}
|
|
13625
14297
|
return null;
|
|
13626
14298
|
};
|
|
13627
|
-
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
14299
|
+
const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allowSoleWriterEffectGuards = false) => {
|
|
13628
14300
|
const keys = /* @__PURE__ */ new Set();
|
|
13629
14301
|
const stableCapturedNames = /* @__PURE__ */ new Set();
|
|
13630
14302
|
const moduleScopeCapturedNames = /* @__PURE__ */ new Set();
|
|
@@ -13635,6 +14307,10 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
|
|
|
13635
14307
|
const symbol = reference.resolvedSymbol;
|
|
13636
14308
|
if (!symbol) continue;
|
|
13637
14309
|
if (isRecursiveInitializerCapture(symbol, callback)) continue;
|
|
14310
|
+
if (allowSoleWriterEffectGuards && isSoleWriterEffectGuardCapture(symbol, callback, scopes)) {
|
|
14311
|
+
stableCapturedNames.add(symbol.name);
|
|
14312
|
+
continue;
|
|
14313
|
+
}
|
|
13638
14314
|
if (symbolHasStableValue(symbol, scopes)) {
|
|
13639
14315
|
stableCapturedNames.add(symbol.name);
|
|
13640
14316
|
continue;
|
|
@@ -14307,7 +14983,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
14307
14983
|
if (isNodeOfType(stripped, "Identifier")) declaredExactBindingKeys.add(key);
|
|
14308
14984
|
declaredKeyToReportNode.set(key, elementNode);
|
|
14309
14985
|
}
|
|
14310
|
-
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys);
|
|
14986
|
+
const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys, SOLE_WRITER_GUARD_HOOKS.has(hookName));
|
|
14311
14987
|
for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
|
|
14312
14988
|
addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
|
|
14313
14989
|
const missingCaptureKeys = [];
|
|
@@ -14747,7 +15423,7 @@ const forbidElements = defineRule({
|
|
|
14747
15423
|
});
|
|
14748
15424
|
//#endregion
|
|
14749
15425
|
//#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
|
|
14750
|
-
const MESSAGE$
|
|
15426
|
+
const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
|
|
14751
15427
|
const forwardRefUsesRef = defineRule({
|
|
14752
15428
|
id: "forward-ref-uses-ref",
|
|
14753
15429
|
title: "forwardRef without ref parameter",
|
|
@@ -14770,7 +15446,7 @@ const forwardRefUsesRef = defineRule({
|
|
|
14770
15446
|
if (isNodeOfType(onlyParam, "RestElement")) return;
|
|
14771
15447
|
context.report({
|
|
14772
15448
|
node: inner,
|
|
14773
|
-
message: MESSAGE$
|
|
15449
|
+
message: MESSAGE$54
|
|
14774
15450
|
});
|
|
14775
15451
|
} })
|
|
14776
15452
|
});
|
|
@@ -14811,7 +15487,7 @@ const gitProviderUrlInjectionRisk = defineRule({
|
|
|
14811
15487
|
});
|
|
14812
15488
|
//#endregion
|
|
14813
15489
|
//#region src/plugin/rules/a11y/heading-has-content.ts
|
|
14814
|
-
const MESSAGE$
|
|
15490
|
+
const MESSAGE$53 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
|
|
14815
15491
|
const DEFAULT_HEADING_TAGS = [
|
|
14816
15492
|
"h1",
|
|
14817
15493
|
"h2",
|
|
@@ -14845,7 +15521,7 @@ const headingHasContent = defineRule({
|
|
|
14845
15521
|
for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
|
|
14846
15522
|
context.report({
|
|
14847
15523
|
node,
|
|
14848
|
-
message: MESSAGE$
|
|
15524
|
+
message: MESSAGE$53
|
|
14849
15525
|
});
|
|
14850
15526
|
} };
|
|
14851
15527
|
}
|
|
@@ -15009,7 +15685,7 @@ const hooksNoNanInDeps = defineRule({
|
|
|
15009
15685
|
});
|
|
15010
15686
|
//#endregion
|
|
15011
15687
|
//#region src/plugin/rules/a11y/html-has-lang.ts
|
|
15012
|
-
const MESSAGE$
|
|
15688
|
+
const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
|
|
15013
15689
|
const resolveSettings$39 = (settings) => {
|
|
15014
15690
|
const reactDoctor = settings?.["react-doctor"];
|
|
15015
15691
|
return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
|
|
@@ -15056,13 +15732,13 @@ const htmlHasLang = defineRule({
|
|
|
15056
15732
|
if (!lang) {
|
|
15057
15733
|
context.report({
|
|
15058
15734
|
node: node.name,
|
|
15059
|
-
message: MESSAGE$
|
|
15735
|
+
message: MESSAGE$52
|
|
15060
15736
|
});
|
|
15061
15737
|
return;
|
|
15062
15738
|
}
|
|
15063
15739
|
if (evaluateLang(lang.value) === "empty") context.report({
|
|
15064
15740
|
node: lang,
|
|
15065
|
-
message: MESSAGE$
|
|
15741
|
+
message: MESSAGE$52
|
|
15066
15742
|
});
|
|
15067
15743
|
} };
|
|
15068
15744
|
}
|
|
@@ -15302,7 +15978,7 @@ const htmlNoNestedInteractive = defineRule({
|
|
|
15302
15978
|
});
|
|
15303
15979
|
//#endregion
|
|
15304
15980
|
//#region src/plugin/rules/a11y/iframe-has-title.ts
|
|
15305
|
-
const MESSAGE$
|
|
15981
|
+
const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
|
|
15306
15982
|
const evaluateTitleValue = (value) => {
|
|
15307
15983
|
if (!value) return "missing";
|
|
15308
15984
|
if (isNodeOfType(value, "Literal")) {
|
|
@@ -15342,14 +16018,14 @@ const iframeHasTitle = defineRule({
|
|
|
15342
16018
|
if (!titleAttr) {
|
|
15343
16019
|
if (hasSpread || tag === "iframe") context.report({
|
|
15344
16020
|
node: node.name,
|
|
15345
|
-
message: MESSAGE$
|
|
16021
|
+
message: MESSAGE$51
|
|
15346
16022
|
});
|
|
15347
16023
|
return;
|
|
15348
16024
|
}
|
|
15349
16025
|
const verdict = evaluateTitleValue(titleAttr.value);
|
|
15350
16026
|
if (verdict === "missing" || verdict === "empty") context.report({
|
|
15351
16027
|
node: titleAttr,
|
|
15352
|
-
message: MESSAGE$
|
|
16028
|
+
message: MESSAGE$51
|
|
15353
16029
|
});
|
|
15354
16030
|
} })
|
|
15355
16031
|
});
|
|
@@ -15474,7 +16150,7 @@ const iframeMissingSandbox = defineRule({
|
|
|
15474
16150
|
});
|
|
15475
16151
|
//#endregion
|
|
15476
16152
|
//#region src/plugin/rules/a11y/img-redundant-alt.ts
|
|
15477
|
-
const MESSAGE$
|
|
16153
|
+
const MESSAGE$50 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
|
|
15478
16154
|
const DEFAULT_COMPONENTS = ["img"];
|
|
15479
16155
|
const DEFAULT_REDUNDANT_WORDS = [
|
|
15480
16156
|
"image",
|
|
@@ -15539,7 +16215,7 @@ const imgRedundantAlt = defineRule({
|
|
|
15539
16215
|
if (!altAttribute) return;
|
|
15540
16216
|
if (altValueRedundant(altAttribute, settings.words)) context.report({
|
|
15541
16217
|
node: altAttribute,
|
|
15542
|
-
message: MESSAGE$
|
|
16218
|
+
message: MESSAGE$50
|
|
15543
16219
|
});
|
|
15544
16220
|
} };
|
|
15545
16221
|
}
|
|
@@ -16121,14 +16797,14 @@ const isBindingInvokedOnRenderPath = (root, bindingName) => {
|
|
|
16121
16797
|
if (didFindRenderPathInvocation) return false;
|
|
16122
16798
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
16123
16799
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== bindingName) return;
|
|
16124
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
16800
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
16125
16801
|
while (enclosingFunction) {
|
|
16126
16802
|
if (isDeferredCallbackPosition$1(enclosingFunction) || getHandlerNamedBindingName(enclosingFunction)) return;
|
|
16127
16803
|
if (containingFunctionIsComponentOrHook(enclosingFunction)) {
|
|
16128
16804
|
didFindRenderPathInvocation = true;
|
|
16129
16805
|
return;
|
|
16130
16806
|
}
|
|
16131
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
16807
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
16132
16808
|
}
|
|
16133
16809
|
});
|
|
16134
16810
|
return didFindRenderPathInvocation;
|
|
@@ -16163,7 +16839,7 @@ const jotaiSelectAtomInRenderBody = defineRule({
|
|
|
16163
16839
|
};
|
|
16164
16840
|
return { CallExpression(node) {
|
|
16165
16841
|
if (!isImportedSelectAtom(node)) return;
|
|
16166
|
-
const nearestFunctionLike = findEnclosingFunction(node);
|
|
16842
|
+
const nearestFunctionLike = findEnclosingFunction$1(node);
|
|
16167
16843
|
if (!nearestFunctionLike) return;
|
|
16168
16844
|
if (isDeferredCallback(nearestFunctionLike)) return;
|
|
16169
16845
|
if (isBindingUsedAsHandler(nearestFunctionLike)) return;
|
|
@@ -17160,7 +17836,7 @@ const isPersistentCacheSetWrite = (callExpression, newExpression, enclosingFunct
|
|
|
17160
17836
|
return !isAstDescendant(receiverBinding.scopeOwner, enclosingFunction);
|
|
17161
17837
|
};
|
|
17162
17838
|
const isInsideCacheMemo = (node) => {
|
|
17163
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
17839
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
17164
17840
|
let child = node;
|
|
17165
17841
|
let cursor = node.parent ?? null;
|
|
17166
17842
|
while (cursor) {
|
|
@@ -17198,7 +17874,7 @@ const getFunctionName = (functionNode) => {
|
|
|
17198
17874
|
return null;
|
|
17199
17875
|
};
|
|
17200
17876
|
const isUncacheableOptionsMergeUtility = (node) => {
|
|
17201
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
17877
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
17202
17878
|
if (!enclosingFunction || !isFunctionLike$2(enclosingFunction)) return false;
|
|
17203
17879
|
const functionName = getFunctionName(enclosingFunction);
|
|
17204
17880
|
if (!functionName || isComponentOrHookName(functionName)) return false;
|
|
@@ -17389,13 +18065,6 @@ const isSafeStatefulReplaceAllSearch = (node, flags, context) => {
|
|
|
17389
18065
|
const callee = stripParenExpression(replaceAllCall.callee);
|
|
17390
18066
|
return isNodeOfType(callee, "MemberExpression") && !callee.computed && !callee.optional && !replaceAllCall.optional && isNodeOfType(callee.property, "Identifier") && callee.property.name === "replaceAll" && isProvenNativeStringReceiver(callee.object, context);
|
|
17391
18067
|
};
|
|
17392
|
-
const getDestructuredBindingPropertyName = (bindingIdentifier) => {
|
|
17393
|
-
let bindingNode = bindingIdentifier;
|
|
17394
|
-
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
17395
|
-
const property = bindingNode.parent;
|
|
17396
|
-
if (!isNodeOfType(property, "Property") || property.value !== bindingNode || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
17397
|
-
return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
17398
|
-
};
|
|
17399
18068
|
const extendGlobalPath = (basePath, propertyName) => {
|
|
17400
18069
|
if (basePath === "global") {
|
|
17401
18070
|
if (propertyName === null || GLOBAL_OBJECT_NAMES.has(propertyName)) return "global";
|
|
@@ -17509,7 +18178,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
|
|
|
17509
18178
|
if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
|
|
17510
18179
|
return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
|
|
17511
18180
|
};
|
|
17512
|
-
const MESSAGE$
|
|
18181
|
+
const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
|
|
17513
18182
|
const jsHoistRegexp = defineRule({
|
|
17514
18183
|
id: "js-hoist-regexp",
|
|
17515
18184
|
title: "RegExp built inside a loop",
|
|
@@ -17526,7 +18195,7 @@ const jsHoistRegexp = defineRule({
|
|
|
17526
18195
|
if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
|
|
17527
18196
|
context.report({
|
|
17528
18197
|
node,
|
|
17529
|
-
message: MESSAGE$
|
|
18198
|
+
message: MESSAGE$49
|
|
17530
18199
|
});
|
|
17531
18200
|
};
|
|
17532
18201
|
return createLoopAwareVisitors({
|
|
@@ -17685,7 +18354,89 @@ const collectEarlierAndGuardOperands = (node) => {
|
|
|
17685
18354
|
return earlierOperands;
|
|
17686
18355
|
};
|
|
17687
18356
|
//#endregion
|
|
18357
|
+
//#region src/plugin/utils/unwrap-object-integrity-expression.ts
|
|
18358
|
+
const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
|
|
18359
|
+
"freeze",
|
|
18360
|
+
"seal",
|
|
18361
|
+
"preventExtensions"
|
|
18362
|
+
]);
|
|
18363
|
+
const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
|
|
18364
|
+
const getObjectIntegrityMethodName = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
18365
|
+
const expression = stripParenExpression(node);
|
|
18366
|
+
if (!isNodeOfType(expression, "CallExpression")) return null;
|
|
18367
|
+
const callee = stripParenExpression(expression.callee);
|
|
18368
|
+
const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : callee;
|
|
18369
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !scopes.isGlobalReference(receiver) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) return null;
|
|
18370
|
+
return callee.property.name;
|
|
18371
|
+
};
|
|
18372
|
+
const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
18373
|
+
let expression = stripParenExpression(node);
|
|
18374
|
+
while (isNodeOfType(expression, "CallExpression")) {
|
|
18375
|
+
if (!getObjectIntegrityMethodName(expression, scopes, methodNames)) break;
|
|
18376
|
+
const wrappedExpression = expression.arguments[0];
|
|
18377
|
+
if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
|
|
18378
|
+
expression = stripParenExpression(wrappedExpression);
|
|
18379
|
+
}
|
|
18380
|
+
return expression;
|
|
18381
|
+
};
|
|
18382
|
+
//#endregion
|
|
17688
18383
|
//#region src/plugin/rules/js-performance/js-length-check-first.ts
|
|
18384
|
+
const OBJECT_CARDINALITY_PROJECTION_METHOD_NAMES = new Set(["keys", "values"]);
|
|
18385
|
+
const getGlobalObjectProjection = (expression, context) => {
|
|
18386
|
+
const callExpression = stripParenExpression(expression);
|
|
18387
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
18388
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
18389
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
18390
|
+
const receiver = stripParenExpression(callee.object);
|
|
18391
|
+
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !context.scopes.isGlobalReference(receiver)) return null;
|
|
18392
|
+
const methodName = getStaticPropertyName(callee);
|
|
18393
|
+
if (!methodName || !OBJECT_CARDINALITY_PROJECTION_METHOD_NAMES.has(methodName)) return null;
|
|
18394
|
+
const callArguments = callExpression.arguments ?? [];
|
|
18395
|
+
if (callArguments.length !== 1 || isNodeOfType(callArguments[0], "SpreadElement")) return null;
|
|
18396
|
+
return {
|
|
18397
|
+
methodName,
|
|
18398
|
+
source: callArguments[0]
|
|
18399
|
+
};
|
|
18400
|
+
};
|
|
18401
|
+
const isPlainDataObjectLiteral = (expression) => {
|
|
18402
|
+
const objectExpression = stripParenExpression(expression);
|
|
18403
|
+
if (!isNodeOfType(objectExpression, "ObjectExpression")) return false;
|
|
18404
|
+
return (objectExpression.properties ?? []).every((property) => {
|
|
18405
|
+
if (!isNodeOfType(property, "Property") || property.kind !== "init" || property.method === true) return false;
|
|
18406
|
+
const propertyValue = stripParenExpression(property.value);
|
|
18407
|
+
return isNodeOfType(propertyValue, "Literal") || isNodeOfType(propertyValue, "TemplateLiteral") && propertyValue.expressions.length === 0;
|
|
18408
|
+
});
|
|
18409
|
+
};
|
|
18410
|
+
const getGlobalObjectFreezeArgument = (expression, context) => {
|
|
18411
|
+
const callExpression = stripParenExpression(expression);
|
|
18412
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
18413
|
+
if (getObjectIntegrityMethodName(callExpression, context.scopes) !== "freeze") return null;
|
|
18414
|
+
const callArguments = callExpression.arguments ?? [];
|
|
18415
|
+
if (callArguments.length !== 1 || isNodeOfType(callArguments[0], "SpreadElement")) return null;
|
|
18416
|
+
return callArguments[0];
|
|
18417
|
+
};
|
|
18418
|
+
const resolveStablePlainObjectRootId = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
18419
|
+
const source = stripParenExpression(expression);
|
|
18420
|
+
if (!isNodeOfType(source, "Identifier")) return null;
|
|
18421
|
+
const symbol = context.scopes.symbolFor(source);
|
|
18422
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
18423
|
+
const directConstInitializer = getDirectConstInitializer(symbol);
|
|
18424
|
+
if (!directConstInitializer) return null;
|
|
18425
|
+
visitedSymbolIds.add(symbol.id);
|
|
18426
|
+
const initializer = stripParenExpression(directConstInitializer);
|
|
18427
|
+
const frozenValue = getGlobalObjectFreezeArgument(initializer, context);
|
|
18428
|
+
if (frozenValue) return isPlainDataObjectLiteral(frozenValue) ? symbol.id : null;
|
|
18429
|
+
return resolveStablePlainObjectRootId(initializer, context, visitedSymbolIds);
|
|
18430
|
+
};
|
|
18431
|
+
const areEqualCardinalityObjectProjections = (receiverArray, indexedArray, context) => {
|
|
18432
|
+
const receiverProjection = getGlobalObjectProjection(receiverArray, context);
|
|
18433
|
+
const indexedProjection = getGlobalObjectProjection(indexedArray, context);
|
|
18434
|
+
if (!receiverProjection || !indexedProjection) return false;
|
|
18435
|
+
if (receiverProjection.methodName === indexedProjection.methodName) return false;
|
|
18436
|
+
const receiverRootId = resolveStablePlainObjectRootId(receiverProjection.source, context);
|
|
18437
|
+
const indexedRootId = resolveStablePlainObjectRootId(indexedProjection.source, context);
|
|
18438
|
+
return receiverRootId !== null && receiverRootId === indexedRootId;
|
|
18439
|
+
};
|
|
17689
18440
|
const findIndexedArrayObject = (callbackBody, indexParameterName) => {
|
|
17690
18441
|
let indexedArrayObject = null;
|
|
17691
18442
|
walkAst(callbackBody, (child) => {
|
|
@@ -17900,6 +18651,7 @@ const jsLengthCheckFirst = defineRule({
|
|
|
17900
18651
|
const resolvedReceiverSource = resolveComparedArraySource(receiverArrayObject, node);
|
|
17901
18652
|
const resolvedIndexedSource = resolveComparedArraySource(indexedArrayObject, node);
|
|
17902
18653
|
if (areExpressionsStructurallyEqual(resolvedReceiverSource, resolvedIndexedSource)) return;
|
|
18654
|
+
if (areEqualCardinalityObjectProjections(receiverArrayObject, indexedArrayObject, context) || areEqualCardinalityObjectProjections(resolvedReceiverSource, resolvedIndexedSource, context)) return;
|
|
17903
18655
|
const comparedArrayPairs = [[receiverArrayObject, indexedArrayObject], [resolvedReceiverSource, resolvedIndexedSource]];
|
|
17904
18656
|
if (collectEarlierAndGuardOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_ANY_COMPARISON_OPERATORS)))) return;
|
|
17905
18657
|
if (collectEarlierOrOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_MISMATCH_OPERATORS)))) return;
|
|
@@ -20146,7 +20898,7 @@ const jsxMaxDepth = defineRule({
|
|
|
20146
20898
|
});
|
|
20147
20899
|
//#endregion
|
|
20148
20900
|
//#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
|
|
20149
|
-
const MESSAGE$
|
|
20901
|
+
const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
|
|
20150
20902
|
const LITERAL_TEXT_TAGS = new Set([
|
|
20151
20903
|
"code",
|
|
20152
20904
|
"pre",
|
|
@@ -20216,7 +20968,7 @@ const jsxNoCommentTextnodes = defineRule({
|
|
|
20216
20968
|
if (isDeliberateStyledCommentToken(node)) return;
|
|
20217
20969
|
context.report({
|
|
20218
20970
|
node,
|
|
20219
|
-
message: MESSAGE$
|
|
20971
|
+
message: MESSAGE$48
|
|
20220
20972
|
});
|
|
20221
20973
|
} })
|
|
20222
20974
|
});
|
|
@@ -20247,7 +20999,7 @@ const isInsideFunctionScope = (node) => {
|
|
|
20247
20999
|
};
|
|
20248
21000
|
//#endregion
|
|
20249
21001
|
//#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
|
|
20250
|
-
const MESSAGE$
|
|
21002
|
+
const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
|
|
20251
21003
|
const CONTEXT_MODULES$1 = [
|
|
20252
21004
|
"react",
|
|
20253
21005
|
"use-context-selector",
|
|
@@ -20345,7 +21097,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
20345
21097
|
if (!isConstructedValue(innerExpression)) continue;
|
|
20346
21098
|
context.report({
|
|
20347
21099
|
node: attribute,
|
|
20348
|
-
message: MESSAGE$
|
|
21100
|
+
message: MESSAGE$47
|
|
20349
21101
|
});
|
|
20350
21102
|
}
|
|
20351
21103
|
}
|
|
@@ -21200,7 +21952,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
|
|
|
21200
21952
|
];
|
|
21201
21953
|
//#endregion
|
|
21202
21954
|
//#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
|
|
21203
|
-
const MESSAGE$
|
|
21955
|
+
const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
|
|
21204
21956
|
const isDataArrayPropName = (propName) => {
|
|
21205
21957
|
if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
|
|
21206
21958
|
for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -21287,7 +22039,7 @@ const jsxNoNewArrayAsProp = defineRule({
|
|
|
21287
22039
|
if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
|
|
21288
22040
|
context.report({
|
|
21289
22041
|
node,
|
|
21290
|
-
message: MESSAGE$
|
|
22042
|
+
message: MESSAGE$46
|
|
21291
22043
|
});
|
|
21292
22044
|
}
|
|
21293
22045
|
};
|
|
@@ -21545,7 +22297,7 @@ const SAFE_RECEIVER_NAMES = new Set([
|
|
|
21545
22297
|
]);
|
|
21546
22298
|
//#endregion
|
|
21547
22299
|
//#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
|
|
21548
|
-
const MESSAGE$
|
|
22300
|
+
const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
|
|
21549
22301
|
const isAccessorPredicateName = (propName) => {
|
|
21550
22302
|
for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
|
|
21551
22303
|
if (propName.length <= prefix.length) continue;
|
|
@@ -21751,7 +22503,7 @@ const jsxNoNewFunctionAsProp = defineRule({
|
|
|
21751
22503
|
if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
|
|
21752
22504
|
context.report({
|
|
21753
22505
|
node,
|
|
21754
|
-
message: MESSAGE$
|
|
22506
|
+
message: MESSAGE$45
|
|
21755
22507
|
});
|
|
21756
22508
|
}
|
|
21757
22509
|
};
|
|
@@ -21971,7 +22723,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
|
|
|
21971
22723
|
];
|
|
21972
22724
|
//#endregion
|
|
21973
22725
|
//#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
|
|
21974
|
-
const MESSAGE$
|
|
22726
|
+
const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
|
|
21975
22727
|
const isConfigObjectPropName = (propName) => {
|
|
21976
22728
|
if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
|
|
21977
22729
|
for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
|
|
@@ -22060,7 +22812,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22060
22812
|
if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
|
|
22061
22813
|
context.report({
|
|
22062
22814
|
node,
|
|
22063
|
-
message: MESSAGE$
|
|
22815
|
+
message: MESSAGE$44
|
|
22064
22816
|
});
|
|
22065
22817
|
}
|
|
22066
22818
|
};
|
|
@@ -22068,7 +22820,7 @@ const jsxNoNewObjectAsProp = defineRule({
|
|
|
22068
22820
|
});
|
|
22069
22821
|
//#endregion
|
|
22070
22822
|
//#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
|
|
22071
|
-
const MESSAGE$
|
|
22823
|
+
const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
|
|
22072
22824
|
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;
|
|
22073
22825
|
const resolveSettings$29 = (settings) => {
|
|
22074
22826
|
const reactDoctor = settings?.["react-doctor"];
|
|
@@ -22106,7 +22858,7 @@ const jsxNoScriptUrl = defineRule({
|
|
|
22106
22858
|
if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
|
|
22107
22859
|
if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
|
|
22108
22860
|
node: attribute,
|
|
22109
|
-
message: MESSAGE$
|
|
22861
|
+
message: MESSAGE$43
|
|
22110
22862
|
});
|
|
22111
22863
|
}
|
|
22112
22864
|
} };
|
|
@@ -22726,7 +23478,7 @@ const jsxPropsNoSpreadMulti = defineRule({
|
|
|
22726
23478
|
});
|
|
22727
23479
|
//#endregion
|
|
22728
23480
|
//#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
|
|
22729
|
-
const MESSAGE$
|
|
23481
|
+
const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
|
|
22730
23482
|
const resolveSettings$25 = (settings) => {
|
|
22731
23483
|
const reactDoctor = settings?.["react-doctor"];
|
|
22732
23484
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
|
|
@@ -22769,7 +23521,7 @@ const jsxPropsNoSpreading = defineRule({
|
|
|
22769
23521
|
}
|
|
22770
23522
|
context.report({
|
|
22771
23523
|
node: attribute,
|
|
22772
|
-
message: MESSAGE$
|
|
23524
|
+
message: MESSAGE$42
|
|
22773
23525
|
});
|
|
22774
23526
|
didReportInFile = true;
|
|
22775
23527
|
return;
|
|
@@ -23045,7 +23797,7 @@ const labelHasAssociatedControl = defineRule({
|
|
|
23045
23797
|
});
|
|
23046
23798
|
//#endregion
|
|
23047
23799
|
//#region src/plugin/rules/a11y/lang.ts
|
|
23048
|
-
const MESSAGE$
|
|
23800
|
+
const MESSAGE$41 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
|
|
23049
23801
|
const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
|
|
23050
23802
|
"aa",
|
|
23051
23803
|
"ab",
|
|
@@ -23525,7 +24277,7 @@ const lang = defineRule({
|
|
|
23525
24277
|
if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
|
|
23526
24278
|
context.report({
|
|
23527
24279
|
node: langAttr,
|
|
23528
|
-
message: MESSAGE$
|
|
24280
|
+
message: MESSAGE$41
|
|
23529
24281
|
});
|
|
23530
24282
|
return;
|
|
23531
24283
|
}
|
|
@@ -23534,7 +24286,7 @@ const lang = defineRule({
|
|
|
23534
24286
|
if (value === null) return;
|
|
23535
24287
|
if (!isValidLangTag(value)) context.report({
|
|
23536
24288
|
node: langAttr,
|
|
23537
|
-
message: MESSAGE$
|
|
24289
|
+
message: MESSAGE$41
|
|
23538
24290
|
});
|
|
23539
24291
|
} })
|
|
23540
24292
|
});
|
|
@@ -23585,7 +24337,7 @@ const mdxSsrExecutionRisk = defineRule({
|
|
|
23585
24337
|
});
|
|
23586
24338
|
//#endregion
|
|
23587
24339
|
//#region src/plugin/rules/a11y/media-has-caption.ts
|
|
23588
|
-
const MESSAGE$
|
|
24340
|
+
const MESSAGE$40 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
|
|
23589
24341
|
const DEFAULT_AUDIO = ["audio"];
|
|
23590
24342
|
const DEFAULT_VIDEO = ["video"];
|
|
23591
24343
|
const DEFAULT_TRACK = ["track"];
|
|
@@ -23674,7 +24426,7 @@ const mediaHasCaption = defineRule({
|
|
|
23674
24426
|
if (!parent || !isNodeOfType(parent, "JSXElement")) {
|
|
23675
24427
|
context.report({
|
|
23676
24428
|
node: node.name,
|
|
23677
|
-
message: MESSAGE$
|
|
24429
|
+
message: MESSAGE$40
|
|
23678
24430
|
});
|
|
23679
24431
|
return;
|
|
23680
24432
|
}
|
|
@@ -23693,7 +24445,7 @@ const mediaHasCaption = defineRule({
|
|
|
23693
24445
|
return kindValue.value.toLowerCase() === "captions";
|
|
23694
24446
|
})) context.report({
|
|
23695
24447
|
node: node.name,
|
|
23696
|
-
message: MESSAGE$
|
|
24448
|
+
message: MESSAGE$40
|
|
23697
24449
|
});
|
|
23698
24450
|
} };
|
|
23699
24451
|
}
|
|
@@ -23777,32 +24529,6 @@ const hasDirective = (programNode, directive) => {
|
|
|
23777
24529
|
return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
|
|
23778
24530
|
};
|
|
23779
24531
|
//#endregion
|
|
23780
|
-
//#region src/plugin/utils/unwrap-object-integrity-expression.ts
|
|
23781
|
-
const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
|
|
23782
|
-
"freeze",
|
|
23783
|
-
"seal",
|
|
23784
|
-
"preventExtensions"
|
|
23785
|
-
]);
|
|
23786
|
-
const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
|
|
23787
|
-
const getObjectIntegrityMethodName = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
23788
|
-
const expression = stripParenExpression(node);
|
|
23789
|
-
if (!isNodeOfType(expression, "CallExpression")) return null;
|
|
23790
|
-
const callee = stripParenExpression(expression.callee);
|
|
23791
|
-
const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : callee;
|
|
23792
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !scopes.isGlobalReference(receiver) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) return null;
|
|
23793
|
-
return callee.property.name;
|
|
23794
|
-
};
|
|
23795
|
-
const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
23796
|
-
let expression = stripParenExpression(node);
|
|
23797
|
-
while (isNodeOfType(expression, "CallExpression")) {
|
|
23798
|
-
if (!getObjectIntegrityMethodName(expression, scopes, methodNames)) break;
|
|
23799
|
-
const wrappedExpression = expression.arguments[0];
|
|
23800
|
-
if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
|
|
23801
|
-
expression = stripParenExpression(wrappedExpression);
|
|
23802
|
-
}
|
|
23803
|
-
return expression;
|
|
23804
|
-
};
|
|
23805
|
-
//#endregion
|
|
23806
24532
|
//#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
|
|
23807
24533
|
const nextjsAsyncClientComponent = defineRule({
|
|
23808
24534
|
id: "nextjs-async-client-component",
|
|
@@ -25236,7 +25962,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
25236
25962
|
});
|
|
25237
25963
|
//#endregion
|
|
25238
25964
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
25239
|
-
const MESSAGE$
|
|
25965
|
+
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
25240
25966
|
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
25241
25967
|
const noAccessKey = defineRule({
|
|
25242
25968
|
id: "no-access-key",
|
|
@@ -25255,7 +25981,7 @@ const noAccessKey = defineRule({
|
|
|
25255
25981
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
25256
25982
|
context.report({
|
|
25257
25983
|
node: accessKey,
|
|
25258
|
-
message: MESSAGE$
|
|
25984
|
+
message: MESSAGE$39
|
|
25259
25985
|
});
|
|
25260
25986
|
return;
|
|
25261
25987
|
}
|
|
@@ -25265,7 +25991,7 @@ const noAccessKey = defineRule({
|
|
|
25265
25991
|
if (isUndefinedIdentifier(expression)) return;
|
|
25266
25992
|
context.report({
|
|
25267
25993
|
node: accessKey,
|
|
25268
|
-
message: MESSAGE$
|
|
25994
|
+
message: MESSAGE$39
|
|
25269
25995
|
});
|
|
25270
25996
|
}
|
|
25271
25997
|
} };
|
|
@@ -26142,7 +26868,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
26142
26868
|
};
|
|
26143
26869
|
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
26144
26870
|
if (!setterRef.resolved) return false;
|
|
26145
|
-
const componentFunction = findEnclosingFunction(effectNode);
|
|
26871
|
+
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
26146
26872
|
if (!componentFunction) return false;
|
|
26147
26873
|
for (const reference of setterRef.resolved.references) {
|
|
26148
26874
|
if (reference.init) continue;
|
|
@@ -27120,7 +27846,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
27120
27846
|
});
|
|
27121
27847
|
//#endregion
|
|
27122
27848
|
//#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
|
|
27123
|
-
const MESSAGE$
|
|
27849
|
+
const MESSAGE$38 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
|
|
27124
27850
|
const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
27125
27851
|
"button",
|
|
27126
27852
|
"embed",
|
|
@@ -27232,7 +27958,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
27232
27958
|
const isImplicitlyFocusable = isNativelyFocusable(tag, node);
|
|
27233
27959
|
if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
|
|
27234
27960
|
node: ariaHidden,
|
|
27235
|
-
message: MESSAGE$
|
|
27961
|
+
message: MESSAGE$38
|
|
27236
27962
|
});
|
|
27237
27963
|
} })
|
|
27238
27964
|
});
|
|
@@ -28154,7 +28880,7 @@ const isAllLiteralArrayExpression = (node) => {
|
|
|
28154
28880
|
};
|
|
28155
28881
|
//#endregion
|
|
28156
28882
|
//#region src/plugin/rules/react-builtins/no-array-index-key.ts
|
|
28157
|
-
const MESSAGE$
|
|
28883
|
+
const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
|
|
28158
28884
|
const SECOND_INDEX_METHODS = new Set([
|
|
28159
28885
|
"every",
|
|
28160
28886
|
"filter",
|
|
@@ -28273,14 +28999,14 @@ const noArrayIndexKey = defineRule({
|
|
|
28273
28999
|
if (propName !== "key") continue;
|
|
28274
29000
|
if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
|
|
28275
29001
|
node: property,
|
|
28276
|
-
message: MESSAGE$
|
|
29002
|
+
message: MESSAGE$37
|
|
28277
29003
|
});
|
|
28278
29004
|
}
|
|
28279
29005
|
} })
|
|
28280
29006
|
});
|
|
28281
29007
|
//#endregion
|
|
28282
29008
|
//#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
|
|
28283
|
-
const MESSAGE$
|
|
29009
|
+
const MESSAGE$36 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
|
|
28284
29010
|
const noAsyncEffectCallback = defineRule({
|
|
28285
29011
|
id: "no-async-effect-callback",
|
|
28286
29012
|
title: "Async effect callback",
|
|
@@ -28298,13 +29024,13 @@ const noAsyncEffectCallback = defineRule({
|
|
|
28298
29024
|
if (!callback.async) return;
|
|
28299
29025
|
context.report({
|
|
28300
29026
|
node: callback,
|
|
28301
|
-
message: MESSAGE$
|
|
29027
|
+
message: MESSAGE$36
|
|
28302
29028
|
});
|
|
28303
29029
|
} })
|
|
28304
29030
|
});
|
|
28305
29031
|
//#endregion
|
|
28306
29032
|
//#region src/plugin/rules/a11y/no-autofocus.ts
|
|
28307
|
-
const MESSAGE$
|
|
29033
|
+
const MESSAGE$35 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
|
|
28308
29034
|
const resolveSettings$21 = (settings) => {
|
|
28309
29035
|
const reactDoctor = settings?.["react-doctor"];
|
|
28310
29036
|
return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
|
|
@@ -28401,7 +29127,7 @@ const noAutofocus = defineRule({
|
|
|
28401
29127
|
if (isConditionallyRendered(node)) return;
|
|
28402
29128
|
context.report({
|
|
28403
29129
|
node: autoFocusAttribute,
|
|
28404
|
-
message: MESSAGE$
|
|
29130
|
+
message: MESSAGE$35
|
|
28405
29131
|
});
|
|
28406
29132
|
} };
|
|
28407
29133
|
}
|
|
@@ -28915,138 +29641,6 @@ const noBarrelImport = defineRule({
|
|
|
28915
29641
|
}
|
|
28916
29642
|
});
|
|
28917
29643
|
//#endregion
|
|
28918
|
-
//#region src/plugin/utils/has-static-property-write-before.ts
|
|
28919
|
-
const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
28920
|
-
const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
|
|
28921
|
-
"CatchClause",
|
|
28922
|
-
"ConditionalExpression",
|
|
28923
|
-
"DoWhileStatement",
|
|
28924
|
-
"ForInStatement",
|
|
28925
|
-
"ForOfStatement",
|
|
28926
|
-
"ForStatement",
|
|
28927
|
-
"IfStatement",
|
|
28928
|
-
"LogicalExpression",
|
|
28929
|
-
"SwitchCase",
|
|
28930
|
-
"SwitchStatement",
|
|
28931
|
-
"TryStatement",
|
|
28932
|
-
"WhileStatement"
|
|
28933
|
-
]);
|
|
28934
|
-
const getResolvedStaticPropertyName = (memberExpression, scopes) => {
|
|
28935
|
-
if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
|
|
28936
|
-
const directPropertyName = getStaticPropertyName(memberExpression);
|
|
28937
|
-
if (directPropertyName || !memberExpression.computed) return directPropertyName;
|
|
28938
|
-
const property = stripParenExpression(memberExpression.property);
|
|
28939
|
-
if (!isNodeOfType(property, "Identifier")) return null;
|
|
28940
|
-
const propertySymbol = resolveConstIdentifierAlias(property, scopes);
|
|
28941
|
-
const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
|
|
28942
|
-
return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
|
|
28943
|
-
};
|
|
28944
|
-
const collectScopeSymbols = (scope, symbols) => {
|
|
28945
|
-
symbols.push(...scope.symbols);
|
|
28946
|
-
for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
|
|
28947
|
-
};
|
|
28948
|
-
const getEquivalentSymbols = (identifier, scopes) => {
|
|
28949
|
-
const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
28950
|
-
if (!rootSymbol) return [];
|
|
28951
|
-
let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
|
|
28952
|
-
if (!symbolsByRootId) {
|
|
28953
|
-
symbolsByRootId = /* @__PURE__ */ new Map();
|
|
28954
|
-
equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
|
|
28955
|
-
}
|
|
28956
|
-
const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
|
|
28957
|
-
if (cachedSymbols) return cachedSymbols;
|
|
28958
|
-
const allSymbols = [];
|
|
28959
|
-
collectScopeSymbols(scopes.rootScope, allSymbols);
|
|
28960
|
-
const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
|
|
28961
|
-
symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
|
|
28962
|
-
return equivalentSymbols;
|
|
28963
|
-
};
|
|
28964
|
-
const findExecutionBoundary = (node) => {
|
|
28965
|
-
let current = node;
|
|
28966
|
-
while (current) {
|
|
28967
|
-
if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
|
|
28968
|
-
current = current.parent ?? null;
|
|
28969
|
-
}
|
|
28970
|
-
return null;
|
|
28971
|
-
};
|
|
28972
|
-
const isOnUnconditionalPath = (node, boundary) => {
|
|
28973
|
-
let current = node.parent ?? null;
|
|
28974
|
-
while (current && current !== boundary) {
|
|
28975
|
-
if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
|
|
28976
|
-
current = current.parent ?? null;
|
|
28977
|
-
}
|
|
28978
|
-
return current === boundary;
|
|
28979
|
-
};
|
|
28980
|
-
const findFunctionBindingIdentifier = (functionNode) => {
|
|
28981
|
-
if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
|
|
28982
|
-
const parent = functionNode.parent;
|
|
28983
|
-
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
28984
|
-
return null;
|
|
28985
|
-
};
|
|
28986
|
-
const findDirectCall = (identifier) => {
|
|
28987
|
-
let callee = identifier;
|
|
28988
|
-
let parent = callee.parent;
|
|
28989
|
-
while (parent && stripParenExpression(parent) === identifier) {
|
|
28990
|
-
callee = parent;
|
|
28991
|
-
parent = callee.parent;
|
|
28992
|
-
}
|
|
28993
|
-
return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
28994
|
-
};
|
|
28995
|
-
const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
28996
|
-
if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
|
|
28997
|
-
visitedFunctionNodes.add(functionNode);
|
|
28998
|
-
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
28999
|
-
if (!referenceBoundary) return false;
|
|
29000
|
-
const invocationCalls = [];
|
|
29001
|
-
const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
|
|
29002
|
-
if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
|
|
29003
|
-
const call = findDirectCall(reference.identifier);
|
|
29004
|
-
if (call) invocationCalls.push(call);
|
|
29005
|
-
}
|
|
29006
|
-
else {
|
|
29007
|
-
const call = findDirectCall(functionNode);
|
|
29008
|
-
if (call) invocationCalls.push(call);
|
|
29009
|
-
}
|
|
29010
|
-
return invocationCalls.some((call) => {
|
|
29011
|
-
if (call.range[0] >= referenceNode.range[0]) return false;
|
|
29012
|
-
const callBoundary = findExecutionBoundary(call);
|
|
29013
|
-
if (!callBoundary) return false;
|
|
29014
|
-
if (callBoundary === referenceBoundary) return true;
|
|
29015
|
-
if (!isFunctionLike$2(callBoundary)) return false;
|
|
29016
|
-
return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
|
|
29017
|
-
});
|
|
29018
|
-
};
|
|
29019
|
-
const isMemberWriteTarget = (memberExpression) => {
|
|
29020
|
-
const parent = memberExpression.parent;
|
|
29021
|
-
if (!parent) return false;
|
|
29022
|
-
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
|
|
29023
|
-
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
|
|
29024
|
-
return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
|
|
29025
|
-
};
|
|
29026
|
-
const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
29027
|
-
let parent = reference.identifier.parent;
|
|
29028
|
-
while (parent && stripParenExpression(parent) === reference.identifier) parent = parent.parent;
|
|
29029
|
-
if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== reference.identifier || getResolvedStaticPropertyName(parent, scopes) !== propertyName || !isMemberWriteTarget(parent)) return false;
|
|
29030
|
-
const writeBoundary = findExecutionBoundary(parent);
|
|
29031
|
-
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
29032
|
-
if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(parent, writeBoundary)) return false;
|
|
29033
|
-
if (writeBoundary === referenceBoundary) return parent.range[0] < referenceNode.range[0];
|
|
29034
|
-
return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
|
|
29035
|
-
});
|
|
29036
|
-
const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
29037
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
29038
|
-
return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
|
|
29039
|
-
};
|
|
29040
|
-
//#endregion
|
|
29041
|
-
//#region src/plugin/utils/has-symbol-write-before.ts
|
|
29042
|
-
const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
|
|
29043
|
-
if (reference.flag === "read") return false;
|
|
29044
|
-
const writeFunction = findEnclosingFunction(reference.identifier);
|
|
29045
|
-
if (writeFunction === findEnclosingFunction(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
|
|
29046
|
-
if (!writeFunction) return true;
|
|
29047
|
-
return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
|
|
29048
|
-
});
|
|
29049
|
-
//#endregion
|
|
29050
29644
|
//#region src/plugin/utils/has-stable-call-target.ts
|
|
29051
29645
|
const hasStableCallTarget = (callExpression, scopes) => {
|
|
29052
29646
|
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -29374,7 +29968,7 @@ const noChainStateUpdates = defineRule({
|
|
|
29374
29968
|
});
|
|
29375
29969
|
//#endregion
|
|
29376
29970
|
//#region src/plugin/rules/react-builtins/no-children-prop.ts
|
|
29377
|
-
const MESSAGE$
|
|
29971
|
+
const MESSAGE$34 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
|
|
29378
29972
|
const noChildrenProp = defineRule({
|
|
29379
29973
|
id: "no-children-prop",
|
|
29380
29974
|
title: "Children passed as a prop",
|
|
@@ -29386,7 +29980,7 @@ const noChildrenProp = defineRule({
|
|
|
29386
29980
|
if (node.name.name !== "children") return;
|
|
29387
29981
|
context.report({
|
|
29388
29982
|
node: node.name,
|
|
29389
|
-
message: MESSAGE$
|
|
29983
|
+
message: MESSAGE$34
|
|
29390
29984
|
});
|
|
29391
29985
|
},
|
|
29392
29986
|
CallExpression(node) {
|
|
@@ -29399,7 +29993,7 @@ const noChildrenProp = defineRule({
|
|
|
29399
29993
|
const propertyKey = property.key;
|
|
29400
29994
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
|
|
29401
29995
|
node: propertyKey,
|
|
29402
|
-
message: MESSAGE$
|
|
29996
|
+
message: MESSAGE$34
|
|
29403
29997
|
});
|
|
29404
29998
|
}
|
|
29405
29999
|
}
|
|
@@ -29407,7 +30001,7 @@ const noChildrenProp = defineRule({
|
|
|
29407
30001
|
});
|
|
29408
30002
|
//#endregion
|
|
29409
30003
|
//#region src/plugin/rules/react-builtins/no-clone-element.ts
|
|
29410
|
-
const MESSAGE$
|
|
30004
|
+
const MESSAGE$33 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
|
|
29411
30005
|
const noCloneElement = defineRule({
|
|
29412
30006
|
id: "no-clone-element",
|
|
29413
30007
|
title: "cloneElement makes child props fragile",
|
|
@@ -29420,7 +30014,7 @@ const noCloneElement = defineRule({
|
|
|
29420
30014
|
if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
|
|
29421
30015
|
if (isImportedFromModule(node, "cloneElement", "react")) context.report({
|
|
29422
30016
|
node: callee,
|
|
29423
|
-
message: MESSAGE$
|
|
30017
|
+
message: MESSAGE$33
|
|
29424
30018
|
});
|
|
29425
30019
|
return;
|
|
29426
30020
|
}
|
|
@@ -29433,7 +30027,7 @@ const noCloneElement = defineRule({
|
|
|
29433
30027
|
if (!isImportedFromModule(node, callee.object.name, "react")) return;
|
|
29434
30028
|
context.report({
|
|
29435
30029
|
node: callee,
|
|
29436
|
-
message: MESSAGE$
|
|
30030
|
+
message: MESSAGE$33
|
|
29437
30031
|
});
|
|
29438
30032
|
}
|
|
29439
30033
|
} })
|
|
@@ -29453,7 +30047,7 @@ const getCallMethodName = (callee) => {
|
|
|
29453
30047
|
};
|
|
29454
30048
|
//#endregion
|
|
29455
30049
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
29456
|
-
const MESSAGE$
|
|
30050
|
+
const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
29457
30051
|
const CONTEXT_MODULES = [
|
|
29458
30052
|
"react",
|
|
29459
30053
|
"use-context-selector",
|
|
@@ -29501,13 +30095,13 @@ const noCreateContextInRender = defineRule({
|
|
|
29501
30095
|
if (!componentOrHookName) return;
|
|
29502
30096
|
context.report({
|
|
29503
30097
|
node,
|
|
29504
|
-
message: `${MESSAGE$
|
|
30098
|
+
message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
|
|
29505
30099
|
});
|
|
29506
30100
|
} })
|
|
29507
30101
|
});
|
|
29508
30102
|
//#endregion
|
|
29509
30103
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
29510
|
-
const MESSAGE$
|
|
30104
|
+
const MESSAGE$31 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
|
|
29511
30105
|
const isUseMemoCallbackArgument = (functionNode) => {
|
|
29512
30106
|
const parent = functionNode.parent;
|
|
29513
30107
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
@@ -29515,8 +30109,8 @@ const isUseMemoCallbackArgument = (functionNode) => {
|
|
|
29515
30109
|
return isReactFunctionCall(parent, "useMemo");
|
|
29516
30110
|
};
|
|
29517
30111
|
const findEnclosingRenderFunction = (node) => {
|
|
29518
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
29519
|
-
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
30112
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
30113
|
+
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
29520
30114
|
return enclosingFunction;
|
|
29521
30115
|
};
|
|
29522
30116
|
const noCreateRefInFunctionComponent = defineRule({
|
|
@@ -29537,7 +30131,7 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
29537
30131
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
|
|
29538
30132
|
context.report({
|
|
29539
30133
|
node,
|
|
29540
|
-
message: MESSAGE$
|
|
30134
|
+
message: MESSAGE$31
|
|
29541
30135
|
});
|
|
29542
30136
|
} })
|
|
29543
30137
|
});
|
|
@@ -29677,7 +30271,7 @@ const noCreateStoreInRender = defineRule({
|
|
|
29677
30271
|
});
|
|
29678
30272
|
//#endregion
|
|
29679
30273
|
//#region src/plugin/rules/react-builtins/no-danger.ts
|
|
29680
|
-
const MESSAGE$
|
|
30274
|
+
const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
|
|
29681
30275
|
const noDanger = defineRule({
|
|
29682
30276
|
id: "no-danger",
|
|
29683
30277
|
title: "Raw HTML injection can run unsafe markup",
|
|
@@ -29691,7 +30285,7 @@ const noDanger = defineRule({
|
|
|
29691
30285
|
if (!propAttribute) return;
|
|
29692
30286
|
context.report({
|
|
29693
30287
|
node: propAttribute.name,
|
|
29694
|
-
message: MESSAGE$
|
|
30288
|
+
message: MESSAGE$30
|
|
29695
30289
|
});
|
|
29696
30290
|
},
|
|
29697
30291
|
CallExpression(node) {
|
|
@@ -29703,7 +30297,7 @@ const noDanger = defineRule({
|
|
|
29703
30297
|
const propertyKey = property.key;
|
|
29704
30298
|
if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
|
|
29705
30299
|
node: propertyKey,
|
|
29706
|
-
message: MESSAGE$
|
|
30300
|
+
message: MESSAGE$30
|
|
29707
30301
|
});
|
|
29708
30302
|
}
|
|
29709
30303
|
}
|
|
@@ -29726,7 +30320,7 @@ const isMeaningfulJsxChild = (child) => {
|
|
|
29726
30320
|
};
|
|
29727
30321
|
//#endregion
|
|
29728
30322
|
//#region src/plugin/rules/react-builtins/no-danger-with-children.ts
|
|
29729
|
-
const MESSAGE$
|
|
30323
|
+
const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
|
|
29730
30324
|
const mergePropsShape = (target, source) => {
|
|
29731
30325
|
target.hasDangerously ||= source.hasDangerously;
|
|
29732
30326
|
target.hasChildren ||= source.hasChildren;
|
|
@@ -29801,7 +30395,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29801
30395
|
if (!hasChildrenProp && !hasNestedChildren) return;
|
|
29802
30396
|
if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
|
|
29803
30397
|
node: opening,
|
|
29804
|
-
message: MESSAGE$
|
|
30398
|
+
message: MESSAGE$29
|
|
29805
30399
|
});
|
|
29806
30400
|
},
|
|
29807
30401
|
CallExpression(node) {
|
|
@@ -29814,7 +30408,7 @@ const noDangerWithChildren = defineRule({
|
|
|
29814
30408
|
const positionalChildren = node.arguments.slice(2);
|
|
29815
30409
|
if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
|
|
29816
30410
|
node,
|
|
29817
|
-
message: MESSAGE$
|
|
30411
|
+
message: MESSAGE$29
|
|
29818
30412
|
});
|
|
29819
30413
|
}
|
|
29820
30414
|
})
|
|
@@ -30567,7 +31161,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
30567
31161
|
const isEffectDrivenResync = (useStateCall) => {
|
|
30568
31162
|
const setterName = getStateSetterName(useStateCall);
|
|
30569
31163
|
if (!setterName) return false;
|
|
30570
|
-
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31164
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
30571
31165
|
if (!componentFunction) return false;
|
|
30572
31166
|
let isExempt = false;
|
|
30573
31167
|
walkAst(componentFunction, (child) => {
|
|
@@ -30671,7 +31265,7 @@ const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
|
|
|
30671
31265
|
const setterName = getStateSetterName(useStateCall);
|
|
30672
31266
|
if (!setterName) return false;
|
|
30673
31267
|
const stateValueName = getStateValueName(useStateCall);
|
|
30674
|
-
const componentFunction = findEnclosingFunction(useStateCall);
|
|
31268
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
30675
31269
|
if (!componentFunction) return false;
|
|
30676
31270
|
let isExempt = false;
|
|
30677
31271
|
walkAst(componentFunction, (child) => {
|
|
@@ -30717,7 +31311,7 @@ const noDerivedUseState = defineRule({
|
|
|
30717
31311
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
30718
31312
|
if (isEffectDrivenResync(node)) return;
|
|
30719
31313
|
if (isNextjsDataFetchingPage(node)) return;
|
|
30720
|
-
const componentFunction = findEnclosingFunction(node);
|
|
31314
|
+
const componentFunction = findEnclosingFunction$1(node);
|
|
30721
31315
|
if (componentFunction) {
|
|
30722
31316
|
if (isDefaultedDestructuredProp(componentFunction, propName)) return;
|
|
30723
31317
|
if (isDraftCommittedToParent(componentFunction, getStateValueName(node), propStackTracker.isPropName)) return;
|
|
@@ -30779,7 +31373,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
30779
31373
|
//#endregion
|
|
30780
31374
|
//#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
|
|
30781
31375
|
const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
|
|
30782
|
-
const MESSAGE$
|
|
31376
|
+
const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
|
|
30783
31377
|
const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
|
|
30784
31378
|
const getEnclosingLifecycleFunction = (setStateCall) => {
|
|
30785
31379
|
let ancestor = setStateCall.parent;
|
|
@@ -30907,7 +31501,7 @@ const noDidMountSetState = defineRule({
|
|
|
30907
31501
|
}
|
|
30908
31502
|
context.report({
|
|
30909
31503
|
node: node.callee,
|
|
30910
|
-
message: MESSAGE$
|
|
31504
|
+
message: MESSAGE$28
|
|
30911
31505
|
});
|
|
30912
31506
|
} };
|
|
30913
31507
|
}
|
|
@@ -30915,7 +31509,7 @@ const noDidMountSetState = defineRule({
|
|
|
30915
31509
|
//#endregion
|
|
30916
31510
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
30917
31511
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
30918
|
-
const MESSAGE$
|
|
31512
|
+
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
30919
31513
|
const EQUALITY_OPERATORS = new Set([
|
|
30920
31514
|
"==",
|
|
30921
31515
|
"===",
|
|
@@ -31089,7 +31683,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
31089
31683
|
if (isInsideDiffGuard(node)) return;
|
|
31090
31684
|
context.report({
|
|
31091
31685
|
node: node.callee,
|
|
31092
|
-
message: MESSAGE$
|
|
31686
|
+
message: MESSAGE$27
|
|
31093
31687
|
});
|
|
31094
31688
|
} };
|
|
31095
31689
|
}
|
|
@@ -31112,7 +31706,7 @@ const isStateMemberExpression = (node) => {
|
|
|
31112
31706
|
};
|
|
31113
31707
|
//#endregion
|
|
31114
31708
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
31115
|
-
const MESSAGE$
|
|
31709
|
+
const MESSAGE$26 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
|
|
31116
31710
|
const shouldIgnoreMutation = (node) => {
|
|
31117
31711
|
let isConstructor = false;
|
|
31118
31712
|
let isInsideCallExpression = false;
|
|
@@ -31134,7 +31728,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
|
|
|
31134
31728
|
if (shouldIgnoreMutation(reportNode)) return;
|
|
31135
31729
|
context.report({
|
|
31136
31730
|
node: reportNode,
|
|
31137
|
-
message: MESSAGE$
|
|
31731
|
+
message: MESSAGE$26
|
|
31138
31732
|
});
|
|
31139
31733
|
};
|
|
31140
31734
|
const noDirectMutationState = defineRule({
|
|
@@ -31485,7 +32079,7 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
31485
32079
|
});
|
|
31486
32080
|
//#endregion
|
|
31487
32081
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
31488
|
-
const MESSAGE$
|
|
32082
|
+
const MESSAGE$25 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
31489
32083
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
31490
32084
|
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
31491
32085
|
const noDocumentWrite = defineRule({
|
|
@@ -31502,7 +32096,7 @@ const noDocumentWrite = defineRule({
|
|
|
31502
32096
|
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
31503
32097
|
context.report({
|
|
31504
32098
|
node,
|
|
31505
|
-
message: MESSAGE$
|
|
32099
|
+
message: MESSAGE$25
|
|
31506
32100
|
});
|
|
31507
32101
|
} })
|
|
31508
32102
|
});
|
|
@@ -32555,7 +33149,7 @@ const isFunctionUsedOutsideHandlers = (analysis, functionNode, componentFunction
|
|
|
32555
33149
|
if (isInsideInlineEventHandler(identifier, componentFunction)) return false;
|
|
32556
33150
|
const parent = identifier.parent;
|
|
32557
33151
|
if (parent && isNodeOfType(parent, "CallExpression") && parent.callee === identifier) {
|
|
32558
|
-
if (findEnclosingFunction(parent) === functionNode) return false;
|
|
33152
|
+
if (findEnclosingFunction$1(parent) === functionNode) return false;
|
|
32559
33153
|
if (isInsideProvenEventHandler(analysis, parent, componentFunction, false)) return false;
|
|
32560
33154
|
}
|
|
32561
33155
|
return true;
|
|
@@ -32601,7 +33195,7 @@ const isStateWrittenOnlyFromEventHandlers = (analysis, stateReference) => {
|
|
|
32601
33195
|
if (!setterVariable) return false;
|
|
32602
33196
|
const stateDeclarator = getUseStateDecl(analysis, stateReference);
|
|
32603
33197
|
if (!stateDeclarator) return false;
|
|
32604
|
-
const componentFunction = findEnclosingFunction(stateDeclarator);
|
|
33198
|
+
const componentFunction = findEnclosingFunction$1(stateDeclarator);
|
|
32605
33199
|
if (!componentFunction) return false;
|
|
32606
33200
|
let hasWriter = false;
|
|
32607
33201
|
for (const reference of setterVariable.references) {
|
|
@@ -33663,8 +34257,8 @@ const isAwaitedInFunction = (request, functionNode) => {
|
|
|
33663
34257
|
return false;
|
|
33664
34258
|
};
|
|
33665
34259
|
const isCompletionSinkForRequest = (completionSink, request) => {
|
|
33666
|
-
const requestFunction = findEnclosingFunction(request);
|
|
33667
|
-
const completionSinkFunction = findEnclosingFunction(completionSink);
|
|
34260
|
+
const requestFunction = findEnclosingFunction$1(request);
|
|
34261
|
+
const completionSinkFunction = findEnclosingFunction$1(completionSink);
|
|
33668
34262
|
if (!requestFunction || !completionSinkFunction) return false;
|
|
33669
34263
|
if (requestFunction === completionSinkFunction) {
|
|
33670
34264
|
if (!isAwaitedInFunction(request, requestFunction)) return false;
|
|
@@ -33720,7 +34314,7 @@ const ALLOWED_NAMESPACES = new Set([
|
|
|
33720
34314
|
"ReactDOM",
|
|
33721
34315
|
"ReactDom"
|
|
33722
34316
|
]);
|
|
33723
|
-
const MESSAGE$
|
|
34317
|
+
const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
|
|
33724
34318
|
const noFindDomNode = defineRule({
|
|
33725
34319
|
id: "no-find-dom-node",
|
|
33726
34320
|
title: "findDOMNode breaks component encapsulation",
|
|
@@ -33731,7 +34325,7 @@ const noFindDomNode = defineRule({
|
|
|
33731
34325
|
if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
|
|
33732
34326
|
if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
|
|
33733
34327
|
node: callee,
|
|
33734
|
-
message: MESSAGE$
|
|
34328
|
+
message: MESSAGE$24
|
|
33735
34329
|
});
|
|
33736
34330
|
return;
|
|
33737
34331
|
}
|
|
@@ -33742,7 +34336,7 @@ const noFindDomNode = defineRule({
|
|
|
33742
34336
|
if (callee.property.name !== "findDOMNode") return;
|
|
33743
34337
|
context.report({
|
|
33744
34338
|
node: callee.property,
|
|
33745
|
-
message: MESSAGE$
|
|
34339
|
+
message: MESSAGE$24
|
|
33746
34340
|
});
|
|
33747
34341
|
}
|
|
33748
34342
|
} })
|
|
@@ -33951,13 +34545,6 @@ const isPublishedLibraryPackage = (filename) => {
|
|
|
33951
34545
|
return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
|
|
33952
34546
|
};
|
|
33953
34547
|
//#endregion
|
|
33954
|
-
//#region src/plugin/utils/is-type-only-import.ts
|
|
33955
|
-
const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
|
|
33956
|
-
if (!specifiers || specifiers.length === 0) return false;
|
|
33957
|
-
return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
|
|
33958
|
-
};
|
|
33959
|
-
const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
|
|
33960
|
-
//#endregion
|
|
33961
34548
|
//#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
|
|
33962
34549
|
const isLibraryDevPage = (filename) => {
|
|
33963
34550
|
if (!filename) return false;
|
|
@@ -34521,7 +35108,7 @@ const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
|
|
|
34521
35108
|
return attribute ? !isEventHandlerAttribute(attribute) : true;
|
|
34522
35109
|
}
|
|
34523
35110
|
if (isNodeOfType(parentNode, "ReturnStatement")) {
|
|
34524
|
-
if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
|
|
35111
|
+
if (findEnclosingFunction$1(parentNode) === componentOrHookNode) return true;
|
|
34525
35112
|
}
|
|
34526
35113
|
if (parentNode === componentOrHookNode) return isFunctionLike$2(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
|
|
34527
35114
|
if (isFunctionLike$2(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
|
|
@@ -34644,7 +35231,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34644
35231
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
34645
35232
|
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
34646
35233
|
if (!componentOrHookNode) return;
|
|
34647
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
35234
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
34648
35235
|
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
|
|
34649
35236
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
34650
35237
|
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
@@ -34656,7 +35243,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
34656
35243
|
});
|
|
34657
35244
|
//#endregion
|
|
34658
35245
|
//#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
|
|
34659
|
-
const MESSAGE$
|
|
35246
|
+
const MESSAGE$23 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
|
|
34660
35247
|
const noImgLazyWithHighFetchpriority = defineRule({
|
|
34661
35248
|
id: "no-img-lazy-with-high-fetchpriority",
|
|
34662
35249
|
title: "Lazy image with high fetchPriority",
|
|
@@ -34670,7 +35257,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
34670
35257
|
if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
|
|
34671
35258
|
context.report({
|
|
34672
35259
|
node: node.name,
|
|
34673
|
-
message: MESSAGE$
|
|
35260
|
+
message: MESSAGE$23
|
|
34674
35261
|
});
|
|
34675
35262
|
} })
|
|
34676
35263
|
});
|
|
@@ -34859,7 +35446,7 @@ const noImpureStateUpdater = defineRule({
|
|
|
34859
35446
|
});
|
|
34860
35447
|
//#endregion
|
|
34861
35448
|
//#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
|
|
34862
|
-
const MESSAGE$
|
|
35449
|
+
const MESSAGE$22 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
|
|
34863
35450
|
const REACT_USE_REF_OPTIONS = {
|
|
34864
35451
|
allowGlobalReactNamespace: false,
|
|
34865
35452
|
allowUnboundBareCalls: false
|
|
@@ -34960,7 +35547,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34960
35547
|
if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
|
|
34961
35548
|
context.report({
|
|
34962
35549
|
node: indeterminateAttribute,
|
|
34963
|
-
message: MESSAGE$
|
|
35550
|
+
message: MESSAGE$22
|
|
34964
35551
|
});
|
|
34965
35552
|
},
|
|
34966
35553
|
CallExpression(node) {
|
|
@@ -34969,7 +35556,7 @@ const noIndeterminateAttribute = defineRule({
|
|
|
34969
35556
|
if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
|
|
34970
35557
|
context.report({
|
|
34971
35558
|
node,
|
|
34972
|
-
message: MESSAGE$
|
|
35559
|
+
message: MESSAGE$22
|
|
34973
35560
|
});
|
|
34974
35561
|
}
|
|
34975
35562
|
};
|
|
@@ -35092,10 +35679,10 @@ const isInsideInstanceField = (node) => {
|
|
|
35092
35679
|
return false;
|
|
35093
35680
|
};
|
|
35094
35681
|
const isOneShotModuleInitialization = (node, context) => {
|
|
35095
|
-
let functionNode = findEnclosingFunction(node);
|
|
35682
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
35096
35683
|
while (functionNode) {
|
|
35097
35684
|
if (!executesDuringRender(functionNode, context.scopes)) return false;
|
|
35098
|
-
functionNode = findEnclosingFunction(functionNode);
|
|
35685
|
+
functionNode = findEnclosingFunction$1(functionNode);
|
|
35099
35686
|
}
|
|
35100
35687
|
return !isInsideInstanceField(node);
|
|
35101
35688
|
};
|
|
@@ -35257,7 +35844,7 @@ const noIsMounted = defineRule({
|
|
|
35257
35844
|
});
|
|
35258
35845
|
//#endregion
|
|
35259
35846
|
//#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
|
|
35260
|
-
const MESSAGE$
|
|
35847
|
+
const MESSAGE$21 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
|
|
35261
35848
|
const isJsonMethodCall = (node, method) => {
|
|
35262
35849
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
35263
35850
|
const callee = node.callee;
|
|
@@ -35321,13 +35908,13 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
35321
35908
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
35322
35909
|
context.report({
|
|
35323
35910
|
node,
|
|
35324
|
-
message: MESSAGE$
|
|
35911
|
+
message: MESSAGE$21
|
|
35325
35912
|
});
|
|
35326
35913
|
} })
|
|
35327
35914
|
});
|
|
35328
35915
|
//#endregion
|
|
35329
35916
|
//#region src/plugin/rules/correctness/no-jsx-element-type.ts
|
|
35330
|
-
const MESSAGE$
|
|
35917
|
+
const MESSAGE$20 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
|
|
35331
35918
|
const isJsxElementTypeReference = (node) => {
|
|
35332
35919
|
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
35333
35920
|
const typeName = node.typeName;
|
|
@@ -35381,7 +35968,7 @@ const noJsxElementType = defineRule({
|
|
|
35381
35968
|
if (isJsxImported) return;
|
|
35382
35969
|
for (const typeAnnotation of flaggedAnnotations) context.report({
|
|
35383
35970
|
node: typeAnnotation,
|
|
35384
|
-
message: MESSAGE$
|
|
35971
|
+
message: MESSAGE$20
|
|
35385
35972
|
});
|
|
35386
35973
|
}
|
|
35387
35974
|
};
|
|
@@ -35613,6 +36200,221 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
35613
36200
|
}
|
|
35614
36201
|
});
|
|
35615
36202
|
//#endregion
|
|
36203
|
+
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
36204
|
+
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
36205
|
+
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
36206
|
+
const expression = stripParenExpression(node);
|
|
36207
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
36208
|
+
const propertyName = getStaticPropertyName(expression);
|
|
36209
|
+
const receiver = stripParenExpression(expression.object);
|
|
36210
|
+
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
36211
|
+
}
|
|
36212
|
+
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36213
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
36214
|
+
const symbol = scopes.symbolFor(expression);
|
|
36215
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
36216
|
+
visitedSymbolIds.add(symbol.id);
|
|
36217
|
+
if (isImportedFromReact(symbol)) {
|
|
36218
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
36219
|
+
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
36220
|
+
}
|
|
36221
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36222
|
+
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
36223
|
+
};
|
|
36224
|
+
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36225
|
+
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
36226
|
+
visitedClassNodes.add(classNode);
|
|
36227
|
+
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
36228
|
+
};
|
|
36229
|
+
//#endregion
|
|
36230
|
+
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
36231
|
+
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
36232
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
36233
|
+
let containsReactHookCall = false;
|
|
36234
|
+
walkAst(functionNode.body, (node) => {
|
|
36235
|
+
if (containsReactHookCall) return false;
|
|
36236
|
+
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
36237
|
+
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36238
|
+
containsReactHookCall = true;
|
|
36239
|
+
return false;
|
|
36240
|
+
}
|
|
36241
|
+
});
|
|
36242
|
+
return containsReactHookCall;
|
|
36243
|
+
};
|
|
36244
|
+
//#endregion
|
|
36245
|
+
//#region src/plugin/utils/function-returns-props-children.ts
|
|
36246
|
+
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
36247
|
+
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
36248
|
+
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
36249
|
+
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
36250
|
+
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
36251
|
+
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
36252
|
+
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
36253
|
+
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
36254
|
+
const propertyValue = stripParenExpression(property.value);
|
|
36255
|
+
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
36256
|
+
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
36257
|
+
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
36258
|
+
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
36259
|
+
}
|
|
36260
|
+
}
|
|
36261
|
+
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
36262
|
+
const candidate = stripParenExpression(expression);
|
|
36263
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
36264
|
+
const symbol = scopes.symbolFor(candidate);
|
|
36265
|
+
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
36266
|
+
}
|
|
36267
|
+
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
36268
|
+
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
36269
|
+
const receiver = stripParenExpression(candidate.object);
|
|
36270
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
36271
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
36272
|
+
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
36273
|
+
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
36274
|
+
}, controlFlow);
|
|
36275
|
+
};
|
|
36276
|
+
//#endregion
|
|
36277
|
+
//#region src/plugin/utils/function-returns-only-null.ts
|
|
36278
|
+
const isNullExpression = (expression) => {
|
|
36279
|
+
const candidate = stripParenExpression(expression);
|
|
36280
|
+
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
36281
|
+
};
|
|
36282
|
+
const functionReturnsOnlyNull = (functionNode) => {
|
|
36283
|
+
if (!isFunctionLike$2(functionNode)) return false;
|
|
36284
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
36285
|
+
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
36286
|
+
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
36287
|
+
};
|
|
36288
|
+
//#endregion
|
|
36289
|
+
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
36290
|
+
const findFactoryRoot = (node) => {
|
|
36291
|
+
const candidate = stripParenExpression(node);
|
|
36292
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
36293
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
36294
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
36295
|
+
return null;
|
|
36296
|
+
};
|
|
36297
|
+
const findFactoryPropertyName = (node) => {
|
|
36298
|
+
const candidate = stripParenExpression(node);
|
|
36299
|
+
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
36300
|
+
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
36301
|
+
return null;
|
|
36302
|
+
};
|
|
36303
|
+
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
36304
|
+
const candidate = stripParenExpression(expression);
|
|
36305
|
+
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
36306
|
+
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
36307
|
+
if (!factoryRoot) return false;
|
|
36308
|
+
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
36309
|
+
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
36310
|
+
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
36311
|
+
if (!symbol || symbol.kind !== "import") return false;
|
|
36312
|
+
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
36313
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
36314
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
36315
|
+
};
|
|
36316
|
+
//#endregion
|
|
36317
|
+
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
36318
|
+
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
36319
|
+
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
36320
|
+
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
36321
|
+
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36322
|
+
const candidate = stripParenExpression(expression);
|
|
36323
|
+
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
36324
|
+
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
36325
|
+
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
36326
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
36327
|
+
const symbol = scopes.symbolFor(candidate);
|
|
36328
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
36329
|
+
visitedSymbolIds.add(symbol.id);
|
|
36330
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
36331
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
36332
|
+
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
36333
|
+
}
|
|
36334
|
+
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
36335
|
+
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
36336
|
+
const factoryCallee = stripParenExpression(candidate.callee);
|
|
36337
|
+
if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
|
|
36338
|
+
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
36339
|
+
const wrappedComponent = candidate.arguments[0];
|
|
36340
|
+
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
36341
|
+
}
|
|
36342
|
+
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
36343
|
+
const factory = candidate.arguments[0];
|
|
36344
|
+
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
36345
|
+
const unwrappedFactory = stripParenExpression(factory);
|
|
36346
|
+
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
36347
|
+
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
36348
|
+
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
36349
|
+
const returnedExpression = returnStatements[0]?.argument;
|
|
36350
|
+
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
36351
|
+
};
|
|
36352
|
+
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
36353
|
+
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
36354
|
+
for (const candidateSymbol of candidateSymbols) {
|
|
36355
|
+
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
36356
|
+
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
36357
|
+
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
36358
|
+
continue;
|
|
36359
|
+
}
|
|
36360
|
+
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
36361
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
36362
|
+
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
36363
|
+
continue;
|
|
36364
|
+
}
|
|
36365
|
+
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
36366
|
+
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
36367
|
+
continue;
|
|
36368
|
+
}
|
|
36369
|
+
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
36370
|
+
}
|
|
36371
|
+
return false;
|
|
36372
|
+
};
|
|
36373
|
+
//#endregion
|
|
36374
|
+
//#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
|
|
36375
|
+
const REACT_COMPONENT_TYPE_NAMES = new Set([
|
|
36376
|
+
"ComponentClass",
|
|
36377
|
+
"ComponentType",
|
|
36378
|
+
"FC",
|
|
36379
|
+
"FunctionComponent"
|
|
36380
|
+
]);
|
|
36381
|
+
const findVisibleSymbol = (identifier, scopes) => {
|
|
36382
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
36383
|
+
let scope = scopes.scopeFor(identifier);
|
|
36384
|
+
while (true) {
|
|
36385
|
+
const symbol = scope.symbolsByName.get(identifier.name);
|
|
36386
|
+
if (symbol) return symbol;
|
|
36387
|
+
if (!scope.parent) return null;
|
|
36388
|
+
scope = scope.parent;
|
|
36389
|
+
}
|
|
36390
|
+
};
|
|
36391
|
+
const isReactNamespaceType = (identifier, scopes) => {
|
|
36392
|
+
const symbol = findVisibleSymbol(identifier, scopes);
|
|
36393
|
+
return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
36394
|
+
};
|
|
36395
|
+
const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
|
|
36396
|
+
if (!typeNode) return false;
|
|
36397
|
+
if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36398
|
+
if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
|
|
36399
|
+
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
36400
|
+
const typeName = typeNode.typeName;
|
|
36401
|
+
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);
|
|
36402
|
+
if (!isNodeOfType(typeName, "Identifier")) return false;
|
|
36403
|
+
const typeSymbol = findVisibleSymbol(typeName, scopes);
|
|
36404
|
+
if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
|
|
36405
|
+
if (isImportedFromReact(typeSymbol)) {
|
|
36406
|
+
const importedName = getImportedName(typeSymbol.declarationNode);
|
|
36407
|
+
return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
|
|
36408
|
+
}
|
|
36409
|
+
if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
|
|
36410
|
+
visitedSymbolIds.add(typeSymbol.id);
|
|
36411
|
+
return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
|
|
36412
|
+
};
|
|
36413
|
+
const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
|
|
36414
|
+
const bindingIdentifier = symbol.bindingIdentifier;
|
|
36415
|
+
return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
|
|
36416
|
+
};
|
|
36417
|
+
//#endregion
|
|
35616
36418
|
//#region src/plugin/rules/architecture/no-legacy-context-api.ts
|
|
35617
36419
|
const LEGACY_CONTEXT_NAMES = new Set([
|
|
35618
36420
|
"childContextTypes",
|
|
@@ -35623,15 +36425,6 @@ const buildLegacyContextMessage = (memberName) => {
|
|
|
35623
36425
|
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.`;
|
|
35624
36426
|
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.";
|
|
35625
36427
|
};
|
|
35626
|
-
const isInsideClassBody = (node) => {
|
|
35627
|
-
let current = node.parent;
|
|
35628
|
-
while (current) {
|
|
35629
|
-
if (isNodeOfType(current, "ClassBody")) return true;
|
|
35630
|
-
if (isFunctionLike$2(current)) return false;
|
|
35631
|
-
current = current.parent;
|
|
35632
|
-
}
|
|
35633
|
-
return false;
|
|
35634
|
-
};
|
|
35635
36428
|
const noLegacyContextApi = defineRule({
|
|
35636
36429
|
id: "no-legacy-context-api",
|
|
35637
36430
|
title: "Legacy context API",
|
|
@@ -35646,6 +36439,7 @@ const noLegacyContextApi = defineRule({
|
|
|
35646
36439
|
if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
|
|
35647
36440
|
if (!isNodeOfType(memberNode.key, "Identifier")) return;
|
|
35648
36441
|
if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
|
|
36442
|
+
if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
|
|
35649
36443
|
context.report({
|
|
35650
36444
|
node: memberNode.key,
|
|
35651
36445
|
message: buildLegacyContextMessage(memberNode.key.name)
|
|
@@ -35653,6 +36447,8 @@ const noLegacyContextApi = defineRule({
|
|
|
35653
36447
|
};
|
|
35654
36448
|
return {
|
|
35655
36449
|
ClassBody(node) {
|
|
36450
|
+
const classNode = node.parent;
|
|
36451
|
+
if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
|
|
35656
36452
|
for (const member of node.body ?? []) checkMember(member);
|
|
35657
36453
|
},
|
|
35658
36454
|
AssignmentExpression(node) {
|
|
@@ -35662,9 +36458,11 @@ const noLegacyContextApi = defineRule({
|
|
|
35662
36458
|
if (left.computed) return;
|
|
35663
36459
|
if (!isNodeOfType(left.property, "Identifier")) return;
|
|
35664
36460
|
if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
|
|
35665
|
-
if (
|
|
35666
|
-
|
|
35667
|
-
if (
|
|
36461
|
+
if (left.property.name === "getChildContext") return;
|
|
36462
|
+
const component = stripParenExpression(left.object);
|
|
36463
|
+
if (!isNodeOfType(component, "Identifier")) return;
|
|
36464
|
+
const symbol = context.scopes.symbolFor(component);
|
|
36465
|
+
if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
|
|
35668
36466
|
context.report({
|
|
35669
36467
|
node: left,
|
|
35670
36468
|
message: buildLegacyContextMessage(left.property.name)
|
|
@@ -35868,10 +36666,10 @@ const getMethodCalls = (functionNode, scopes) => {
|
|
|
35868
36666
|
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
35869
36667
|
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
35870
36668
|
visitedFunctionNodes.add(functionNode);
|
|
35871
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36669
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
35872
36670
|
const immediateCall = getDirectCallForExpression(functionNode);
|
|
35873
36671
|
if (immediateCall) {
|
|
35874
|
-
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
36672
|
+
const immediateCallFunction = findEnclosingFunction$1(immediateCall);
|
|
35875
36673
|
if (immediateCallFunction === usageFunction) {
|
|
35876
36674
|
const immediateCallStart = getRangeStart(immediateCall);
|
|
35877
36675
|
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
@@ -35880,7 +36678,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
35880
36678
|
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
35881
36679
|
}
|
|
35882
36680
|
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
35883
|
-
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
36681
|
+
const methodCallFunction = findEnclosingFunction$1(methodCall);
|
|
35884
36682
|
if (methodCallFunction === usageFunction) {
|
|
35885
36683
|
const methodCallStart = getRangeStart(methodCall);
|
|
35886
36684
|
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
@@ -35903,7 +36701,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
35903
36701
|
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
35904
36702
|
const call = getDirectCallForExpression(child);
|
|
35905
36703
|
if (!call) return false;
|
|
35906
|
-
const callFunction = findEnclosingFunction(call);
|
|
36704
|
+
const callFunction = findEnclosingFunction$1(call);
|
|
35907
36705
|
if (callFunction === usageFunction) {
|
|
35908
36706
|
const callStart = getRangeStart(call);
|
|
35909
36707
|
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
@@ -35934,14 +36732,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
|
|
|
35934
36732
|
visitedMutationSymbolIds.add(symbol.id);
|
|
35935
36733
|
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
35936
36734
|
if (typeof usageBoundary !== "number") return true;
|
|
35937
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
36735
|
+
const usageFunction = findEnclosingFunction$1(usageNode);
|
|
35938
36736
|
return symbol.references.some((reference) => {
|
|
35939
36737
|
const referenceStart = getRangeStart(reference.identifier);
|
|
35940
36738
|
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
35941
36739
|
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
35942
36740
|
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
35943
36741
|
if (referenceStart === null) return true;
|
|
35944
|
-
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
36742
|
+
const mutationFunction = findEnclosingFunction$1(reference.identifier);
|
|
35945
36743
|
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
35946
36744
|
if (!mutationFunction) return usageFunction !== null;
|
|
35947
36745
|
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
@@ -36486,7 +37284,7 @@ const noMoment = defineRule({
|
|
|
36486
37284
|
});
|
|
36487
37285
|
//#endregion
|
|
36488
37286
|
//#region src/plugin/rules/react-builtins/no-multi-comp.ts
|
|
36489
|
-
const MESSAGE$
|
|
37287
|
+
const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
|
|
36490
37288
|
const resolveSettings$16 = (settings) => {
|
|
36491
37289
|
const reactDoctor = settings?.["react-doctor"];
|
|
36492
37290
|
return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
|
|
@@ -36523,13 +37321,6 @@ const isReactNamespaceExpression = (node, scopes) => {
|
|
|
36523
37321
|
return isReactNamespaceImport(node, scopes);
|
|
36524
37322
|
};
|
|
36525
37323
|
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));
|
|
36526
|
-
const getDestructuredPropertyName$1 = (symbol) => {
|
|
36527
|
-
const property = symbol.bindingIdentifier.parent;
|
|
36528
|
-
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
36529
|
-
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
36530
|
-
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
36531
|
-
return null;
|
|
36532
|
-
};
|
|
36533
37324
|
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
36534
37325
|
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
36535
37326
|
visitedSymbolIds.add(symbol.id);
|
|
@@ -36539,7 +37330,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
|
36539
37330
|
}
|
|
36540
37331
|
const init = symbol.initializer;
|
|
36541
37332
|
if (!init) return false;
|
|
36542
|
-
const destructuredPropertyName =
|
|
37333
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
36543
37334
|
if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
|
|
36544
37335
|
if (isReactHocMemberReference(init, scopes)) return true;
|
|
36545
37336
|
if (isNodeOfType(init, "Identifier")) {
|
|
@@ -36845,7 +37636,7 @@ const noMultiComp = defineRule({
|
|
|
36845
37636
|
if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
|
|
36846
37637
|
for (const component of flagged.slice(1)) context.report({
|
|
36847
37638
|
node: component.reportNode,
|
|
36848
|
-
message: MESSAGE$
|
|
37639
|
+
message: MESSAGE$19
|
|
36849
37640
|
});
|
|
36850
37641
|
} };
|
|
36851
37642
|
}
|
|
@@ -37058,7 +37849,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
37058
37849
|
};
|
|
37059
37850
|
//#endregion
|
|
37060
37851
|
//#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
|
|
37061
|
-
const MESSAGE$
|
|
37852
|
+
const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
|
|
37062
37853
|
const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
|
|
37063
37854
|
"copyWithin",
|
|
37064
37855
|
"fill",
|
|
@@ -37267,7 +38058,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37267
38058
|
reportedNodes.add(options.crossFileConsumerCallSite);
|
|
37268
38059
|
context.report({
|
|
37269
38060
|
node: options.crossFileConsumerCallSite,
|
|
37270
|
-
message: `${MESSAGE$
|
|
38061
|
+
message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
|
|
37271
38062
|
});
|
|
37272
38063
|
return;
|
|
37273
38064
|
}
|
|
@@ -37276,7 +38067,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
|
|
|
37276
38067
|
reportedNodes.add(mutation.node);
|
|
37277
38068
|
context.report({
|
|
37278
38069
|
node: mutation.node,
|
|
37279
|
-
message: MESSAGE$
|
|
38070
|
+
message: MESSAGE$18
|
|
37280
38071
|
});
|
|
37281
38072
|
}
|
|
37282
38073
|
};
|
|
@@ -37683,7 +38474,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
|
|
|
37683
38474
|
});
|
|
37684
38475
|
//#endregion
|
|
37685
38476
|
//#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
|
|
37686
|
-
const MESSAGE$
|
|
38477
|
+
const MESSAGE$17 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
|
|
37687
38478
|
const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
37688
38479
|
"onKeyDown",
|
|
37689
38480
|
"onKeyUp",
|
|
@@ -37802,7 +38593,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37802
38593
|
if (numeric === null) {
|
|
37803
38594
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
|
|
37804
38595
|
node: tabIndex,
|
|
37805
|
-
message: MESSAGE$
|
|
38596
|
+
message: MESSAGE$17
|
|
37806
38597
|
});
|
|
37807
38598
|
return;
|
|
37808
38599
|
}
|
|
@@ -37824,7 +38615,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37824
38615
|
if (!roleAttribute) {
|
|
37825
38616
|
context.report({
|
|
37826
38617
|
node: tabIndex,
|
|
37827
|
-
message: MESSAGE$
|
|
38618
|
+
message: MESSAGE$17
|
|
37828
38619
|
});
|
|
37829
38620
|
return;
|
|
37830
38621
|
}
|
|
@@ -37838,7 +38629,7 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
37838
38629
|
}
|
|
37839
38630
|
context.report({
|
|
37840
38631
|
node: tabIndex,
|
|
37841
|
-
message: MESSAGE$
|
|
38632
|
+
message: MESSAGE$17
|
|
37842
38633
|
});
|
|
37843
38634
|
} };
|
|
37844
38635
|
}
|
|
@@ -38293,12 +39084,6 @@ const getDeclarationKind = (declarator) => {
|
|
|
38293
39084
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
38294
39085
|
};
|
|
38295
39086
|
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
38296
|
-
const getDestructuredPropertyName = (bindingIdentifier) => {
|
|
38297
|
-
const property = bindingIdentifier.parent;
|
|
38298
|
-
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
38299
|
-
if (property.computed) return isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? String(property.key.value) : null;
|
|
38300
|
-
return isNodeOfType(property.key, "Identifier") ? property.key.name : null;
|
|
38301
|
-
};
|
|
38302
39087
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
38303
39088
|
const unwrappedExpression = stripParenExpression(expression);
|
|
38304
39089
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -38308,7 +39093,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
38308
39093
|
if (isProp(analysis, callbackReference)) {
|
|
38309
39094
|
if (isWholePropsObjectReference(analysis, callbackReference)) return null;
|
|
38310
39095
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
38311
|
-
return (bindingIdentifier &&
|
|
39096
|
+
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
38312
39097
|
}
|
|
38313
39098
|
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
38314
39099
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -38318,7 +39103,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
38318
39103
|
visitedVariables.add(callbackVariable);
|
|
38319
39104
|
if (isNodeOfType(declarator.id, "ObjectPattern")) {
|
|
38320
39105
|
const bindingIdentifier = callbackVariable.defs[0]?.name;
|
|
38321
|
-
const propertyName = bindingIdentifier ?
|
|
39106
|
+
const propertyName = bindingIdentifier ? getDestructuredBindingPropertyName(bindingIdentifier) : null;
|
|
38322
39107
|
const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
|
|
38323
39108
|
return propertyName && propsReference ? propertyName : null;
|
|
38324
39109
|
}
|
|
@@ -38421,7 +39206,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
38421
39206
|
const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
|
|
38422
39207
|
if (!bindingProvenance) return null;
|
|
38423
39208
|
const { refCall, variables } = bindingProvenance;
|
|
38424
|
-
const componentFunction = findEnclosingFunction(effectCall);
|
|
39209
|
+
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
38425
39210
|
if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
38426
39211
|
if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
|
|
38427
39212
|
const callbackPropNames = /* @__PURE__ */ new Set();
|
|
@@ -39257,174 +40042,6 @@ const noPropCallbackInRender = defineRule({
|
|
|
39257
40042
|
} })
|
|
39258
40043
|
});
|
|
39259
40044
|
//#endregion
|
|
39260
|
-
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
39261
|
-
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
39262
|
-
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
39263
|
-
const expression = stripParenExpression(node);
|
|
39264
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39265
|
-
const propertyName = getStaticPropertyName(expression);
|
|
39266
|
-
const receiver = stripParenExpression(expression.object);
|
|
39267
|
-
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
39268
|
-
}
|
|
39269
|
-
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39270
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
39271
|
-
const symbol = scopes.symbolFor(expression);
|
|
39272
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
39273
|
-
visitedSymbolIds.add(symbol.id);
|
|
39274
|
-
if (isImportedFromReact(symbol)) {
|
|
39275
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
39276
|
-
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
39277
|
-
}
|
|
39278
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39279
|
-
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
39280
|
-
};
|
|
39281
|
-
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39282
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
39283
|
-
visitedClassNodes.add(classNode);
|
|
39284
|
-
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
39285
|
-
};
|
|
39286
|
-
//#endregion
|
|
39287
|
-
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
39288
|
-
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
39289
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
39290
|
-
let containsReactHookCall = false;
|
|
39291
|
-
walkAst(functionNode.body, (node) => {
|
|
39292
|
-
if (containsReactHookCall) return false;
|
|
39293
|
-
if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
39294
|
-
if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39295
|
-
containsReactHookCall = true;
|
|
39296
|
-
return false;
|
|
39297
|
-
}
|
|
39298
|
-
});
|
|
39299
|
-
return containsReactHookCall;
|
|
39300
|
-
};
|
|
39301
|
-
//#endregion
|
|
39302
|
-
//#region src/plugin/utils/function-returns-props-children.ts
|
|
39303
|
-
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
39304
|
-
if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
|
|
39305
|
-
const firstParameter = stripParenExpression(functionNode.params[0]);
|
|
39306
|
-
const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
|
|
39307
|
-
const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
|
|
39308
|
-
const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
|
|
39309
|
-
if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
|
|
39310
|
-
for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
|
|
39311
|
-
const propertyValue = stripParenExpression(property.value);
|
|
39312
|
-
const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
|
|
39313
|
-
if (!isNodeOfType(childrenBinding, "Identifier")) continue;
|
|
39314
|
-
const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
|
|
39315
|
-
if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
|
|
39316
|
-
}
|
|
39317
|
-
}
|
|
39318
|
-
return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
|
|
39319
|
-
const candidate = stripParenExpression(expression);
|
|
39320
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
39321
|
-
const symbol = scopes.symbolFor(candidate);
|
|
39322
|
-
return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
|
|
39323
|
-
}
|
|
39324
|
-
if (!isNodeOfType(candidate, "MemberExpression")) return false;
|
|
39325
|
-
if (getStaticPropertyName(candidate) !== "children") return false;
|
|
39326
|
-
const receiver = stripParenExpression(candidate.object);
|
|
39327
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
39328
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
39329
|
-
if (!receiverSymbol || !propsParameterSymbol) return false;
|
|
39330
|
-
return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
|
|
39331
|
-
}, controlFlow);
|
|
39332
|
-
};
|
|
39333
|
-
//#endregion
|
|
39334
|
-
//#region src/plugin/utils/function-returns-only-null.ts
|
|
39335
|
-
const isNullExpression = (expression) => {
|
|
39336
|
-
const candidate = stripParenExpression(expression);
|
|
39337
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39338
|
-
};
|
|
39339
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
39340
|
-
if (!isFunctionLike$2(functionNode)) return false;
|
|
39341
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39342
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39343
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39344
|
-
};
|
|
39345
|
-
//#endregion
|
|
39346
|
-
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
39347
|
-
const findFactoryRoot = (node) => {
|
|
39348
|
-
const candidate = stripParenExpression(node);
|
|
39349
|
-
if (isNodeOfType(candidate, "Identifier")) return candidate;
|
|
39350
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
|
|
39351
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
|
|
39352
|
-
return null;
|
|
39353
|
-
};
|
|
39354
|
-
const findFactoryPropertyName = (node) => {
|
|
39355
|
-
const candidate = stripParenExpression(node);
|
|
39356
|
-
if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
|
|
39357
|
-
if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
|
|
39358
|
-
return null;
|
|
39359
|
-
};
|
|
39360
|
-
const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
39361
|
-
const candidate = stripParenExpression(expression);
|
|
39362
|
-
if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
|
|
39363
|
-
const factoryRoot = findFactoryRoot(candidate.tag);
|
|
39364
|
-
if (!factoryRoot) return false;
|
|
39365
|
-
const factoryPropertyName = findFactoryPropertyName(candidate.tag);
|
|
39366
|
-
if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
|
|
39367
|
-
const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
|
|
39368
|
-
if (!symbol || symbol.kind !== "import") return false;
|
|
39369
|
-
if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
|
|
39370
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
39371
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
|
|
39372
|
-
};
|
|
39373
|
-
//#endregion
|
|
39374
|
-
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
39375
|
-
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
39376
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
39377
|
-
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39378
|
-
const candidate = stripParenExpression(expression);
|
|
39379
|
-
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
39380
|
-
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
39381
|
-
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
39382
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
39383
|
-
const symbol = scopes.symbolFor(candidate);
|
|
39384
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
39385
|
-
visitedSymbolIds.add(symbol.id);
|
|
39386
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
39387
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
39388
|
-
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
39389
|
-
}
|
|
39390
|
-
if (!isNodeOfType(candidate, "CallExpression")) return false;
|
|
39391
|
-
if (!hasStableCallTarget(candidate, scopes)) return false;
|
|
39392
|
-
if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
|
|
39393
|
-
const wrappedComponent = candidate.arguments[0];
|
|
39394
|
-
return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
|
|
39395
|
-
}
|
|
39396
|
-
if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
|
|
39397
|
-
const factory = candidate.arguments[0];
|
|
39398
|
-
if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
|
|
39399
|
-
const unwrappedFactory = stripParenExpression(factory);
|
|
39400
|
-
if (!isInlineFunctionExpression(unwrappedFactory)) return false;
|
|
39401
|
-
if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
|
|
39402
|
-
const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
|
|
39403
|
-
const returnedExpression = returnStatements[0]?.argument;
|
|
39404
|
-
return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
|
|
39405
|
-
};
|
|
39406
|
-
const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
|
|
39407
|
-
const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
|
|
39408
|
-
for (const candidateSymbol of candidateSymbols) {
|
|
39409
|
-
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
39410
|
-
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
39411
|
-
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
39412
|
-
continue;
|
|
39413
|
-
}
|
|
39414
|
-
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
39415
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
|
|
39416
|
-
if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
|
|
39417
|
-
continue;
|
|
39418
|
-
}
|
|
39419
|
-
if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
|
|
39420
|
-
if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
|
|
39421
|
-
continue;
|
|
39422
|
-
}
|
|
39423
|
-
if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
|
|
39424
|
-
}
|
|
39425
|
-
return false;
|
|
39426
|
-
};
|
|
39427
|
-
//#endregion
|
|
39428
40045
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
39429
40046
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
39430
40047
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -39577,7 +40194,7 @@ const isModuleScopedBinding = (identifier) => {
|
|
|
39577
40194
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
39578
40195
|
if (!binding) return false;
|
|
39579
40196
|
if (isNodeOfType(binding.scopeOwner, "Program")) return true;
|
|
39580
|
-
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
|
|
40197
|
+
if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction$1(binding.scopeOwner) === null;
|
|
39581
40198
|
return false;
|
|
39582
40199
|
};
|
|
39583
40200
|
const looksLikeFreshUpdateExpression = (expression) => {
|
|
@@ -39632,7 +40249,7 @@ const noRandomKey = defineRule({
|
|
|
39632
40249
|
});
|
|
39633
40250
|
//#endregion
|
|
39634
40251
|
//#region src/plugin/rules/react-builtins/no-react-children.ts
|
|
39635
|
-
const MESSAGE$
|
|
40252
|
+
const MESSAGE$16 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
|
|
39636
40253
|
const isChildrenIdentifier = (node, contextNode) => {
|
|
39637
40254
|
if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
|
|
39638
40255
|
return isImportedFromModule(contextNode, "Children", "react");
|
|
@@ -39658,13 +40275,13 @@ const noReactChildren = defineRule({
|
|
|
39658
40275
|
if (isChildrenIdentifier(memberObject, node)) {
|
|
39659
40276
|
context.report({
|
|
39660
40277
|
node: calleeOuter,
|
|
39661
|
-
message: MESSAGE$
|
|
40278
|
+
message: MESSAGE$16
|
|
39662
40279
|
});
|
|
39663
40280
|
return;
|
|
39664
40281
|
}
|
|
39665
40282
|
if (isReactNamespaceMember(memberObject, node)) context.report({
|
|
39666
40283
|
node: calleeOuter,
|
|
39667
|
-
message: MESSAGE$
|
|
40284
|
+
message: MESSAGE$16
|
|
39668
40285
|
});
|
|
39669
40286
|
} })
|
|
39670
40287
|
});
|
|
@@ -40345,7 +40962,7 @@ const noRenderPropChildren = defineRule({
|
|
|
40345
40962
|
});
|
|
40346
40963
|
//#endregion
|
|
40347
40964
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
40348
|
-
const MESSAGE$
|
|
40965
|
+
const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
40349
40966
|
const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
|
|
40350
40967
|
const isUsedAsReturnValue = (parent) => {
|
|
40351
40968
|
if (!parent) return false;
|
|
@@ -40363,7 +40980,7 @@ const noRenderReturnValue = defineRule({
|
|
|
40363
40980
|
if (!isUsedAsReturnValue(node.parent)) return;
|
|
40364
40981
|
context.report({
|
|
40365
40982
|
node: node.callee,
|
|
40366
|
-
message: MESSAGE$
|
|
40983
|
+
message: MESSAGE$15
|
|
40367
40984
|
});
|
|
40368
40985
|
} })
|
|
40369
40986
|
});
|
|
@@ -41172,7 +41789,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
41172
41789
|
});
|
|
41173
41790
|
//#endregion
|
|
41174
41791
|
//#region src/plugin/rules/react-builtins/no-set-state.ts
|
|
41175
|
-
const MESSAGE$
|
|
41792
|
+
const MESSAGE$14 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
|
|
41176
41793
|
const noSetState = defineRule({
|
|
41177
41794
|
id: "no-set-state",
|
|
41178
41795
|
title: "Local class state forbidden",
|
|
@@ -41187,7 +41804,7 @@ const noSetState = defineRule({
|
|
|
41187
41804
|
if (!getParentComponent(node)) return;
|
|
41188
41805
|
context.report({
|
|
41189
41806
|
node: node.callee,
|
|
41190
|
-
message: MESSAGE$
|
|
41807
|
+
message: MESSAGE$14
|
|
41191
41808
|
});
|
|
41192
41809
|
} })
|
|
41193
41810
|
});
|
|
@@ -41494,9 +42111,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
|
41494
42111
|
return isReturnedFromEffect;
|
|
41495
42112
|
};
|
|
41496
42113
|
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
41497
|
-
let functionNode = findEnclosingFunction(node);
|
|
42114
|
+
let functionNode = findEnclosingFunction$1(node);
|
|
41498
42115
|
while (functionNode) {
|
|
41499
|
-
const outerFunction = findEnclosingFunction(functionNode);
|
|
42116
|
+
const outerFunction = findEnclosingFunction$1(functionNode);
|
|
41500
42117
|
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
41501
42118
|
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
41502
42119
|
functionNode = outerFunction;
|
|
@@ -41504,7 +42121,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
|
41504
42121
|
return false;
|
|
41505
42122
|
};
|
|
41506
42123
|
const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
|
|
41507
|
-
const enclosingFunction = findEnclosingFunction(clearCall);
|
|
42124
|
+
const enclosingFunction = findEnclosingFunction$1(clearCall);
|
|
41508
42125
|
if (!isFunctionLike$2(enclosingFunction)) return false;
|
|
41509
42126
|
if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
41510
42127
|
const clearStart = getRangeStart(clearCall);
|
|
@@ -41557,7 +42174,7 @@ const isAbstractRole = (openingElement, settings) => {
|
|
|
41557
42174
|
};
|
|
41558
42175
|
//#endregion
|
|
41559
42176
|
//#region src/plugin/rules/a11y/no-static-element-interactions.ts
|
|
41560
|
-
const MESSAGE$
|
|
42177
|
+
const MESSAGE$13 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
|
|
41561
42178
|
const DEFAULT_HANDLERS = [
|
|
41562
42179
|
"onClick",
|
|
41563
42180
|
"onMouseDown",
|
|
@@ -41672,7 +42289,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41672
42289
|
if (!roleAttribute || !roleAttribute.value) {
|
|
41673
42290
|
context.report({
|
|
41674
42291
|
node: node.name,
|
|
41675
|
-
message: MESSAGE$
|
|
42292
|
+
message: MESSAGE$13
|
|
41676
42293
|
});
|
|
41677
42294
|
return;
|
|
41678
42295
|
}
|
|
@@ -41682,7 +42299,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41682
42299
|
if (isRecognizedRoleString(attributeValue.value)) return;
|
|
41683
42300
|
context.report({
|
|
41684
42301
|
node: node.name,
|
|
41685
|
-
message: MESSAGE$
|
|
42302
|
+
message: MESSAGE$13
|
|
41686
42303
|
});
|
|
41687
42304
|
return;
|
|
41688
42305
|
}
|
|
@@ -41690,7 +42307,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41690
42307
|
if (!settings.allowExpressionValues) {
|
|
41691
42308
|
context.report({
|
|
41692
42309
|
node: node.name,
|
|
41693
|
-
message: MESSAGE$
|
|
42310
|
+
message: MESSAGE$13
|
|
41694
42311
|
});
|
|
41695
42312
|
return;
|
|
41696
42313
|
}
|
|
@@ -41698,7 +42315,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41698
42315
|
if (isStaticNullishExpression(expression)) {
|
|
41699
42316
|
context.report({
|
|
41700
42317
|
node: node.name,
|
|
41701
|
-
message: MESSAGE$
|
|
42318
|
+
message: MESSAGE$13
|
|
41702
42319
|
});
|
|
41703
42320
|
return;
|
|
41704
42321
|
}
|
|
@@ -41708,7 +42325,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41708
42325
|
if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
|
|
41709
42326
|
context.report({
|
|
41710
42327
|
node: node.name,
|
|
41711
|
-
message: MESSAGE$
|
|
42328
|
+
message: MESSAGE$13
|
|
41712
42329
|
});
|
|
41713
42330
|
return;
|
|
41714
42331
|
}
|
|
@@ -41716,7 +42333,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
41716
42333
|
}
|
|
41717
42334
|
context.report({
|
|
41718
42335
|
node: node.name,
|
|
41719
|
-
message: MESSAGE$
|
|
42336
|
+
message: MESSAGE$13
|
|
41720
42337
|
});
|
|
41721
42338
|
} };
|
|
41722
42339
|
}
|
|
@@ -41822,7 +42439,7 @@ const noStringRefs = defineRule({
|
|
|
41822
42439
|
});
|
|
41823
42440
|
//#endregion
|
|
41824
42441
|
//#region src/plugin/rules/js-performance/no-sync-xhr.ts
|
|
41825
|
-
const MESSAGE$
|
|
42442
|
+
const MESSAGE$12 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
|
|
41826
42443
|
const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
|
|
41827
42444
|
const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
|
|
41828
42445
|
const noSyncXhr = defineRule({
|
|
@@ -41843,7 +42460,7 @@ const noSyncXhr = defineRule({
|
|
|
41843
42460
|
if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
|
|
41844
42461
|
context.report({
|
|
41845
42462
|
node,
|
|
41846
|
-
message: MESSAGE$
|
|
42463
|
+
message: MESSAGE$12
|
|
41847
42464
|
});
|
|
41848
42465
|
};
|
|
41849
42466
|
return {
|
|
@@ -41868,7 +42485,7 @@ const noSyncXhr = defineRule({
|
|
|
41868
42485
|
});
|
|
41869
42486
|
//#endregion
|
|
41870
42487
|
//#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
|
|
41871
|
-
const MESSAGE$
|
|
42488
|
+
const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
|
|
41872
42489
|
const isInsideClassMethod = (node, customClassFactoryNames) => {
|
|
41873
42490
|
let ancestor = node.parent;
|
|
41874
42491
|
while (ancestor) {
|
|
@@ -41956,7 +42573,7 @@ const noThisInSfc = defineRule({
|
|
|
41956
42573
|
if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
|
|
41957
42574
|
context.report({
|
|
41958
42575
|
node,
|
|
41959
|
-
message: MESSAGE$
|
|
42576
|
+
message: MESSAGE$11
|
|
41960
42577
|
});
|
|
41961
42578
|
} };
|
|
41962
42579
|
}
|
|
@@ -42334,7 +42951,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
|
|
|
42334
42951
|
return false;
|
|
42335
42952
|
};
|
|
42336
42953
|
const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
|
|
42337
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
42954
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
42338
42955
|
if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
42339
42956
|
let currentNode = node;
|
|
42340
42957
|
while (currentNode !== enclosingFunction) {
|
|
@@ -43601,7 +44218,11 @@ const resolveSettings$8 = (settings) => {
|
|
|
43601
44218
|
propNamePattern: ruleSettings.propNamePattern ?? "render*"
|
|
43602
44219
|
};
|
|
43603
44220
|
};
|
|
43604
|
-
const
|
|
44221
|
+
const isReactCreateElementCall = (node, scopes) => isReactApiCall(node, "createElement", scopes, {
|
|
44222
|
+
allowGlobalReactNamespace: true,
|
|
44223
|
+
resolveNamedAliases: true
|
|
44224
|
+
});
|
|
44225
|
+
const expressionContainsJsxOrCreateElement = (root, scopes) => {
|
|
43605
44226
|
let found = false;
|
|
43606
44227
|
walkAst(root, (node) => {
|
|
43607
44228
|
if (found) return false;
|
|
@@ -43610,17 +44231,17 @@ const expressionContainsJsxOrCreateElement = (root) => {
|
|
|
43610
44231
|
found = true;
|
|
43611
44232
|
return false;
|
|
43612
44233
|
}
|
|
43613
|
-
if (isNodeOfType(node, "CallExpression") &&
|
|
44234
|
+
if (isNodeOfType(node, "CallExpression") && isReactCreateElementCall(node, scopes)) {
|
|
43614
44235
|
found = true;
|
|
43615
44236
|
return false;
|
|
43616
44237
|
}
|
|
43617
44238
|
});
|
|
43618
44239
|
return found;
|
|
43619
44240
|
};
|
|
43620
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
|
|
43621
|
-
const isReactClassComponent = (classNode) => {
|
|
44241
|
+
const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => expressionContainsJsxOrCreateElement(expression, scopes), controlFlow);
|
|
44242
|
+
const isReactClassComponent = (classNode, scopes) => {
|
|
43622
44243
|
if (isEs6Component(classNode)) return true;
|
|
43623
|
-
return expressionContainsJsxOrCreateElement(classNode);
|
|
44244
|
+
return expressionContainsJsxOrCreateElement(classNode, scopes);
|
|
43624
44245
|
};
|
|
43625
44246
|
const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
43626
44247
|
let walker = node.parent;
|
|
@@ -43637,7 +44258,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
|
|
|
43637
44258
|
};
|
|
43638
44259
|
}
|
|
43639
44260
|
if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
|
|
43640
|
-
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
|
|
44261
|
+
if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker, scopes)) return {
|
|
43641
44262
|
component: walker,
|
|
43642
44263
|
name: walker.id.name
|
|
43643
44264
|
};
|
|
@@ -43726,12 +44347,12 @@ const isObjectCallbackCandidate = (node) => {
|
|
|
43726
44347
|
}
|
|
43727
44348
|
return false;
|
|
43728
44349
|
};
|
|
43729
|
-
const hocCallContainsComponent = (call) => {
|
|
44350
|
+
const hocCallContainsComponent = (call, scopes) => {
|
|
43730
44351
|
const firstArgument = call.arguments[0];
|
|
43731
44352
|
if (!firstArgument) return false;
|
|
43732
|
-
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument);
|
|
43733
|
-
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument);
|
|
43734
|
-
return expressionContainsJsxOrCreateElement(firstArgument);
|
|
44353
|
+
if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument, scopes);
|
|
44354
|
+
if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument, scopes);
|
|
44355
|
+
return expressionContainsJsxOrCreateElement(firstArgument, scopes);
|
|
43735
44356
|
};
|
|
43736
44357
|
const isFirstArgumentOfHocCall = (node) => {
|
|
43737
44358
|
const parent = node.parent;
|
|
@@ -43764,19 +44385,35 @@ const isReturnOfMapCallback = (node) => {
|
|
|
43764
44385
|
}
|
|
43765
44386
|
return false;
|
|
43766
44387
|
};
|
|
43767
|
-
const isCloneElementCall = (node) => {
|
|
43768
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
43769
|
-
const callee = node.callee;
|
|
43770
|
-
if (isNodeOfType(callee, "Identifier")) return callee.name === "cloneElement";
|
|
43771
|
-
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "cloneElement";
|
|
43772
|
-
};
|
|
43773
44388
|
const TS_VALUE_PASSTHROUGH_TYPES = new Set([
|
|
43774
44389
|
"TSAsExpression",
|
|
43775
44390
|
"TSNonNullExpression",
|
|
43776
44391
|
"TSSatisfiesExpression",
|
|
43777
44392
|
"TSTypeAssertion"
|
|
43778
44393
|
]);
|
|
43779
|
-
const
|
|
44394
|
+
const ELEMENT_TYPE_PROP_NAMES = new Set([
|
|
44395
|
+
"as",
|
|
44396
|
+
"body",
|
|
44397
|
+
"calendarcontainer",
|
|
44398
|
+
"component",
|
|
44399
|
+
"fallback",
|
|
44400
|
+
"tooltip"
|
|
44401
|
+
]);
|
|
44402
|
+
const isElementTypeJsxAttribute = (node) => {
|
|
44403
|
+
if (!isNodeOfType(node, "JSXAttribute")) return false;
|
|
44404
|
+
if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
|
|
44405
|
+
const attributeName = node.name.name;
|
|
44406
|
+
return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
|
|
44407
|
+
};
|
|
44408
|
+
const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
|
|
44409
|
+
allowGlobalReactNamespace: true,
|
|
44410
|
+
resolveNamedAliases: true
|
|
44411
|
+
});
|
|
44412
|
+
const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
|
|
44413
|
+
allowGlobalReactNamespace: true,
|
|
44414
|
+
resolveNamedAliases: true
|
|
44415
|
+
});
|
|
44416
|
+
const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
43780
44417
|
let valueNode = identifier;
|
|
43781
44418
|
let parent = valueNode.parent;
|
|
43782
44419
|
while (parent) {
|
|
@@ -43786,20 +44423,26 @@ const isRenderFlowingReadReference = (identifier) => {
|
|
|
43786
44423
|
continue;
|
|
43787
44424
|
}
|
|
43788
44425
|
switch (parent.type) {
|
|
43789
|
-
case "
|
|
43790
|
-
case "
|
|
43791
|
-
case "
|
|
44426
|
+
case "JSXOpeningElement": return parent.name === valueNode;
|
|
44427
|
+
case "JSXExpressionContainer": return Boolean(parent.parent && isElementTypeJsxAttribute(parent.parent));
|
|
44428
|
+
case "ReturnStatement": return false;
|
|
44429
|
+
case "ArrowFunctionExpression": return false;
|
|
43792
44430
|
case "CallExpression":
|
|
43793
44431
|
if (parent.callee === valueNode) return false;
|
|
43794
|
-
if (
|
|
44432
|
+
if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
|
|
44433
|
+
if (isReactCreateElementCall(parent, scopes)) return true;
|
|
43795
44434
|
valueNode = parent;
|
|
43796
44435
|
parent = parent.parent;
|
|
43797
44436
|
continue;
|
|
43798
|
-
case "VariableDeclarator":
|
|
43799
|
-
|
|
43800
|
-
const
|
|
43801
|
-
|
|
44437
|
+
case "VariableDeclarator": {
|
|
44438
|
+
if (parent.init !== valueNode || !isNodeOfType(parent.id, "Identifier")) return false;
|
|
44439
|
+
const aliasSymbol = scopes.symbolFor(parent.id);
|
|
44440
|
+
if (!aliasSymbol || aliasSymbol.kind !== "const" || visitedSymbols.has(aliasSymbol.id)) return false;
|
|
44441
|
+
const nextVisitedSymbols = new Set(visitedSymbols);
|
|
44442
|
+
nextVisitedSymbols.add(aliasSymbol.id);
|
|
44443
|
+
return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
|
|
43802
44444
|
}
|
|
44445
|
+
case "AssignmentExpression": return false;
|
|
43803
44446
|
case "Property":
|
|
43804
44447
|
if (parent.value !== valueNode) return false;
|
|
43805
44448
|
valueNode = parent;
|
|
@@ -43837,6 +44480,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43837
44480
|
severity: "warn",
|
|
43838
44481
|
recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
|
|
43839
44482
|
category: "Performance",
|
|
44483
|
+
tags: ["react-jsx-only"],
|
|
43840
44484
|
create: (context) => {
|
|
43841
44485
|
const settings = resolveSettings$8(context.settings);
|
|
43842
44486
|
const renderPropRegex = compileGlob(settings.propNamePattern);
|
|
@@ -43899,7 +44543,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43899
44543
|
},
|
|
43900
44544
|
Identifier(node) {
|
|
43901
44545
|
if (!isReactComponentName(node.name)) return;
|
|
43902
|
-
if (!isRenderFlowingReadReference(node)) return;
|
|
44546
|
+
if (!isRenderFlowingReadReference(node, context.scopes)) return;
|
|
43903
44547
|
recordInstantiation(node, node.name);
|
|
43904
44548
|
},
|
|
43905
44549
|
FunctionDeclaration: checkFunctionLike,
|
|
@@ -43908,28 +44552,33 @@ const noUnstableNestedComponents = defineRule({
|
|
|
43908
44552
|
ClassDeclaration(node) {
|
|
43909
44553
|
if (!node.id) return;
|
|
43910
44554
|
if (!isReactComponentName(node.id.name)) return;
|
|
43911
|
-
if (!isReactClassComponent(node)) return;
|
|
44555
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43912
44556
|
enqueueCandidate(node, null);
|
|
43913
44557
|
},
|
|
43914
44558
|
ClassExpression(node) {
|
|
43915
44559
|
const inferredName = node.id?.name ?? inferFunctionLikeName(node);
|
|
43916
44560
|
if (!inferredName || !isReactComponentName(inferredName)) return;
|
|
43917
|
-
if (!isReactClassComponent(node)) return;
|
|
44561
|
+
if (!isReactClassComponent(node, context.scopes)) return;
|
|
43918
44562
|
enqueueCandidate(node, null);
|
|
43919
44563
|
},
|
|
43920
44564
|
CallExpression(node) {
|
|
43921
|
-
if (
|
|
44565
|
+
if (isReactCreateElementCall(node, context.scopes)) {
|
|
43922
44566
|
const firstArgument = node.arguments[0];
|
|
43923
44567
|
if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
|
|
43924
44568
|
else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
|
|
43925
44569
|
}
|
|
43926
|
-
|
|
43927
|
-
if (!
|
|
43928
|
-
|
|
44570
|
+
const isReactLazy = isReactLazyCall(node, context.scopes);
|
|
44571
|
+
if (!isReactLazy && !isHocCallee$1(node)) return;
|
|
44572
|
+
if (!isReactLazy && !hocCallContainsComponent(node, context.scopes)) return;
|
|
44573
|
+
const inferredName = inferFunctionLikeName(node);
|
|
44574
|
+
const propInfo = isComponentDeclaredInProp(node);
|
|
44575
|
+
if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
|
|
44576
|
+
enqueueCandidate(node, propInfo === null ? inferredName : null);
|
|
43929
44577
|
},
|
|
43930
44578
|
"Program:exit"() {
|
|
43931
44579
|
for (const report of queuedReports) {
|
|
43932
44580
|
if (report.requiredInstantiationName !== null) {
|
|
44581
|
+
if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
|
|
43933
44582
|
if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
|
|
43934
44583
|
}
|
|
43935
44584
|
context.report({
|
|
@@ -44103,7 +44752,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
44103
44752
|
//#endregion
|
|
44104
44753
|
//#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
|
|
44105
44754
|
const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
|
|
44106
|
-
const MESSAGE$
|
|
44755
|
+
const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
44107
44756
|
const resolveSettings$7 = (settings) => {
|
|
44108
44757
|
const reactDoctor = settings?.["react-doctor"];
|
|
44109
44758
|
return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
|
|
@@ -44137,7 +44786,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
44137
44786
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
44138
44787
|
context.report({
|
|
44139
44788
|
node: node.callee,
|
|
44140
|
-
message: MESSAGE$
|
|
44789
|
+
message: MESSAGE$10
|
|
44141
44790
|
});
|
|
44142
44791
|
} };
|
|
44143
44792
|
}
|
|
@@ -44962,7 +45611,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
|
|
|
44962
45611
|
const symbol = scopes.symbolFor(identifier);
|
|
44963
45612
|
if (symbol) {
|
|
44964
45613
|
if (symbol.kind !== "parameter") return null;
|
|
44965
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45614
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
44966
45615
|
if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
|
|
44967
45616
|
return null;
|
|
44968
45617
|
}
|
|
@@ -45008,7 +45657,7 @@ const isChildrenMemberExpression = (node, scopes) => {
|
|
|
45008
45657
|
const symbol = scopes.symbolFor(propsObject);
|
|
45009
45658
|
if (symbol) {
|
|
45010
45659
|
if (symbol.kind !== "parameter") return false;
|
|
45011
|
-
const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
|
|
45660
|
+
const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
45012
45661
|
return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
|
|
45013
45662
|
}
|
|
45014
45663
|
return hasComponentLikeAncestorFunction(node);
|
|
@@ -45133,7 +45782,7 @@ const preactNoRenderArguments = defineRule({
|
|
|
45133
45782
|
});
|
|
45134
45783
|
//#endregion
|
|
45135
45784
|
//#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
|
|
45136
|
-
const MESSAGE$
|
|
45785
|
+
const MESSAGE$9 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
|
|
45137
45786
|
const preactPreferOndblclick = defineRule({
|
|
45138
45787
|
id: "preact-prefer-ondblclick",
|
|
45139
45788
|
title: "onDoubleClick instead of onDblClick",
|
|
@@ -45148,7 +45797,7 @@ const preactPreferOndblclick = defineRule({
|
|
|
45148
45797
|
if (!onDoubleClickAttribute) return;
|
|
45149
45798
|
context.report({
|
|
45150
45799
|
node: onDoubleClickAttribute,
|
|
45151
|
-
message: MESSAGE$
|
|
45800
|
+
message: MESSAGE$9
|
|
45152
45801
|
});
|
|
45153
45802
|
} })
|
|
45154
45803
|
});
|
|
@@ -45399,7 +46048,7 @@ const preferExplicitVariants = defineRule({
|
|
|
45399
46048
|
});
|
|
45400
46049
|
//#endregion
|
|
45401
46050
|
//#region src/plugin/rules/react-builtins/prefer-function-component.ts
|
|
45402
|
-
const MESSAGE$
|
|
46051
|
+
const MESSAGE$8 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
|
|
45403
46052
|
const resolveSettings$4 = (settings) => {
|
|
45404
46053
|
const reactDoctor = settings?.["react-doctor"];
|
|
45405
46054
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
|
|
@@ -45438,7 +46087,7 @@ const preferFunctionComponent = defineRule({
|
|
|
45438
46087
|
const reportNode = node.id ?? node;
|
|
45439
46088
|
context.report({
|
|
45440
46089
|
node: reportNode,
|
|
45441
|
-
message: MESSAGE$
|
|
46090
|
+
message: MESSAGE$8
|
|
45442
46091
|
});
|
|
45443
46092
|
};
|
|
45444
46093
|
return {
|
|
@@ -45660,7 +46309,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
|
|
|
45660
46309
|
//#endregion
|
|
45661
46310
|
//#region src/plugin/utils/enclosing-component-or-hook-scope.ts
|
|
45662
46311
|
const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
|
|
45663
|
-
const functionNode = findEnclosingFunction(startNode);
|
|
46312
|
+
const functionNode = findEnclosingFunction$1(startNode);
|
|
45664
46313
|
if (!functionNode) return null;
|
|
45665
46314
|
const displayName = componentOrHookDisplayNameForFunction(functionNode);
|
|
45666
46315
|
if (!displayName) return null;
|
|
@@ -46722,7 +47371,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
|
|
|
46722
47371
|
//#region src/plugin/rules/tanstack-query/query-destructure-result.ts
|
|
46723
47372
|
const isHookReturnForwardingSpread = (objectExpression) => {
|
|
46724
47373
|
const objectParent = objectExpression.parent;
|
|
46725
|
-
const enclosingFunction = findEnclosingFunction(objectExpression);
|
|
47374
|
+
const enclosingFunction = findEnclosingFunction$1(objectExpression);
|
|
46726
47375
|
if (!enclosingFunction) return false;
|
|
46727
47376
|
if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
|
|
46728
47377
|
const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
|
|
@@ -47100,10 +47749,10 @@ const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
|
47100
47749
|
return hasSuspension;
|
|
47101
47750
|
};
|
|
47102
47751
|
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47103
|
-
let enclosingFunction = findEnclosingFunction(functionNode);
|
|
47752
|
+
let enclosingFunction = findEnclosingFunction$1(functionNode);
|
|
47104
47753
|
while (enclosingFunction) {
|
|
47105
47754
|
if (enclosingFunction === ancestor) return true;
|
|
47106
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
47755
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47107
47756
|
}
|
|
47108
47757
|
return false;
|
|
47109
47758
|
};
|
|
@@ -47125,7 +47774,7 @@ const functionInvokesTarget = (callerFunction, targetFunction, context, visitedF
|
|
|
47125
47774
|
return invokesTarget;
|
|
47126
47775
|
};
|
|
47127
47776
|
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47128
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47777
|
+
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47129
47778
|
const boundaryStart = getRangeStart(boundary);
|
|
47130
47779
|
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47131
47780
|
let isInvokedBefore = false;
|
|
@@ -47162,11 +47811,11 @@ const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, conte
|
|
|
47162
47811
|
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47163
47812
|
const writeStart = getRangeStart(writeNode);
|
|
47164
47813
|
const boundaryStart = getRangeStart(boundary);
|
|
47165
|
-
const writeFunction = findEnclosingFunction(writeNode);
|
|
47814
|
+
const writeFunction = findEnclosingFunction$1(writeNode);
|
|
47166
47815
|
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47167
47816
|
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47168
|
-
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47169
|
-
const renderFunction = deferredExecutionFunction ? findEnclosingFunction(deferredExecutionFunction) : null;
|
|
47817
|
+
const boundaryFunction = findEnclosingFunction$1(boundary);
|
|
47818
|
+
const renderFunction = deferredExecutionFunction ? findEnclosingFunction$1(deferredExecutionFunction) : null;
|
|
47170
47819
|
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47171
47820
|
if (!writeFunction) return writeStart < boundaryStart;
|
|
47172
47821
|
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
@@ -47438,8 +48087,8 @@ const queryStableQueryClient = defineRule({
|
|
|
47438
48087
|
create: (context) => ({ NewExpression(node) {
|
|
47439
48088
|
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
|
|
47440
48089
|
if (isStableHookWrapperArgument(node)) return;
|
|
47441
|
-
let enclosingFunction = findEnclosingFunction(node);
|
|
47442
|
-
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
48090
|
+
let enclosingFunction = findEnclosingFunction$1(node);
|
|
48091
|
+
while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
47443
48092
|
if (!enclosingFunction) return;
|
|
47444
48093
|
if (!isComponentFunction(enclosingFunction)) return;
|
|
47445
48094
|
context.report({
|
|
@@ -47534,7 +48183,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
47534
48183
|
const comparatorArgument = node.arguments?.[1];
|
|
47535
48184
|
if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
|
|
47536
48185
|
} else {
|
|
47537
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
48186
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
47538
48187
|
if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
|
|
47539
48188
|
}
|
|
47540
48189
|
const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
|
|
@@ -47547,7 +48196,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
47547
48196
|
});
|
|
47548
48197
|
//#endregion
|
|
47549
48198
|
//#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
|
|
47550
|
-
const MESSAGE$
|
|
48199
|
+
const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
|
|
47551
48200
|
const reactInJsxScope = defineRule({
|
|
47552
48201
|
id: "react-in-jsx-scope",
|
|
47553
48202
|
title: "React not in scope for JSX",
|
|
@@ -47559,19 +48208,190 @@ const reactInJsxScope = defineRule({
|
|
|
47559
48208
|
if (findVariableInitializer(node, "React")) return;
|
|
47560
48209
|
context.report({
|
|
47561
48210
|
node: node.name,
|
|
47562
|
-
message: MESSAGE$
|
|
48211
|
+
message: MESSAGE$7
|
|
47563
48212
|
});
|
|
47564
48213
|
},
|
|
47565
48214
|
JSXFragment(node) {
|
|
47566
48215
|
if (findVariableInitializer(node, "React")) return;
|
|
47567
48216
|
context.report({
|
|
47568
48217
|
node: node.openingFragment,
|
|
47569
|
-
message: MESSAGE$
|
|
48218
|
+
message: MESSAGE$7
|
|
47570
48219
|
});
|
|
47571
48220
|
}
|
|
47572
48221
|
})
|
|
47573
48222
|
});
|
|
47574
48223
|
//#endregion
|
|
48224
|
+
//#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
|
|
48225
|
+
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.";
|
|
48226
|
+
const REACT_MARKDOWN_MODULE = "react-markdown";
|
|
48227
|
+
const REHYPE_RAW_MODULE = "rehype-raw";
|
|
48228
|
+
const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
|
|
48229
|
+
const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
|
|
48230
|
+
const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
|
|
48231
|
+
const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
|
|
48232
|
+
const DEFAULT_EXPORT_NAMES = new Set(["default"]);
|
|
48233
|
+
const getImportDeclaration = (symbol) => {
|
|
48234
|
+
if (symbol.kind !== "import") return null;
|
|
48235
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
48236
|
+
return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
|
|
48237
|
+
};
|
|
48238
|
+
const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
|
|
48239
|
+
const isDefaultImportSymbol = (symbol, moduleName) => {
|
|
48240
|
+
if (!isImportFromModule(symbol, moduleName)) return false;
|
|
48241
|
+
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
48242
|
+
};
|
|
48243
|
+
const resolveImportedIdentifier = (node, scopes) => {
|
|
48244
|
+
if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
|
|
48245
|
+
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
48246
|
+
return symbol?.kind === "import" ? symbol : null;
|
|
48247
|
+
};
|
|
48248
|
+
const getStaticMemberParts = (node) => {
|
|
48249
|
+
if (isNodeOfType(node, "MemberExpression")) {
|
|
48250
|
+
if (node.computed || !isNodeOfType(node.property, "Identifier")) return null;
|
|
48251
|
+
return {
|
|
48252
|
+
object: node.object,
|
|
48253
|
+
propertyName: node.property.name
|
|
48254
|
+
};
|
|
48255
|
+
}
|
|
48256
|
+
if (isNodeOfType(node, "JSXMemberExpression")) {
|
|
48257
|
+
if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
|
|
48258
|
+
return {
|
|
48259
|
+
object: node.object,
|
|
48260
|
+
propertyName: node.property.name
|
|
48261
|
+
};
|
|
48262
|
+
}
|
|
48263
|
+
return null;
|
|
48264
|
+
};
|
|
48265
|
+
const isNamespaceMemberFromModule = (node, moduleName, memberNames, scopes) => {
|
|
48266
|
+
const memberParts = getStaticMemberParts(node);
|
|
48267
|
+
if (!memberParts || !memberNames.has(memberParts.propertyName)) return false;
|
|
48268
|
+
const namespaceSymbol = resolveImportedIdentifier(memberParts.object, scopes);
|
|
48269
|
+
return Boolean(namespaceSymbol && isImportFromModule(namespaceSymbol, moduleName) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
|
|
48270
|
+
};
|
|
48271
|
+
const isReactMarkdownComponent = (node, scopes) => {
|
|
48272
|
+
if (isNodeOfType(node, "JSXIdentifier")) {
|
|
48273
|
+
const symbol = resolveImportedIdentifier(node, scopes);
|
|
48274
|
+
if (!symbol || !isImportFromModule(symbol, REACT_MARKDOWN_MODULE)) return false;
|
|
48275
|
+
if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return true;
|
|
48276
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
48277
|
+
return importedName === "default" || Boolean(importedName && REACT_MARKDOWN_NAMED_EXPORTS.has(importedName));
|
|
48278
|
+
}
|
|
48279
|
+
return isNamespaceMemberFromModule(node, REACT_MARKDOWN_MODULE, REACT_MARKDOWN_NAMESPACE_EXPORTS, scopes);
|
|
48280
|
+
};
|
|
48281
|
+
const isPluginFromModule = (rawNode, moduleName, scopes, visitedSymbolIds) => {
|
|
48282
|
+
const node = stripParenExpression(rawNode);
|
|
48283
|
+
if (isNodeOfType(node, "Identifier")) {
|
|
48284
|
+
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
48285
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
48286
|
+
if (isDefaultImportSymbol(symbol, moduleName)) return true;
|
|
48287
|
+
if (symbol.kind !== "const" || !symbol.initializer) return false;
|
|
48288
|
+
visitedSymbolIds.add(symbol.id);
|
|
48289
|
+
return isPluginFromModule(symbol.initializer, moduleName, scopes, visitedSymbolIds);
|
|
48290
|
+
}
|
|
48291
|
+
if (isNamespaceMemberFromModule(node, moduleName, DEFAULT_EXPORT_NAMES, scopes)) return true;
|
|
48292
|
+
if (!isNodeOfType(node, "ArrayExpression")) return false;
|
|
48293
|
+
for (const element of node.elements) {
|
|
48294
|
+
if (!element || isNodeOfType(element, "SpreadElement")) continue;
|
|
48295
|
+
return isPluginFromModule(element, moduleName, scopes, visitedSymbolIds);
|
|
48296
|
+
}
|
|
48297
|
+
return false;
|
|
48298
|
+
};
|
|
48299
|
+
const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
|
|
48300
|
+
const node = stripParenExpression(rawNode);
|
|
48301
|
+
if (isNodeOfType(node, "Identifier")) {
|
|
48302
|
+
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
48303
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
48304
|
+
visitedSymbolIds.add(symbol.id);
|
|
48305
|
+
return collectPluginEntries(symbol.initializer, scopes, visitedSymbolIds);
|
|
48306
|
+
}
|
|
48307
|
+
if (!isNodeOfType(node, "ArrayExpression")) return null;
|
|
48308
|
+
const entries = [];
|
|
48309
|
+
for (const element of node.elements) {
|
|
48310
|
+
if (!element) continue;
|
|
48311
|
+
if (isNodeOfType(element, "SpreadElement")) {
|
|
48312
|
+
const spreadEntries = collectPluginEntries(element.argument, scopes, new Set(visitedSymbolIds));
|
|
48313
|
+
if (spreadEntries === null) return null;
|
|
48314
|
+
entries.push(...spreadEntries);
|
|
48315
|
+
continue;
|
|
48316
|
+
}
|
|
48317
|
+
entries.push(element);
|
|
48318
|
+
}
|
|
48319
|
+
return entries;
|
|
48320
|
+
};
|
|
48321
|
+
const findEffectiveExplicitAttribute = (attributes, attributeName) => {
|
|
48322
|
+
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
48323
|
+
const attribute = attributes[attributeIndex];
|
|
48324
|
+
if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
|
|
48325
|
+
if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
|
|
48326
|
+
}
|
|
48327
|
+
return null;
|
|
48328
|
+
};
|
|
48329
|
+
const getAttributeExpression = (attribute) => {
|
|
48330
|
+
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
48331
|
+
return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
|
|
48332
|
+
};
|
|
48333
|
+
const isDomPurifyNamespace = (node, scopes) => {
|
|
48334
|
+
const symbol = resolveImportedIdentifier(node, scopes);
|
|
48335
|
+
if (!symbol) return false;
|
|
48336
|
+
const importDeclaration = getImportDeclaration(symbol);
|
|
48337
|
+
if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
|
|
48338
|
+
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
48339
|
+
};
|
|
48340
|
+
const isDomPurifySanitizeCall = (node, scopes) => {
|
|
48341
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
48342
|
+
const memberParts = getStaticMemberParts(stripParenExpression(node.callee));
|
|
48343
|
+
return Boolean(memberParts && memberParts.propertyName === "sanitize" && isDomPurifyNamespace(memberParts.object, scopes));
|
|
48344
|
+
};
|
|
48345
|
+
const isStaticOrSanitizedMarkdownExpression = (rawNode, scopes, visitedSymbolIds) => {
|
|
48346
|
+
const node = stripParenExpression(rawNode);
|
|
48347
|
+
if (isNodeOfType(node, "Literal")) return true;
|
|
48348
|
+
if (isNodeOfType(node, "TemplateLiteral")) return node.expressions.every((expression) => isStaticOrSanitizedMarkdownExpression(expression, scopes, new Set(visitedSymbolIds)));
|
|
48349
|
+
if (isDomPurifySanitizeCall(node, scopes)) return true;
|
|
48350
|
+
if (isNodeOfType(node, "ConditionalExpression")) return isStaticOrSanitizedMarkdownExpression(node.consequent, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.alternate, scopes, new Set(visitedSymbolIds));
|
|
48351
|
+
if (isNodeOfType(node, "BinaryExpression") && node.operator === "+") return isStaticOrSanitizedMarkdownExpression(node.left, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.right, scopes, new Set(visitedSymbolIds));
|
|
48352
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
48353
|
+
const symbol = scopes.referenceFor(node)?.resolvedSymbol;
|
|
48354
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
48355
|
+
visitedSymbolIds.add(symbol.id);
|
|
48356
|
+
return isStaticOrSanitizedMarkdownExpression(symbol.initializer, scopes, visitedSymbolIds);
|
|
48357
|
+
};
|
|
48358
|
+
const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
|
|
48359
|
+
const jsxElement = openingElement.parent;
|
|
48360
|
+
if (!isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
48361
|
+
const meaningfulChildren = jsxElement.children.filter((child) => !isNodeOfType(child, "JSXText") || child.value.trim().length > 0);
|
|
48362
|
+
if (meaningfulChildren.length > 0) return meaningfulChildren.some((child) => {
|
|
48363
|
+
if (isNodeOfType(child, "JSXText")) return false;
|
|
48364
|
+
if (!isNodeOfType(child, "JSXExpressionContainer")) return true;
|
|
48365
|
+
if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
|
|
48366
|
+
return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
|
|
48367
|
+
});
|
|
48368
|
+
const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
|
|
48369
|
+
if (!childrenAttribute) return false;
|
|
48370
|
+
const childrenExpression = getAttributeExpression(childrenAttribute);
|
|
48371
|
+
return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
|
|
48372
|
+
};
|
|
48373
|
+
const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
48374
|
+
id: "react-markdown-unsanitized-raw-html",
|
|
48375
|
+
title: "Unsanitized raw HTML in React Markdown",
|
|
48376
|
+
severity: "warn",
|
|
48377
|
+
recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
|
|
48378
|
+
create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
|
|
48379
|
+
if (!isReactMarkdownComponent(node.name, context.scopes)) return;
|
|
48380
|
+
const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
|
|
48381
|
+
if (!pluginsAttribute) return;
|
|
48382
|
+
const pluginsExpression = getAttributeExpression(pluginsAttribute);
|
|
48383
|
+
if (!pluginsExpression) return;
|
|
48384
|
+
const pluginEntries = collectPluginEntries(pluginsExpression, context.scopes, /* @__PURE__ */ new Set());
|
|
48385
|
+
if (pluginEntries === null) return;
|
|
48386
|
+
if (!pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_RAW_MODULE, context.scopes, /* @__PURE__ */ new Set()))) return;
|
|
48387
|
+
if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
|
|
48388
|
+
context.report({
|
|
48389
|
+
node,
|
|
48390
|
+
message: MESSAGE$6
|
|
48391
|
+
});
|
|
48392
|
+
} }))
|
|
48393
|
+
});
|
|
48394
|
+
//#endregion
|
|
47575
48395
|
//#region src/plugin/utils/collect-react-redux-selector-aliases.ts
|
|
47576
48396
|
const REACT_REDUX_MODULE = "react-redux";
|
|
47577
48397
|
const collectReactReduxSelectorAliases = (programRoot) => {
|
|
@@ -50468,7 +51288,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
|
|
|
50468
51288
|
};
|
|
50469
51289
|
const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
|
|
50470
51290
|
const isInsideStyleFactoryCallback = (node) => {
|
|
50471
|
-
const enclosingFunction = findEnclosingFunction(node);
|
|
51291
|
+
const enclosingFunction = findEnclosingFunction$1(node);
|
|
50472
51292
|
if (!enclosingFunction) return false;
|
|
50473
51293
|
const callExpression = enclosingFunction.parent;
|
|
50474
51294
|
if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
|
|
@@ -57324,12 +58144,14 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
|
|
|
57324
58144
|
}
|
|
57325
58145
|
return false;
|
|
57326
58146
|
};
|
|
57327
|
-
const declarationAwaitsGate = (declaration) => {
|
|
58147
|
+
const declarationAwaitsGate = (declaration, context) => {
|
|
57328
58148
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
57329
58149
|
for (const declarator of declaration.declarations ?? []) {
|
|
57330
58150
|
if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
|
|
57331
58151
|
const argument = declarator.init.argument;
|
|
57332
58152
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
58153
|
+
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
58154
|
+
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
57333
58155
|
const calleeName = getCalleeName$2(argument);
|
|
57334
58156
|
if (!calleeName) continue;
|
|
57335
58157
|
if (isAuthGuardName(calleeName)) return true;
|
|
@@ -57357,7 +58179,7 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
57357
58179
|
if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
|
|
57358
58180
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
57359
58181
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
57360
|
-
if (declarationAwaitsGate(currentStatement)) continue;
|
|
58182
|
+
if (declarationAwaitsGate(currentStatement, context)) continue;
|
|
57361
58183
|
context.report({
|
|
57362
58184
|
node: nextStatement,
|
|
57363
58185
|
message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
|
|
@@ -58343,7 +59165,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
58343
59165
|
const isReturnedFromCustomHook = (functionNode) => {
|
|
58344
59166
|
const parent = functionNode.parent;
|
|
58345
59167
|
if (isNodeOfType(parent, "ReturnStatement")) {
|
|
58346
|
-
const outerFunction = findEnclosingFunction(parent);
|
|
59168
|
+
const outerFunction = findEnclosingFunction$1(parent);
|
|
58347
59169
|
const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
|
|
58348
59170
|
return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
|
|
58349
59171
|
}
|
|
@@ -58360,13 +59182,13 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
58360
59182
|
return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
|
|
58361
59183
|
};
|
|
58362
59184
|
const isDeferredNavigateCall = (callNode) => {
|
|
58363
|
-
let enclosingFunction = findEnclosingFunction(callNode);
|
|
59185
|
+
let enclosingFunction = findEnclosingFunction$1(callNode);
|
|
58364
59186
|
while (enclosingFunction) {
|
|
58365
59187
|
if (isDeferredCallbackPosition(enclosingFunction)) return true;
|
|
58366
59188
|
if (isWiredAsEventHandler(enclosingFunction)) return true;
|
|
58367
59189
|
if (isReturnedFromCustomHook(enclosingFunction)) return true;
|
|
58368
59190
|
if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
|
|
58369
|
-
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
59191
|
+
enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
58370
59192
|
}
|
|
58371
59193
|
return false;
|
|
58372
59194
|
};
|
|
@@ -62805,6 +63627,17 @@ const reactDoctorRules = [
|
|
|
62805
63627
|
requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
|
|
62806
63628
|
}
|
|
62807
63629
|
},
|
|
63630
|
+
{
|
|
63631
|
+
key: "react-doctor/react-markdown-unsanitized-raw-html",
|
|
63632
|
+
id: "react-markdown-unsanitized-raw-html",
|
|
63633
|
+
source: "react-doctor",
|
|
63634
|
+
originallyExternal: false,
|
|
63635
|
+
rule: {
|
|
63636
|
+
...reactMarkdownUnsanitizedRawHtml,
|
|
63637
|
+
framework: "global",
|
|
63638
|
+
category: "Security"
|
|
63639
|
+
}
|
|
63640
|
+
},
|
|
62808
63641
|
{
|
|
62809
63642
|
key: "react-doctor/redux-useselector-inline-derivation",
|
|
62810
63643
|
id: "redux-useselector-inline-derivation",
|