oxlint-plugin-react-doctor 0.7.6-dev.17418 → 0.7.6-dev.1f14fa1

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.
Files changed (2) hide show
  1. package/dist/index.js +692 -202
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1180,8 +1180,8 @@ const stripParenExpression = (node) => {
1180
1180
  return current;
1181
1181
  };
1182
1182
  //#endregion
1183
- //#region src/plugin/utils/get-root-identifier-name.ts
1184
- const getRootIdentifierName = (node, options) => {
1183
+ //#region src/plugin/utils/get-root-identifier.ts
1184
+ const getRootIdentifier$1 = (node, options) => {
1185
1185
  if (!node) return null;
1186
1186
  const followCallChains = options?.followCallChains === true;
1187
1187
  let cursor = node;
@@ -1192,16 +1192,18 @@ const getRootIdentifierName = (node, options) => {
1192
1192
  continue;
1193
1193
  }
1194
1194
  if (followCallChains && isNodeOfType(cursor, "CallExpression")) {
1195
- const callee = cursor.callee;
1196
- if (!isNodeOfType(callee, "MemberExpression")) return null;
1197
- cursor = callee.object;
1195
+ if (!isNodeOfType(cursor.callee, "MemberExpression")) return null;
1196
+ cursor = cursor.callee.object;
1198
1197
  continue;
1199
1198
  }
1200
1199
  break;
1201
1200
  }
1202
- return isNodeOfType(cursor, "Identifier") ? cursor.name : null;
1201
+ return isNodeOfType(cursor, "Identifier") ? cursor : null;
1203
1202
  };
1204
1203
  //#endregion
1204
+ //#region src/plugin/utils/get-root-identifier-name.ts
1205
+ const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
1206
+ //#endregion
1205
1207
  //#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
1206
1208
  const STABLE_HANDLER_HOOK_NAMES = new Set([
1207
1209
  "useCallback",
@@ -1518,6 +1520,37 @@ const hasJsxPropIgnoreCase = (attributes, targetProp) => {
1518
1520
  }
1519
1521
  };
1520
1522
  //#endregion
1523
+ //#region src/plugin/utils/is-uppercase-name.ts
1524
+ const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
1525
+ //#endregion
1526
+ //#region src/plugin/utils/resolve-jsx-element-type.ts
1527
+ const resolveConstantStringBinding = (name) => {
1528
+ const binding = findVariableInitializer(name, name.name);
1529
+ if (!binding?.initializer) return null;
1530
+ const declarator = binding.bindingIdentifier.parent;
1531
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
1532
+ if (declarator.id !== binding.bindingIdentifier) return null;
1533
+ const declaration = declarator.parent;
1534
+ if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
1535
+ if (declaration.kind !== "const") return null;
1536
+ const initializer = stripParenExpression(binding.initializer);
1537
+ return isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
1538
+ };
1539
+ const flattenJsxName$2 = (name) => {
1540
+ if (isNodeOfType(name, "JSXIdentifier")) return name.name;
1541
+ if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName$2(name.object)}.${name.property.name}`;
1542
+ if (isNodeOfType(name, "JSXNamespacedName")) return `${name.namespace.name}:${name.name.name}`;
1543
+ return "";
1544
+ };
1545
+ const resolveJsxElementType = (openingElement) => {
1546
+ const name = openingElement.name;
1547
+ if (isNodeOfType(name, "JSXIdentifier")) {
1548
+ if (!isUppercaseName(name.name)) return name.name;
1549
+ return resolveConstantStringBinding(name) ?? name.name;
1550
+ }
1551
+ return flattenJsxName$2(name);
1552
+ };
1553
+ //#endregion
1521
1554
  //#region src/plugin/utils/get-element-type.ts
1522
1555
  const EMPTY_JSX_A11Y_SETTINGS = Object.freeze({});
1523
1556
  const jsxA11ySettingsCache = /* @__PURE__ */ new WeakMap();
@@ -1530,14 +1563,8 @@ const readJsxA11ySettings = (settings) => {
1530
1563
  jsxA11ySettingsCache.set(settings, a11ySettings);
1531
1564
  return a11ySettings;
1532
1565
  };
1533
- const flattenJsxName$2 = (name) => {
1534
- if (isNodeOfType(name, "JSXIdentifier")) return name.name;
1535
- if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName$2(name.object)}.${name.property.name}`;
1536
- if (isNodeOfType(name, "JSXNamespacedName")) return `${name.namespace.name}:${name.name.name}`;
1537
- return "";
1538
- };
1539
1566
  const computeElementType = (openingElement, a11ySettings) => {
1540
- const baseName = flattenJsxName$2(openingElement.name);
1567
+ const baseName = resolveJsxElementType(openingElement);
1541
1568
  if (a11ySettings.polymorphicPropName) {
1542
1569
  const polymorphicAttribute = hasJsxPropIgnoreCase(openingElement.attributes, a11ySettings.polymorphicPropName);
1543
1570
  if (polymorphicAttribute) {
@@ -2372,8 +2399,9 @@ const anchorIsValid = defineRule({
2372
2399
  const isTestlikeFile = isTestlikeFilename(context.filename);
2373
2400
  return { JSXOpeningElement(node) {
2374
2401
  if (isTestlikeFile) return;
2375
- if (!fileHasJsxA11ySettings && (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a")) return;
2376
- if (getElementType(node, context.settings) !== "a") return;
2402
+ const tag = getElementType(node, context.settings);
2403
+ if (!fileHasJsxA11ySettings && tag !== "a") return;
2404
+ if (tag !== "a") return;
2377
2405
  let hrefAttribute;
2378
2406
  for (const attributeName of settings.hrefAttributeNames) {
2379
2407
  hrefAttribute = hasJsxPropIgnoreCase(node.attributes, attributeName);
@@ -5728,7 +5756,7 @@ const buttonHasType = defineRule({
5728
5756
  return {
5729
5757
  JSXOpeningElement(node) {
5730
5758
  if (isTestlikeFile) return;
5731
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "button") return;
5759
+ if (resolveJsxElementType(node) !== "button") return;
5732
5760
  const typeAttr = hasJsxPropIgnoreCase(node.attributes, "type");
5733
5761
  if (!typeAttr) {
5734
5762
  if (hasJsxSpreadAttribute$1(node.attributes)) {
@@ -5918,7 +5946,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5918
5946
  };
5919
5947
  return {
5920
5948
  JSXOpeningElement(node) {
5921
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
5949
+ if (resolveJsxElementType(node) !== "input") return;
5922
5950
  reportFromPresence(collectFromJsxAttributes(node.attributes));
5923
5951
  },
5924
5952
  CallExpression(node) {
@@ -5995,7 +6023,7 @@ const isPureEventBlockerHandler = (attribute) => {
5995
6023
  //#endregion
5996
6024
  //#region src/plugin/utils/resolve-const-identifier-alias.ts
5997
6025
  const resolveConstIdentifierAlias = (identifier, scopes) => {
5998
- if (!isNodeOfType(identifier, "Identifier")) return null;
6026
+ if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
5999
6027
  const visitedSymbolIds = /* @__PURE__ */ new Set();
6000
6028
  let symbol = scopes.symbolFor(identifier);
6001
6029
  while (symbol?.kind === "const") {
@@ -6270,6 +6298,131 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
6270
6298
  return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6271
6299
  };
6272
6300
  //#endregion
6301
+ //#region src/plugin/utils/collect-safely-validated-local-storage-keys.ts
6302
+ const isLocalStorageCall = (node, methodName) => {
6303
+ if (!isNodeOfType(node, "CallExpression")) return false;
6304
+ const callee = stripParenExpression(node.callee);
6305
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
6306
+ const receiver = stripParenExpression(callee.object);
6307
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "localStorage" && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6308
+ };
6309
+ const catchReturnsFallback = (tryStatement) => {
6310
+ const handler = tryStatement.handler;
6311
+ if (!handler) return false;
6312
+ let hasReturn = false;
6313
+ let hasThrow = false;
6314
+ walkAst(handler.body, (child) => {
6315
+ if (isFunctionLike$1(child)) return false;
6316
+ if (isNodeOfType(child, "ReturnStatement")) hasReturn = true;
6317
+ if (isNodeOfType(child, "ThrowStatement")) hasThrow = true;
6318
+ });
6319
+ return hasReturn && !hasThrow;
6320
+ };
6321
+ const resolveValidatorFunction = (callee, scopes) => {
6322
+ const unwrappedCallee = stripParenExpression(callee);
6323
+ if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
6324
+ const symbol = scopes.symbolFor(unwrappedCallee);
6325
+ if (!symbol) return null;
6326
+ const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
6327
+ return isFunctionLike$1(candidate) ? candidate : null;
6328
+ };
6329
+ const validatorChecksPayloadProperties = (validatorFunction, scopes) => {
6330
+ if (!isFunctionLike$1(validatorFunction)) return false;
6331
+ const firstParameter = validatorFunction.params?.[0];
6332
+ if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
6333
+ const parameterSymbol = scopes.symbolFor(firstParameter);
6334
+ if (!parameterSymbol) return false;
6335
+ const payloadSymbolIds = new Set([parameterSymbol.id]);
6336
+ let hasPropertyTypeCheck = false;
6337
+ walkAst(validatorFunction.body, (child) => {
6338
+ if (isFunctionLike$1(child)) return false;
6339
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init) {
6340
+ const initializer = stripParenExpression(child.init);
6341
+ if (isNodeOfType(initializer, "Identifier")) {
6342
+ const initializerSymbol = scopes.symbolFor(initializer);
6343
+ if (initializerSymbol && payloadSymbolIds.has(initializerSymbol.id)) {
6344
+ const aliasSymbol = scopes.symbolFor(child.id);
6345
+ if (aliasSymbol) payloadSymbolIds.add(aliasSymbol.id);
6346
+ }
6347
+ }
6348
+ }
6349
+ if (!isNodeOfType(child, "UnaryExpression") || child.operator !== "typeof") return;
6350
+ const checkedValue = stripParenExpression(child.argument);
6351
+ if (!isNodeOfType(checkedValue, "MemberExpression")) return;
6352
+ const receiver = stripParenExpression(checkedValue.object);
6353
+ if (!isNodeOfType(receiver, "Identifier")) return;
6354
+ const receiverSymbol = scopes.symbolFor(receiver);
6355
+ if (receiverSymbol && payloadSymbolIds.has(receiverSymbol.id)) hasPropertyTypeCheck = true;
6356
+ });
6357
+ return hasPropertyTypeCheck;
6358
+ };
6359
+ const incrementCount = (counts, keyValue) => {
6360
+ counts.set(keyValue, (counts.get(keyValue) ?? 0) + 1);
6361
+ };
6362
+ const isReturnedExpression = (expression) => {
6363
+ const parent = expression.parent;
6364
+ return Boolean(parent && isNodeOfType(parent, "ReturnStatement") && parent.argument === expression);
6365
+ };
6366
+ const collectSafelyValidatedLocalStorageKeys = ({ programNode, scopes, resolveKey }) => {
6367
+ const readCountsByKey = /* @__PURE__ */ new Map();
6368
+ const safeReadCountsByKey = /* @__PURE__ */ new Map();
6369
+ const safeReadSymbolIds = /* @__PURE__ */ new Set();
6370
+ walkAst(programNode, (child) => {
6371
+ if (!isNodeOfType(child, "CallExpression") || !isLocalStorageCall(child, "getItem")) return;
6372
+ const keyArgument = child.arguments?.[0];
6373
+ if (!keyArgument) return;
6374
+ const keyValue = resolveKey(keyArgument);
6375
+ if (keyValue !== null) incrementCount(readCountsByKey, keyValue);
6376
+ });
6377
+ walkAst(programNode, (child) => {
6378
+ if (!isNodeOfType(child, "TryStatement") || !catchReturnsFallback(child)) return;
6379
+ const rawValueKeys = /* @__PURE__ */ new Map();
6380
+ const parsedValueSources = /* @__PURE__ */ new Map();
6381
+ walkAst(child.block, (tryChild) => {
6382
+ if (isFunctionLike$1(tryChild)) return false;
6383
+ if (isNodeOfType(tryChild, "VariableDeclarator") && isNodeOfType(tryChild.id, "Identifier") && tryChild.init) {
6384
+ const initializer = stripParenExpression(tryChild.init);
6385
+ if (isNodeOfType(initializer, "CallExpression") && isLocalStorageCall(initializer, "getItem")) {
6386
+ const keyArgument = initializer.arguments?.[0];
6387
+ const bindingSymbol = scopes.symbolFor(tryChild.id);
6388
+ if (keyArgument && bindingSymbol) {
6389
+ const keyValue = resolveKey(keyArgument);
6390
+ if (keyValue !== null) rawValueKeys.set(bindingSymbol.id, keyValue);
6391
+ }
6392
+ }
6393
+ if (isNodeOfType(initializer, "CallExpression") && isGlobalMethodCall(initializer, "JSON", "parse")) {
6394
+ const rawValueArgument = initializer.arguments?.[0];
6395
+ const parsedValueSymbol = scopes.symbolFor(tryChild.id);
6396
+ if (rawValueArgument && isNodeOfType(rawValueArgument, "Identifier") && parsedValueSymbol) {
6397
+ const rawValueSymbol = scopes.symbolFor(rawValueArgument);
6398
+ if (rawValueSymbol && rawValueKeys.has(rawValueSymbol.id)) parsedValueSources.set(parsedValueSymbol.id, rawValueSymbol.id);
6399
+ }
6400
+ }
6401
+ }
6402
+ if (!isNodeOfType(tryChild, "ConditionalExpression") || !isReturnedExpression(tryChild)) return;
6403
+ const test = stripParenExpression(tryChild.test);
6404
+ if (!isNodeOfType(test, "CallExpression")) return;
6405
+ const testedValue = test.arguments?.[0];
6406
+ if (!testedValue || !isNodeOfType(testedValue, "Identifier")) return;
6407
+ const parsedValueSymbol = scopes.symbolFor(testedValue);
6408
+ if (!parsedValueSymbol) return;
6409
+ const rawValueSymbolId = parsedValueSources.get(parsedValueSymbol.id);
6410
+ if (rawValueSymbolId === void 0) return;
6411
+ const returnedValue = stripParenExpression(tryChild.consequent);
6412
+ if (!isNodeOfType(returnedValue, "Identifier") || scopes.symbolFor(returnedValue)?.id !== parsedValueSymbol.id) return;
6413
+ const validatorFunction = resolveValidatorFunction(test.callee, scopes);
6414
+ if (!validatorFunction || !validatorChecksPayloadProperties(validatorFunction, scopes)) return;
6415
+ const keyValue = rawValueKeys.get(rawValueSymbolId);
6416
+ if (keyValue === void 0 || safeReadSymbolIds.has(rawValueSymbolId)) return;
6417
+ safeReadSymbolIds.add(rawValueSymbolId);
6418
+ incrementCount(safeReadCountsByKey, keyValue);
6419
+ });
6420
+ });
6421
+ const safeKeys = /* @__PURE__ */ new Set();
6422
+ for (const [keyValue, readCount] of readCountsByKey) if (safeReadCountsByKey.get(keyValue) === readCount) safeKeys.add(keyValue);
6423
+ return safeKeys;
6424
+ };
6425
+ //#endregion
6273
6426
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6274
6427
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6275
6428
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
@@ -6291,26 +6444,39 @@ const clientLocalstorageNoVersion = defineRule({
6291
6444
  severity: "warn",
6292
6445
  category: "Correctness",
6293
6446
  recommendation: "Put a version in the storage key (e.g. \"myKey:v1\"). If you change the data shape later, old saved data can be ignored instead of crashing the app.",
6294
- create: (context) => ({ CallExpression(node) {
6295
- if (!isNodeOfType(node.callee, "MemberExpression")) return;
6296
- const receiver = stripParenExpression(node.callee.object);
6297
- if (!isNodeOfType(receiver, "Identifier")) return;
6298
- if (receiver.name !== "localStorage") return;
6299
- if (!isNodeOfType(node.callee.property, "Identifier")) return;
6300
- if (node.callee.property.name !== "setItem") return;
6301
- const keyArg = node.arguments?.[0];
6302
- if (!keyArg) return;
6303
- const keyValue = resolveStringKey(keyArg, context);
6304
- if (keyValue === null) return;
6305
- if (isVersionedKey(keyValue)) return;
6306
- const valueArg = node.arguments?.[1];
6307
- if (!valueArg) return;
6308
- if (!isJsonStringifyCall(valueArg)) return;
6309
- context.report({
6310
- node: keyArg,
6311
- message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
6312
- });
6313
- } })
6447
+ create: (context) => {
6448
+ let safelyValidatedStorageKeys = /* @__PURE__ */ new Set();
6449
+ return {
6450
+ Program(node) {
6451
+ safelyValidatedStorageKeys = collectSafelyValidatedLocalStorageKeys({
6452
+ programNode: node,
6453
+ scopes: context.scopes,
6454
+ resolveKey: (keyNode) => resolveStringKey(keyNode, context)
6455
+ });
6456
+ },
6457
+ CallExpression(node) {
6458
+ if (!isNodeOfType(node.callee, "MemberExpression")) return;
6459
+ const receiver = stripParenExpression(node.callee.object);
6460
+ if (!isNodeOfType(receiver, "Identifier")) return;
6461
+ if (receiver.name !== "localStorage") return;
6462
+ if (!isNodeOfType(node.callee.property, "Identifier")) return;
6463
+ if (node.callee.property.name !== "setItem") return;
6464
+ const keyArg = node.arguments?.[0];
6465
+ if (!keyArg) return;
6466
+ const keyValue = resolveStringKey(keyArg, context);
6467
+ if (keyValue === null) return;
6468
+ if (isVersionedKey(keyValue)) return;
6469
+ if (safelyValidatedStorageKeys.has(keyValue)) return;
6470
+ const valueArg = node.arguments?.[1];
6471
+ if (!valueArg) return;
6472
+ if (!isJsonStringifyCall(valueArg)) return;
6473
+ context.report({
6474
+ node: keyArg,
6475
+ message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
6476
+ });
6477
+ }
6478
+ };
6479
+ }
6314
6480
  });
6315
6481
  //#endregion
6316
6482
  //#region src/plugin/rules/client/client-passive-event-listeners.ts
@@ -7146,12 +7312,17 @@ const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) =>
7146
7312
  }
7147
7313
  return nearestDeclaration;
7148
7314
  };
7315
+ const isDeclarationStable = (identifier, declaration, usageIndex, fileContent) => {
7316
+ if (declaration.isImmutable) return true;
7317
+ const textAfterInitializer = fileContent.slice(declaration.initializerStartIndex + declaration.initializer.length, usageIndex);
7318
+ return !new RegExp(`(?:^|[^\\w$.])${escapeRegExp(identifier)}\\s*=(?!=)`).test(textAfterInitializer);
7319
+ };
7149
7320
  const isTrustedHighlighterValue = (valueExpression, fileContent, sinkIndex) => {
7150
7321
  if (!/highlight/i.test(valueExpression)) return false;
7151
7322
  const identifier = valueExpression.match(/^([\w$]+)/)?.[1];
7152
7323
  if (identifier === void 0) return false;
7153
7324
  const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
7154
- if (declaration !== null) return SERIALIZER_CALL_PROVENANCE_PATTERN.test(declaration.initializer);
7325
+ if (declaration !== null) return isDeclarationStable(identifier, declaration, sinkIndex, fileContent) && SERIALIZER_CALL_PROVENANCE_PATTERN.test(declaration.initializer);
7155
7326
  return /highlighted/i.test(valueExpression) || HIGHLIGHTER_LIBRARY_PATTERN.test(fileContent);
7156
7327
  };
7157
7328
  const isExplicitlyTrustedHtmlValue = (valueExpression, fileContent, sinkIndex, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
@@ -7163,7 +7334,7 @@ const isExplicitlyTrustedHtmlValue = (valueExpression, fileContent, sinkIndex, v
7163
7334
  const identifier = trimmedExpression.match(/^([\w$]+)(?:(?:\??\.[\w$]+)|(?:\[\s*(?:"[^"\n]*"|'[^'\n]*'|\d+)\s*\]))*$/)?.[1];
7164
7335
  if (identifier && !visitedIdentifiers.has(identifier)) {
7165
7336
  const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
7166
- if (declaration) {
7337
+ if (declaration && isDeclarationStable(identifier, declaration, sinkIndex, fileContent)) {
7167
7338
  const nextVisitedIdentifiers = new Set(visitedIdentifiers);
7168
7339
  nextVisitedIdentifiers.add(identifier);
7169
7340
  return isExplicitlyTrustedHtmlValue(declaration.initializer, fileContent, declaration.initializerStartIndex, nextVisitedIdentifiers);
@@ -7230,6 +7401,16 @@ const splitTopLevelArguments = (argumentText) => {
7230
7401
  argumentsList.push(argumentText.slice(startIndex).trim());
7231
7402
  return argumentsList;
7232
7403
  };
7404
+ const isBackedByFunctionParameter = (expression, expressionIndex, fileContent, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
7405
+ const identifier = expression.trim().match(/^([\w$]+)$/)?.[1];
7406
+ if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
7407
+ if (findContainingFunctionParameterSource(identifier, expressionIndex, fileContent) !== null) return true;
7408
+ const declaration = findVisibleIdentifierDeclaration(identifier, expressionIndex, fileContent);
7409
+ if (!declaration || !isDeclarationStable(identifier, declaration, expressionIndex, fileContent)) return false;
7410
+ const nextVisitedIdentifiers = new Set(visitedIdentifiers);
7411
+ nextVisitedIdentifiers.add(identifier);
7412
+ return isBackedByFunctionParameter(declaration.initializer, declaration.initializerStartIndex, fileContent, nextVisitedIdentifiers);
7413
+ };
7233
7414
  const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers, visitedCallSites) => {
7234
7415
  const trimmedExpression = expression.trim();
7235
7416
  if (isExplicitlyTrustedHtmlValue(trimmedExpression, fileContent, sinkIndex)) return false;
@@ -7240,8 +7421,11 @@ const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers, v
7240
7421
  const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
7241
7422
  const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
7242
7423
  if (declaration !== null && (parameterSource === null || declaration.declarationIndex > parameterSource.declarationNameIndex)) {
7243
- if (isExplicitlyTrustedHtmlValue(declaration.initializer, fileContent, declaration.initializerStartIndex)) return false;
7244
- return isHtmlTainted(declaration.initializer, fileContent, declaration.initializerStartIndex, visitedIdentifiers, visitedCallSites) || HTML_TAINT_PATTERN.test(trimmedExpression);
7424
+ if (isDeclarationStable(identifier, declaration, sinkIndex, fileContent) && isExplicitlyTrustedHtmlValue(declaration.initializer, fileContent, declaration.initializerStartIndex)) return false;
7425
+ if (!isDeclarationStable(identifier, declaration, sinkIndex, fileContent)) return true;
7426
+ if (isHtmlTainted(declaration.initializer, fileContent, declaration.initializerStartIndex, visitedIdentifiers, visitedCallSites)) return true;
7427
+ if (isBackedByFunctionParameter(declaration.initializer, declaration.initializerStartIndex, fileContent)) return false;
7428
+ return HTML_TAINT_PATTERN.test(trimmedExpression);
7245
7429
  }
7246
7430
  if (parameterSource !== null && parameterSource.functionName.length > 0) {
7247
7431
  const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(`, "g");
@@ -7323,12 +7507,13 @@ const dangerousHtmlSink = defineRule({
7323
7507
  if (isTrustedHighlighterValue(valueExpression, file.content, sinkIndex)) continue;
7324
7508
  if (valueIdentifier !== void 0) {
7325
7509
  const escapedIdentifier = escapeRegExp(valueIdentifier);
7326
- const visibleInitializer = findVisibleIdentifierDeclaration(valueIdentifier, sinkIndex, file.content)?.initializer;
7510
+ const visibleDeclaration = findVisibleIdentifierDeclaration(valueIdentifier, sinkIndex, file.content);
7511
+ const visibleInitializer = visibleDeclaration?.initializer;
7327
7512
  if (!(visibleInitializer !== void 0 && (splitTopLevelByPlus(visibleInitializer).length > 1 || (visibleInitializer.match(/\$\{/g)?.length ?? 0) > 1))) {
7328
7513
  const fromSerializer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SERIALIZER_ASSIGNMENT_PATTERN.source}`, "i");
7329
- if (visibleInitializer === void 0 ? fromSerializer.test(file.content) : SERIALIZER_CALL_PROVENANCE_PATTERN.test(visibleInitializer)) continue;
7514
+ if (visibleInitializer === void 0 ? fromSerializer.test(file.content) : visibleDeclaration !== null && isDeclarationStable(valueIdentifier, visibleDeclaration, sinkIndex, file.content) && SERIALIZER_CALL_PROVENANCE_PATTERN.test(visibleInitializer)) continue;
7330
7515
  const fromSanitizer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SANITIZED_ASSIGNMENT_PATTERN.source}`, "i");
7331
- if (visibleInitializer === void 0 ? fromSanitizer.test(file.content) : SANITIZED_ASSIGNMENT_PATTERN.test(`=${visibleInitializer}`)) continue;
7516
+ if (visibleInitializer === void 0 ? fromSanitizer.test(file.content) : visibleDeclaration !== null && isDeclarationStable(valueIdentifier, visibleDeclaration, sinkIndex, file.content) && SANITIZED_ASSIGNMENT_PATTERN.test(`=${visibleInitializer}`)) continue;
7332
7517
  }
7333
7518
  if (new RegExp(`\\b${escapedIdentifier}\\b\\s*${DOM_CONTENT_ASSIGNMENT_PATTERN.source}`).test(file.content)) continue;
7334
7519
  if (new RegExp(`highlight[\\w$]*\\s*\\.map\\(\\s*(?:async\\s+)?\\(?\\s*${escapedIdentifier}\\b`, "i").test(file.content) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
@@ -7414,7 +7599,7 @@ const findJsxAttribute = (attributes, attributeName) => attributes?.find((attrib
7414
7599
  const getOpeningElementTagName = (openingElement) => {
7415
7600
  if (!openingElement) return null;
7416
7601
  if (!isNodeOfType(openingElement, "JSXOpeningElement")) return null;
7417
- if (isNodeOfType(openingElement.name, "JSXIdentifier")) return openingElement.name.name;
7602
+ if (isNodeOfType(openingElement.name, "JSXIdentifier")) return resolveJsxElementType(openingElement);
7418
7603
  if (isNodeOfType(openingElement.name, "JSXMemberExpression")) {
7419
7604
  let cursor = openingElement.name;
7420
7605
  while (isNodeOfType(cursor, "JSXMemberExpression")) cursor = cursor.property;
@@ -7666,7 +7851,7 @@ const dialogHasAccessibleName = defineRule({
7666
7851
  if (isTestlikeFilename(context.filename)) return {};
7667
7852
  return { JSXOpeningElement(node) {
7668
7853
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
7669
- const tagName = node.name.name;
7854
+ const tagName = resolveJsxElementType(node);
7670
7855
  if (tagName[0] !== tagName[0]?.toLowerCase()) return;
7671
7856
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
7672
7857
  const roleValue = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
@@ -11898,7 +12083,7 @@ const forbidDomProps = defineRule({
11898
12083
  return { JSXOpeningElement(node) {
11899
12084
  if (forbidMap.size === 0) return;
11900
12085
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
11901
- const elementName = node.name.name;
12086
+ const elementName = resolveJsxElementType(node);
11902
12087
  if (isReactComponentName(elementName)) return;
11903
12088
  for (const attribute of node.attributes) {
11904
12089
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -11976,7 +12161,7 @@ const forbidElements = defineRule({
11976
12161
  return {
11977
12162
  JSXOpeningElement(node) {
11978
12163
  if (forbidMap.size === 0) return;
11979
- const fullName = flattenJsxName$1(node.name);
12164
+ const fullName = resolveJsxElementType(node);
11980
12165
  if (!fullName || !forbidMap.has(fullName)) return;
11981
12166
  context.report({
11982
12167
  node: node.name,
@@ -12338,8 +12523,7 @@ const buildMessage$22 = (childTagName) => `Your users get reshuffled HTML becaus
12338
12523
  const isParagraphElement = (candidate) => {
12339
12524
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12340
12525
  const opening = candidate.openingElement;
12341
- if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12342
- return opening.name.name === "p";
12526
+ return resolveJsxElementType(opening) === "p";
12343
12527
  };
12344
12528
  const findEnclosingParagraph = (openingElement) => {
12345
12529
  const owningElement = openingElement.parent;
@@ -12360,8 +12544,7 @@ const htmlNoInvalidParagraphChild = defineRule({
12360
12544
  severity: "warn",
12361
12545
  recommendation: "Swap the `<p>` for a `<div>`, or move the child outside the paragraph.",
12362
12546
  create: (context) => ({ JSXOpeningElement(node) {
12363
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12364
- const childTagName = node.name.name;
12547
+ const childTagName = resolveJsxElementType(node);
12365
12548
  if (!BLOCK_LEVEL_ELEMENTS.has(childTagName)) return;
12366
12549
  if (!findEnclosingParagraph(node)) return;
12367
12550
  context.report({
@@ -12392,7 +12575,7 @@ const getHostTagName = (jsxElement) => {
12392
12575
  if (!isNodeOfType(jsxElement, "JSXElement")) return null;
12393
12576
  const opening = jsxElement.openingElement;
12394
12577
  if (!isNodeOfType(opening.name, "JSXIdentifier")) return null;
12395
- const tagName = opening.name.name;
12578
+ const tagName = resolveJsxElementType(opening);
12396
12579
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return null;
12397
12580
  return tagName;
12398
12581
  };
@@ -12422,7 +12605,7 @@ const findClosestHostAncestor = (jsxElement) => {
12422
12605
  if (isNodeOfType(ancestor, "JSXElement")) {
12423
12606
  const opening = ancestor.openingElement;
12424
12607
  if (isNodeOfType(opening.name, "JSXIdentifier")) {
12425
- const ancestorTag = opening.name.name;
12608
+ const ancestorTag = resolveJsxElementType(opening);
12426
12609
  if (ancestorTag.length === 0) {
12427
12610
  previous = ancestor;
12428
12611
  ancestor = ancestor.parent ?? null;
@@ -12504,8 +12687,7 @@ const buildMessage$20 = (tagName) => `Your users get broken clicks, focus & scre
12504
12687
  const isJsxElementWithTagName = (candidate, tagName) => {
12505
12688
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12506
12689
  const opening = candidate.openingElement;
12507
- if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12508
- return opening.name.name === tagName;
12690
+ return resolveJsxElementType(opening) === tagName;
12509
12691
  };
12510
12692
  const findEnclosingSameTag = (openingElement, tagName) => {
12511
12693
  const owningElement = openingElement.parent;
@@ -12526,8 +12708,7 @@ const htmlNoNestedInteractive = defineRule({
12526
12708
  severity: "warn",
12527
12709
  recommendation: "Move the inner `<a>` or `<button>` so it's a sibling, or change the outer one to a plain `<div>` or `<span>`.",
12528
12710
  create: (context) => ({ JSXOpeningElement(node) {
12529
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12530
- const tagName = node.name.name;
12711
+ const tagName = resolveJsxElementType(node);
12531
12712
  if (tagName !== "a" && tagName !== "button") return;
12532
12713
  if (!findEnclosingSameTag(node, tagName)) return;
12533
12714
  context.report({
@@ -12642,7 +12823,7 @@ const iframeMissingSandbox = defineRule({
12642
12823
  matchByOccurrence: true,
12643
12824
  create: skipNonProductionFiles((context) => ({
12644
12825
  JSXOpeningElement(node) {
12645
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "iframe") return;
12826
+ if (resolveJsxElementType(node) !== "iframe") return;
12646
12827
  const sandboxAttr = hasJsxPropIgnoreCase(node.attributes, "sandbox");
12647
12828
  if (!sandboxAttr) {
12648
12829
  const hasExplicitSrc = Boolean(hasJsxPropIgnoreCase(node.attributes, "src"));
@@ -13634,6 +13815,28 @@ const jsAsyncReduceWithoutAwaitedAcc = defineRule({
13634
13815
  } })
13635
13816
  });
13636
13817
  //#endregion
13818
+ //#region src/plugin/utils/unwrap-discarded-expression.ts
13819
+ const unwrapDiscardedExpression = (node) => {
13820
+ let expression = isNodeOfType(node, "ExpressionStatement") ? node.expression : node;
13821
+ for (;;) {
13822
+ expression = stripParenExpression(expression);
13823
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") {
13824
+ expression = expression.argument;
13825
+ continue;
13826
+ }
13827
+ if (isNodeOfType(expression, "SequenceExpression")) {
13828
+ const expressions = expression.expressions ?? [];
13829
+ const finalExpression = expressions.at(-1);
13830
+ const prefixExpressions = expressions.slice(0, -1);
13831
+ if (finalExpression && prefixExpressions.length > 0 && prefixExpressions.every((prefixExpression) => isNodeOfType(stripParenExpression(prefixExpression), "Literal"))) {
13832
+ expression = finalExpression;
13833
+ continue;
13834
+ }
13835
+ }
13836
+ return expression;
13837
+ }
13838
+ };
13839
+ //#endregion
13637
13840
  //#region src/plugin/rules/js-performance/js-batch-dom-css.ts
13638
13841
  const ITERATOR_METHOD_NAMES$2 = new Set([
13639
13842
  "forEach",
@@ -13818,9 +14021,19 @@ const hasAttachmentBefore = (scopeOwner, elementName, beforeStart) => {
13818
14021
  });
13819
14022
  return foundAttachment;
13820
14023
  };
14024
+ const getStyleAssignment = (node) => {
14025
+ if (!isNodeOfType(node, "ExpressionStatement")) return null;
14026
+ const expression = unwrapDiscardedExpression(node);
14027
+ if (!isNodeOfType(expression, "AssignmentExpression")) return null;
14028
+ if (!isNodeOfType(expression.left, "MemberExpression")) return null;
14029
+ if (!isNodeOfType(expression.left.object, "MemberExpression")) return null;
14030
+ if (!isNodeOfType(expression.left.object.property, "Identifier")) return null;
14031
+ return expression.left.object.property.name === "style" ? expression : null;
14032
+ };
13821
14033
  const isProvablyDetachedAtWrite = (styleWriteStatement) => {
13822
- if (!isNodeOfType(styleWriteStatement, "ExpressionStatement") || !isNodeOfType(styleWriteStatement.expression, "AssignmentExpression") || !isNodeOfType(styleWriteStatement.expression.left, "MemberExpression") || !isNodeOfType(styleWriteStatement.expression.left.object, "MemberExpression")) return false;
13823
- const elementExpression = styleWriteStatement.expression.left.object.object;
14034
+ const assignment = getStyleAssignment(styleWriteStatement);
14035
+ if (!assignment || !isNodeOfType(assignment.left, "MemberExpression") || !isNodeOfType(assignment.left.object, "MemberExpression")) return false;
14036
+ const elementExpression = assignment.left.object.object;
13824
14037
  const creationRoot = resolveDetachedCreationRoot(elementExpression, 0);
13825
14038
  if (!creationRoot) return false;
13826
14039
  return !hasAttachmentBefore(creationRoot.scopeOwner, creationRoot.rootName, getNodeStart$1(styleWriteStatement));
@@ -13832,15 +14045,17 @@ const jsBatchDomCss = defineRule({
13832
14045
  severity: "warn",
13833
14046
  recommendation: "Do all your reads first, then all your writes. Mixing them inside a loop makes the browser recalculate the layout again and again, which is slow",
13834
14047
  create: (context) => {
13835
- const isStyleAssignment = (node) => isNodeOfType(node, "ExpressionStatement") && isNodeOfType(node.expression, "AssignmentExpression") && isNodeOfType(node.expression.left, "MemberExpression") && isNodeOfType(node.expression.left.object, "MemberExpression") && isNodeOfType(node.expression.left.object.property, "Identifier") && node.expression.left.object.property.name === "style";
13836
- const writesLayoutAffectingProperty = (node) => isNodeOfType(node, "ExpressionStatement") && isNodeOfType(node.expression, "AssignmentExpression") && isNodeOfType(node.expression.left, "MemberExpression") && isNodeOfType(node.expression.left.property, "Identifier") && !LAYOUT_NEUTRAL_STYLE_PROPERTY_NAMES.has(node.expression.left.property.name);
14048
+ const writesLayoutAffectingProperty = (node) => {
14049
+ const assignment = getStyleAssignment(node);
14050
+ return assignment !== null && isNodeOfType(assignment.left, "MemberExpression") && isNodeOfType(assignment.left.property, "Identifier") && !LAYOUT_NEUTRAL_STYLE_PROPERTY_NAMES.has(assignment.left.property.name);
14051
+ };
13837
14052
  return { BlockStatement(node) {
13838
14053
  const perIterationBody = findEnclosingPerIterationBody(node);
13839
14054
  if (!perIterationBody) return;
13840
14055
  const statements = node.body ?? [];
13841
14056
  let layoutReads = null;
13842
14057
  for (let statementIndex = 1; statementIndex < statements.length; statementIndex++) {
13843
- if (!isStyleAssignment(statements[statementIndex]) || !isStyleAssignment(statements[statementIndex - 1])) continue;
14058
+ if (getStyleAssignment(statements[statementIndex]) === null || getStyleAssignment(statements[statementIndex - 1]) === null) continue;
13844
14059
  if (!writesLayoutAffectingProperty(statements[statementIndex]) && !writesLayoutAffectingProperty(statements[statementIndex - 1])) continue;
13845
14060
  layoutReads ??= scanPerIterationLayoutReads(perIterationBody);
13846
14061
  if (!layoutReads.hasUsedLayoutRead || layoutReads.hasDeliberateForcedReflow) return;
@@ -14536,6 +14751,18 @@ const jsHoistRegexp = defineRule({
14536
14751
  }, { treatIteratorCallbacksAsLoops: true })
14537
14752
  });
14538
14753
  //#endregion
14754
+ //#region src/plugin/utils/resolve-first-argument-binding.ts
14755
+ const resolveFirstArgumentBinding = (firstParameter) => {
14756
+ if (!firstParameter) return null;
14757
+ if (!isNodeOfType(firstParameter, "RestElement")) return firstParameter;
14758
+ if (!isNodeOfType(firstParameter.argument, "ArrayPattern")) return null;
14759
+ const elements = firstParameter.argument.elements ?? [];
14760
+ if (elements.length !== 1) return null;
14761
+ const firstBinding = elements[0];
14762
+ if (!firstBinding || isNodeOfType(firstBinding, "RestElement")) return null;
14763
+ return firstBinding;
14764
+ };
14765
+ //#endregion
14539
14766
  //#region src/plugin/rules/js-performance/js-index-maps.ts
14540
14767
  const referencesParameter = (expression, parameterName) => {
14541
14768
  if (!expression) return false;
@@ -14546,7 +14773,7 @@ const referencesParameter = (expression, parameterName) => {
14546
14773
  const isSingleFieldEqualityPredicate = (node) => {
14547
14774
  const callback = node.arguments?.[0];
14548
14775
  if (!isInlineFunctionExpression(callback)) return false;
14549
- const firstParameter = callback.params?.[0];
14776
+ const firstParameter = resolveFirstArgumentBinding(callback.params?.[0]);
14550
14777
  if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
14551
14778
  let predicate = null;
14552
14779
  const body = callback.body;
@@ -15238,9 +15465,21 @@ const isSmallFixedListMember = (receiver) => {
15238
15465
  };
15239
15466
  const getResolvedInitializer = (receiver) => {
15240
15467
  if (!isNodeOfType(receiver, "Identifier")) return null;
15241
- const initializer = findVariableInitializer(receiver, receiver.name)?.initializer ?? null;
15242
- if (initializer && isNodeOfType(initializer, "Identifier")) return findVariableInitializer(initializer, initializer.name)?.initializer ?? initializer;
15243
- return initializer;
15468
+ const binding = findVariableInitializer(receiver, receiver.name);
15469
+ const initializer = binding?.initializer ?? null;
15470
+ if (!binding || !initializer) return null;
15471
+ const isDefault = isNodeOfType(binding.bindingIdentifier.parent, "AssignmentPattern");
15472
+ if (isNodeOfType(initializer, "Identifier")) {
15473
+ const aliased = findVariableInitializer(initializer, initializer.name);
15474
+ if (aliased?.initializer) return {
15475
+ initializer: aliased.initializer,
15476
+ isDefault: isDefault || isNodeOfType(aliased.bindingIdentifier.parent, "AssignmentPattern")
15477
+ };
15478
+ }
15479
+ return {
15480
+ initializer,
15481
+ isDefault
15482
+ };
15244
15483
  };
15245
15484
  const isSplitCall = (expression) => {
15246
15485
  if (!expression) return false;
@@ -15406,8 +15645,8 @@ const isBoundedConstantCollection = (collection) => {
15406
15645
  if (isScreamingSnakeCaseConstantReceiver(stripped)) return true;
15407
15646
  if (isSmallInlineLiteralArray(stripped)) return true;
15408
15647
  if (isNodeOfType(stripped, "Identifier")) {
15409
- const initializer = getResolvedInitializer(stripped);
15410
- if (initializer && isSmallInlineLiteralArray(initializer)) return true;
15648
+ const resolved = getResolvedInitializer(stripped);
15649
+ if (resolved && !resolved.isDefault && isSmallInlineLiteralArray(resolved.initializer)) return true;
15411
15650
  }
15412
15651
  return false;
15413
15652
  };
@@ -15459,8 +15698,8 @@ const jsSetMapLookups = defineRule({
15459
15698
  if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
15460
15699
  const resolvedInitializer = getResolvedInitializer(receiver);
15461
15700
  if (resolvedInitializer) {
15462
- if (isLikelyStringReceiver(resolvedInitializer)) return;
15463
- if (isSmallInlineLiteralArray(resolvedInitializer)) return;
15701
+ if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
15702
+ if (!resolvedInitializer.isDefault && isSmallInlineLiteralArray(resolvedInitializer.initializer)) return;
15464
15703
  }
15465
15704
  if (isStringElementOfSplitIteration(receiver)) return;
15466
15705
  if (isReceiverDeclaredInNearestLoop(receiver, node)) return;
@@ -15873,14 +16112,21 @@ const jsxFilenameExtension = defineRule({
15873
16112
  });
15874
16113
  //#endregion
15875
16114
  //#region src/plugin/utils/is-jsx-fragment-element.ts
15876
- const isJsxFragmentElement = (node) => {
16115
+ const isJsxFragmentElement = (node, scopes) => {
15877
16116
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
15878
16117
  const elementName = node.name;
15879
- if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
16118
+ if (isNodeOfType(elementName, "JSXIdentifier")) {
16119
+ if (!scopes) return elementName.name === "Fragment";
16120
+ const symbol = resolveConstIdentifierAlias(elementName, scopes);
16121
+ if (!symbol) return elementName.name === "Fragment" && scopes.isGlobalReference(elementName);
16122
+ return isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment";
16123
+ }
15880
16124
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
15881
16125
  if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
15882
- if (elementName.object.name !== "React") return false;
15883
- return elementName.property.name === "Fragment";
16126
+ if (elementName.property.name !== "Fragment") return false;
16127
+ if (!scopes) return elementName.object.name === "React";
16128
+ if (isReactNamespaceImport(elementName.object, scopes)) return true;
16129
+ return elementName.object.name === "React" && scopes.isGlobalReference(elementName.object);
15884
16130
  }
15885
16131
  return false;
15886
16132
  };
@@ -15906,7 +16152,7 @@ const jsxFragments = defineRule({
15906
16152
  if (mode !== "syntax") return;
15907
16153
  if (!node.closingElement) return;
15908
16154
  const openingElement = node.openingElement;
15909
- if (!isJsxFragmentElement(openingElement)) return;
16155
+ if (!isJsxFragmentElement(openingElement, context.scopes)) return;
15910
16156
  if (openingElement.attributes.length > 0) return;
15911
16157
  context.report({
15912
16158
  node: openingElement,
@@ -18587,10 +18833,6 @@ const resolveSettings$29 = (settings) => {
18587
18833
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
18588
18834
  return reactDoctor.jsxNoScriptUrl ?? {};
18589
18835
  };
18590
- const getElementName = (node) => {
18591
- if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
18592
- return null;
18593
- };
18594
18836
  const isLinkPropForElement = (elementName, attributeName, options) => {
18595
18837
  if (elementName === "a" && attributeName === "href") return true;
18596
18838
  const explicit = options.components?.[elementName];
@@ -18610,7 +18852,8 @@ const jsxNoScriptUrl = defineRule({
18610
18852
  create: (context) => {
18611
18853
  const options = resolveSettings$29(context.settings);
18612
18854
  return { JSXOpeningElement(node) {
18613
- const elementName = getElementName(node);
18855
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18856
+ const elementName = resolveJsxElementType(node);
18614
18857
  if (!elementName) return;
18615
18858
  for (const attribute of node.attributes) {
18616
18859
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -18797,11 +19040,6 @@ const checkTarget = (attributeValue) => {
18797
19040
  alternate: false
18798
19041
  };
18799
19042
  };
18800
- const getOpeningElementName = (node) => {
18801
- const name = node.name;
18802
- if (isNodeOfType(name, "JSXIdentifier")) return name.name;
18803
- return null;
18804
- };
18805
19043
  const jsxNoTargetBlank = defineRule({
18806
19044
  id: "jsx-no-target-blank",
18807
19045
  title: "Unsafe target=_blank link",
@@ -18821,7 +19059,8 @@ const jsxNoTargetBlank = defineRule({
18821
19059
  return settings.formComponents.has(tagName);
18822
19060
  };
18823
19061
  return { JSXOpeningElement(node) {
18824
- const tagName = getOpeningElementName(node);
19062
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
19063
+ const tagName = resolveJsxElementType(node);
18825
19064
  if (!tagName) return;
18826
19065
  if (!isLink(tagName) && !isForm(tagName)) return;
18827
19066
  const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
@@ -19054,7 +19293,7 @@ const jsxNoUselessFragment = defineRule({
19054
19293
  return {
19055
19294
  JSXElement(node) {
19056
19295
  const openingElement = node.openingElement;
19057
- if (!isJsxFragmentElement(openingElement)) return;
19296
+ if (!isJsxFragmentElement(openingElement, context.scopes)) return;
19058
19297
  if (hasJsxKeyAttribute(openingElement)) return;
19059
19298
  if (checkChildren(node, openingElement, node.children)) return;
19060
19299
  if (isChildOfHtmlElement(node)) context.report({
@@ -20296,11 +20535,24 @@ const hasDirective = (programNode, directive) => {
20296
20535
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20297
20536
  };
20298
20537
  //#endregion
20299
- //#region src/plugin/utils/is-uppercase-name.ts
20300
- const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
20301
- //#endregion
20302
- //#region src/plugin/utils/is-component-assignment.ts
20303
- const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
20538
+ //#region src/plugin/utils/unwrap-object-integrity-expression.ts
20539
+ const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
20540
+ "freeze",
20541
+ "seal",
20542
+ "preventExtensions"
20543
+ ]);
20544
+ const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
20545
+ const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
20546
+ let expression = stripParenExpression(node);
20547
+ while (isNodeOfType(expression, "CallExpression")) {
20548
+ const callee = stripParenExpression(expression.callee);
20549
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || callee.object.name !== "Object" || !scopes.isGlobalReference(callee.object) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) break;
20550
+ const wrappedExpression = expression.arguments[0];
20551
+ if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
20552
+ expression = stripParenExpression(wrappedExpression);
20553
+ }
20554
+ return expression;
20555
+ };
20304
20556
  //#endregion
20305
20557
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
20306
20558
  const nextjsAsyncClientComponent = defineRule({
@@ -20326,10 +20578,10 @@ const nextjsAsyncClientComponent = defineRule({
20326
20578
  },
20327
20579
  VariableDeclarator(node) {
20328
20580
  if (!fileHasUseClient) return;
20329
- if (!isComponentAssignment(node)) return;
20330
- if (!isInlineFunctionExpression(node.init)) return;
20331
- if (!node.init.async) return;
20332
20581
  if (!isNodeOfType(node.id, "Identifier")) return;
20582
+ if (!isUppercaseName(node.id.name) || !node.init) return;
20583
+ const componentFunction = unwrapObjectIntegrityExpression(node.init, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES);
20584
+ if (!isInlineFunctionExpression(componentFunction) || !componentFunction.async) return;
20333
20585
  context.report({
20334
20586
  node,
20335
20587
  message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
@@ -20631,7 +20883,7 @@ const nextjsNoAElement = defineRule({
20631
20883
  severity: "warn",
20632
20884
  recommendation: "`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",
20633
20885
  create: (context) => ({ JSXOpeningElement(node) {
20634
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
20886
+ if (resolveJsxElementType(node) !== "a") return;
20635
20887
  const attributes = node.attributes ?? [];
20636
20888
  const downloadAttribute = findJsxAttribute(attributes, "download");
20637
20889
  if (downloadAttribute) {
@@ -20827,7 +21079,7 @@ const nextjsNoCssLink = defineRule({
20827
21079
  severity: "warn",
20828
21080
  recommendation: "Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.",
20829
21081
  create: (context) => ({ JSXOpeningElement(node) {
20830
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21082
+ if (resolveJsxElementType(node) !== "link") return;
20831
21083
  const attributes = node.attributes ?? [];
20832
21084
  const relAttribute = findJsxAttribute(attributes, "rel");
20833
21085
  if (!relAttribute?.value) return;
@@ -20935,7 +21187,7 @@ const nextjsNoFontLink = defineRule({
20935
21187
  severity: "warn",
20936
21188
  recommendation: "`import { Inter } from \"next/font/google\"` for self-hosting, zero layout shift, and no render-blocking requests",
20937
21189
  create: (context) => ({ JSXOpeningElement(node) {
20938
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21190
+ if (resolveJsxElementType(node) !== "link") return;
20939
21191
  const attributes = node.attributes ?? [];
20940
21192
  const hrefAttribute = findJsxAttribute(attributes, "href");
20941
21193
  if (!hrefAttribute?.value) return;
@@ -21110,7 +21362,7 @@ const nextjsNoImgElement = defineRule({
21110
21362
  create: (context) => {
21111
21363
  if (isGeneratedImageRenderContext(context)) return {};
21112
21364
  return { JSXOpeningElement(node) {
21113
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
21365
+ if (resolveJsxElementType(node) !== "img") return;
21114
21366
  if (isGeneratedImageRenderContext(context, node)) return;
21115
21367
  const programRoot = findProgramRoot(node);
21116
21368
  if (programRoot && hasEmailTemplateImport(programRoot)) return;
@@ -21157,8 +21409,8 @@ const nextjsNoPolyfillScript = defineRule({
21157
21409
  severity: "warn",
21158
21410
  recommendation: "Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically",
21159
21411
  create: (context) => ({ JSXOpeningElement(node) {
21160
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
21161
- if (node.name.name !== "script" && node.name.name !== "Script") return;
21412
+ const elementName = resolveJsxElementType(node);
21413
+ if (elementName !== "script" && elementName !== "Script") return;
21162
21414
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21163
21415
  if (!srcAttribute?.value) return;
21164
21416
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
@@ -22966,10 +23218,15 @@ const isProp = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
22966
23218
  if (!declaringNode) return false;
22967
23219
  return isReactFunctionalComponent(declaringNode) && !isReactFunctionalHOC(analysis, declaringNode) || isCustomHook(declaringNode);
22968
23220
  }));
23221
+ const isWholePropsParameterBinding = (bindingNode) => {
23222
+ if (isFunctionLike$1(bindingNode.parent)) return true;
23223
+ let declaringFunction = bindingNode.parent;
23224
+ while (declaringFunction && !isFunctionLike$1(declaringFunction)) declaringFunction = declaringFunction.parent;
23225
+ return Boolean(declaringFunction && resolveFirstArgumentBinding(declaringFunction.params?.[0]) === bindingNode);
23226
+ };
22969
23227
  const isWholePropsObjectReference = (analysis, ref) => isProp(analysis, ref) && Boolean(ref.resolved?.defs.some((def) => {
22970
23228
  if (def.type !== "Parameter") return false;
22971
- const bindingParent = def.name.parent;
22972
- return isFunctionLike$1(bindingParent);
23229
+ return isWholePropsParameterBinding(def.name);
22973
23230
  }));
22974
23231
  const isIdentifierOrMemberExpression = (node) => isNodeOfType(node, "Identifier") || isNodeOfType(node, "MemberExpression");
22975
23232
  const isPropAlias = (analysis, ref) => {
@@ -25966,6 +26223,56 @@ const noBarrelImport = defineRule({
25966
26223
  }
25967
26224
  });
25968
26225
  //#endregion
26226
+ //#region src/plugin/utils/function-returns-matching-expression.ts
26227
+ const collectReturnedExpressions = (functionNode) => {
26228
+ if (!isFunctionLike$1(functionNode)) return [];
26229
+ const body = functionNode.body;
26230
+ if (!body) return [];
26231
+ if (!isNodeOfType(body, "BlockStatement")) return [body];
26232
+ const returnedExpressions = [];
26233
+ walkAst(body, (node) => {
26234
+ if (node !== body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
26235
+ if (isNodeOfType(node, "ReturnStatement") && node.argument) returnedExpressions.push(node.argument);
26236
+ });
26237
+ return returnedExpressions;
26238
+ };
26239
+ const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpression) => {
26240
+ const visitedExpressions = /* @__PURE__ */ new Set();
26241
+ const visitedFunctions = /* @__PURE__ */ new Set();
26242
+ const functionMatches = (candidateFunction) => {
26243
+ if (visitedFunctions.has(candidateFunction)) return false;
26244
+ visitedFunctions.add(candidateFunction);
26245
+ return collectReturnedExpressions(candidateFunction).some(expressionMatches);
26246
+ };
26247
+ const expressionMatches = (expression) => {
26248
+ const unwrappedExpression = stripParenExpression(expression);
26249
+ if (visitedExpressions.has(unwrappedExpression)) return false;
26250
+ visitedExpressions.add(unwrappedExpression);
26251
+ if (matchesExpression(unwrappedExpression)) return true;
26252
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
26253
+ const symbol = scopes.symbolFor(unwrappedExpression);
26254
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer) return false;
26255
+ const initializer = stripParenExpression(symbol.initializer);
26256
+ if (isFunctionLike$1(initializer)) return false;
26257
+ return expressionMatches(initializer);
26258
+ }
26259
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
26260
+ if (unwrappedExpression.arguments.length !== 0) return false;
26261
+ if (!isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
26262
+ const symbol = scopes.symbolFor(unwrappedExpression.callee);
26263
+ if (!symbol || symbol.kind !== "const" && symbol.kind !== "function") return false;
26264
+ const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
26265
+ const candidateFunction = isFunctionLike$1(initializer) ? initializer : isFunctionLike$1(symbol.declarationNode) ? symbol.declarationNode : null;
26266
+ if (!candidateFunction || candidateFunction.async || candidateFunction.generator || candidateFunction.params.length !== 0) return false;
26267
+ return functionMatches(candidateFunction);
26268
+ }
26269
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return expressionMatches(unwrappedExpression.consequent) || expressionMatches(unwrappedExpression.alternate);
26270
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return expressionMatches(unwrappedExpression.left) || expressionMatches(unwrappedExpression.right);
26271
+ return false;
26272
+ };
26273
+ return functionMatches(functionNode);
26274
+ };
26275
+ //#endregion
25969
26276
  //#region src/plugin/utils/function-contains-react-render-output.ts
25970
26277
  const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
25971
26278
  "FunctionDeclaration",
@@ -25985,12 +26292,13 @@ const isCallArgumentFunctionExpression = (node) => {
25985
26292
  return parent.arguments.some((argumentNode) => argumentNode === node);
25986
26293
  };
25987
26294
  const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
26295
+ const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
25988
26296
  const containsRenderOutput$1 = (rootNode, scopes) => {
25989
26297
  let hasRenderOutput = false;
25990
26298
  walkAst(rootNode, (node) => {
25991
26299
  if (hasRenderOutput) return false;
25992
26300
  if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
25993
- if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
26301
+ if (isRenderOutputExpression(node, scopes)) {
25994
26302
  hasRenderOutput = true;
25995
26303
  return false;
25996
26304
  }
@@ -26001,7 +26309,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
26001
26309
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
26002
26310
  const cachedEntry = renderOutputCache.get(functionNode);
26003
26311
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
26004
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
26312
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
26005
26313
  renderOutputCache.set(functionNode, {
26006
26314
  scopes,
26007
26315
  hasRenderOutput
@@ -26009,6 +26317,9 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
26009
26317
  return hasRenderOutput;
26010
26318
  };
26011
26319
  //#endregion
26320
+ //#region src/plugin/utils/is-component-assignment.ts
26321
+ const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
26322
+ //#endregion
26012
26323
  //#region src/plugin/utils/is-component-declaration.ts
26013
26324
  const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
26014
26325
  //#endregion
@@ -28466,7 +28777,7 @@ const noDisabledZoom = defineRule({
28466
28777
  category: "Accessibility",
28467
28778
  recommendation: "Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If the layout breaks at 200% zoom, fix the layout instead.",
28468
28779
  create: (context) => ({ JSXOpeningElement(node) {
28469
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "meta") return;
28780
+ if (resolveJsxElementType(node) !== "meta") return;
28470
28781
  const nameAttr = findJsxAttribute(node.attributes ?? [], "name");
28471
28782
  if (!nameAttr?.value) return;
28472
28783
  if ((isNodeOfType(nameAttr.value, "Literal") ? nameAttr.value.value : null) !== "viewport") return;
@@ -28856,7 +29167,7 @@ const findTopLevelEffectCalls = (componentBody) => {
28856
29167
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
28857
29168
  for (const statement of componentBody.body ?? []) {
28858
29169
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
28859
- const expression = statement.expression;
29170
+ const expression = unwrapDiscardedExpression(statement);
28860
29171
  if (!isNodeOfType(expression, "CallExpression")) continue;
28861
29172
  if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
28862
29173
  effectCalls.push(expression);
@@ -31638,7 +31949,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
31638
31949
  severity: "warn",
31639
31950
  recommendation: "Don't combine `loading=\"lazy\"` with `fetchPriority=\"high\"`. A high-priority image (usually the LCP) should load eagerly; a lazy image is by definition not high priority.",
31640
31951
  create: (context) => ({ JSXOpeningElement(node) {
31641
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
31952
+ if (resolveJsxElementType(node) !== "img") return;
31642
31953
  const loadingAttribute = hasJsxPropIgnoreCase(node.attributes, "loading");
31643
31954
  if (!loadingAttribute || getJsxPropStringValue(loadingAttribute)?.toLowerCase() !== "lazy") return;
31644
31955
  const fetchPriorityAttribute = hasJsxPropIgnoreCase(node.attributes, "fetchPriority");
@@ -31650,6 +31961,26 @@ const noImgLazyWithHighFetchpriority = defineRule({
31650
31961
  } })
31651
31962
  });
31652
31963
  //#endregion
31964
+ //#region src/plugin/utils/react-ref-origin.ts
31965
+ const resolveReactRefSymbol = (memberExpression, scopes) => {
31966
+ const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
31967
+ if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
31968
+ const symbol = resolveConstIdentifierAlias(receiver, scopes);
31969
+ if (!symbol?.initializer) return null;
31970
+ const initializer = stripParenExpression(symbol.initializer);
31971
+ if (!isNodeOfType(initializer, "CallExpression")) return null;
31972
+ return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
31973
+ };
31974
+ const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
31975
+ const expression = stripParenExpression(node);
31976
+ if (resolveReactRefSymbol(expression, scopes)) return true;
31977
+ if (!isNodeOfType(expression, "Identifier")) return false;
31978
+ const symbol = resolveConstIdentifierAlias(expression, scopes);
31979
+ if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
31980
+ visitedSymbolIds.add(symbol.id);
31981
+ return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
31982
+ };
31983
+ //#endregion
31653
31984
  //#region src/plugin/rules/state-and-effects/no-impure-state-updater.ts
31654
31985
  const TIMER_FUNCTION_NAMES = new Set([
31655
31986
  "cancelAnimationFrame",
@@ -31681,6 +32012,33 @@ const NOTIFICATION_METHOD_NAMES = new Set([
31681
32012
  "success",
31682
32013
  "warning"
31683
32014
  ]);
32015
+ const NOTIFICATION_MODULE_SOURCES = new Set([
32016
+ "@chakra-ui/react",
32017
+ "@heroui/react",
32018
+ "@mantine/notifications",
32019
+ "antd",
32020
+ "react-hot-toast",
32021
+ "react-toastify",
32022
+ "sonner"
32023
+ ]);
32024
+ const SYNCHRONOUS_ARRAY_METHOD_NAMES = new Set([
32025
+ "every",
32026
+ "filter",
32027
+ "find",
32028
+ "findIndex",
32029
+ "flatMap",
32030
+ "forEach",
32031
+ "map",
32032
+ "reduce",
32033
+ "reduceRight",
32034
+ "some",
32035
+ "sort"
32036
+ ]);
32037
+ const isNotificationModuleSource = (source) => {
32038
+ if (!source) return false;
32039
+ for (const moduleSource of NOTIFICATION_MODULE_SOURCES) if (source === moduleSource || source.startsWith(`${moduleSource}/`)) return true;
32040
+ return /(?:^|[/_.-])(?:notification|toast)s?(?:$|[/_.-])/i.test(source);
32041
+ };
31684
32042
  const getMemberCall = (node) => {
31685
32043
  if (!isNodeOfType(node, "CallExpression")) return null;
31686
32044
  if (!isNodeOfType(node.callee, "MemberExpression") || node.callee.computed) return null;
@@ -31692,27 +32050,37 @@ const getMemberCall = (node) => {
31692
32050
  };
31693
32051
  const isNotificationReceiver = (receiver, scopes) => {
31694
32052
  if (!isNodeOfType(receiver, "Identifier")) return false;
31695
- if (!NOTIFICATION_RECEIVER_NAMES.has(receiver.name)) return false;
32053
+ const importBinding = getImportBindingForName(receiver, receiver.name);
32054
+ if (importBinding) return isNotificationModuleSource(importBinding.source) && (importBinding.exportedName === "default" || importBinding.exportedName !== null && NOTIFICATION_RECEIVER_NAMES.has(importBinding.exportedName));
31696
32055
  const symbol = scopes.symbolFor(receiver);
31697
- if (symbol?.kind === "import") return true;
31698
32056
  if (!isNodeOfType(symbol?.initializer, "CallExpression")) return false;
31699
32057
  const callee = symbol.initializer.callee;
31700
- return isNodeOfType(callee, "Identifier") && /^use(?:Message|Notification|Toast)$/.test(callee.name);
32058
+ if (!isNodeOfType(callee, "Identifier")) return false;
32059
+ const hookImport = getImportBindingForName(callee, callee.name);
32060
+ return Boolean(hookImport && isNotificationModuleSource(hookImport.source) && hookImport.exportedName !== null && /^use(?:Message|Notification|Toast)$/.test(hookImport.exportedName));
32061
+ };
32062
+ const isKnownGlobalObject = (node, expectedName, scopes) => isNodeOfType(node, "Identifier") && node.name === expectedName && scopes.isGlobalReference(node);
32063
+ const isGlobalMember = (node, expectedPropertyName, scopes) => isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && node.property.name === expectedPropertyName && (isKnownGlobalObject(node.object, "window", scopes) || isKnownGlobalObject(node.object, "globalThis", scopes));
32064
+ const isStorageReceiver = (node, scopes) => {
32065
+ if (isNodeOfType(node, "Identifier") && STORAGE_RECEIVER_NAMES.has(node.name) && scopes.isGlobalReference(node)) return true;
32066
+ for (const receiverName of STORAGE_RECEIVER_NAMES) if (isGlobalMember(node, receiverName, scopes)) return true;
32067
+ return false;
31701
32068
  };
31702
32069
  const getKnownImpureCall = (callExpression, scopes) => {
31703
32070
  if (isNodeOfType(callExpression.callee, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.name) && scopes.isGlobalReference(callExpression.callee)) return `${callExpression.callee.name}()`;
32071
+ if (isNodeOfType(callExpression.callee, "MemberExpression") && !callExpression.callee.computed && isNodeOfType(callExpression.callee.property, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.property.name) && (isKnownGlobalObject(callExpression.callee.object, "window", scopes) || isKnownGlobalObject(callExpression.callee.object, "globalThis", scopes))) return `${callExpression.callee.property.name}()`;
31704
32072
  const memberCall = getMemberCall(callExpression);
31705
32073
  if (!memberCall) return null;
31706
32074
  const { methodName, receiver } = memberCall;
31707
- if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && STORAGE_RECEIVER_NAMES.has(receiver.name) && scopes.isGlobalReference(receiver)) return `${receiver.name}.${methodName}()`;
31708
- if (EXTERNAL_READ_METHOD_NAMES.has(methodName)) return `.${methodName}()`;
31709
- if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && isNotificationReceiver(receiver, scopes)) return `${receiver.name}.${methodName}()`;
32075
+ if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isStorageReceiver(receiver, scopes)) return `${methodName}()`;
32076
+ if (EXTERNAL_READ_METHOD_NAMES.has(methodName) && hasReactRefCurrentOrigin(receiver, scopes)) return `.${methodName}()`;
32077
+ const receiverRoot = getRootIdentifier$1(receiver);
32078
+ if (EXTERNAL_READ_METHOD_NAMES.has(methodName) && receiverRoot !== null && isKnownGlobalObject(receiverRoot, "document", scopes)) return `.${methodName}()`;
32079
+ if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNotificationReceiver(receiver, scopes)) return `${methodName}()`;
31710
32080
  return null;
31711
32081
  };
31712
32082
  const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) => {
31713
- let rootIdentifier = null;
31714
- if (isNodeOfType(assignmentTarget, "Identifier")) rootIdentifier = assignmentTarget;
31715
- else if (isNodeOfType(assignmentTarget, "MemberExpression") && isNodeOfType(assignmentTarget.object, "Identifier")) rootIdentifier = assignmentTarget.object;
32083
+ const rootIdentifier = getRootIdentifier$1(assignmentTarget);
31716
32084
  if (!rootIdentifier) return null;
31717
32085
  const updaterScope = scopes.ownScopeFor(updater);
31718
32086
  if (!updaterScope) return null;
@@ -31721,12 +32089,26 @@ const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) =>
31721
32089
  if (symbol.kind === "parameter" && symbol.scope === updaterScope) return `the updater argument "${rootIdentifier.name}"`;
31722
32090
  return isDescendantScope(symbol.scope, updaterScope) ? null : `the captured value "${rootIdentifier.name}"`;
31723
32091
  };
32092
+ const isArrayValue = (node, scopes) => {
32093
+ const expression = stripParenExpression(node);
32094
+ if (isNodeOfType(expression, "ArrayExpression")) return true;
32095
+ if (!isNodeOfType(expression, "Identifier")) return false;
32096
+ return isNodeOfType(resolveConstIdentifierAlias(expression, scopes)?.initializer, "ArrayExpression");
32097
+ };
32098
+ const isDefinitelySynchronousCallback = (callback, scopes) => {
32099
+ const parent = callback.parent;
32100
+ if (!parent) return false;
32101
+ if (isNodeOfType(parent, "CallExpression") && parent.callee === callback) return true;
32102
+ if (!isNodeOfType(parent, "CallExpression") || !parent.arguments?.some((argument) => argument === callback)) return false;
32103
+ if (isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ARRAY_METHOD_NAMES.has(parent.callee.property.name) && isArrayValue(parent.callee.object, scopes)) return true;
32104
+ return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.object, "Identifier") && parent.callee.object.name === "Array" && scopes.isGlobalReference(parent.callee.object) && isNodeOfType(parent.callee.property, "Identifier") && parent.callee.property.name === "from" && parent.arguments[1] === callback;
32105
+ };
31724
32106
  const findImpureUpdaterOperation = (updater, scopes) => {
31725
32107
  const analysis = getProgramAnalysis(updater);
31726
32108
  let operation = null;
31727
32109
  walkAst(updater, (child) => {
31728
32110
  if (operation) return false;
31729
- if (child !== updater && isFunctionLike$1(child) && !executesDuringRender(child, scopes)) return false;
32111
+ if (child !== updater && isFunctionLike$1(child) && !isDefinitelySynchronousCallback(child, scopes)) return false;
31730
32112
  if (isNodeOfType(child, "CallExpression")) {
31731
32113
  if (isNodeOfType(child.callee, "Identifier") && analysis) {
31732
32114
  const calleeReference = getRef(analysis, child.callee);
@@ -31758,18 +32140,25 @@ const noImpureStateUpdater = defineRule({
31758
32140
  severity: "error",
31759
32141
  recommendation: "Keep state updater callbacks pure and return only the next state. Move notifications, storage, timers, ref writes, and other external work into the event or effect that queues the update.",
31760
32142
  create: (context) => ({ CallExpression(node) {
31761
- const updater = node.arguments?.[0];
31762
- if (!updater || !isFunctionLike$1(updater)) return;
32143
+ const updaterArgument = node.arguments?.[0];
32144
+ if (!updaterArgument) return;
31763
32145
  const analysis = getProgramAnalysis(node);
31764
32146
  if (!analysis || !isNodeOfType(node.callee, "Identifier")) return;
31765
32147
  const calleeReference = getRef(analysis, node.callee);
31766
32148
  if (!calleeReference || !isStateSetterCall(analysis, calleeReference)) return;
31767
32149
  const stateDeclarator = getUseStateDecl(analysis, calleeReference);
31768
32150
  if (!isNodeOfType(stateDeclarator, "VariableDeclarator") || !isNodeOfType(stateDeclarator.init, "CallExpression") || !isReactApiCall(stateDeclarator.init, "useState", context.scopes, { allowGlobalReactNamespace: true })) return;
32151
+ let updater = null;
32152
+ if (isFunctionLike$1(updaterArgument)) updater = updaterArgument;
32153
+ else if (isNodeOfType(updaterArgument, "Identifier")) {
32154
+ const updaterReference = getRef(analysis, updaterArgument);
32155
+ if (updaterReference) updater = resolveToFunction(updaterReference);
32156
+ }
32157
+ if (!updater) return;
31769
32158
  const operation = findImpureUpdaterOperation(updater, context.scopes);
31770
32159
  if (!operation) return;
31771
32160
  context.report({
31772
- node: updater,
32161
+ node: updaterArgument,
31773
32162
  message: `This state updater performs ${operation}. React may run updater functions more than once, so side effects here can repeat or observe inconsistent external state.`
31774
32163
  });
31775
32164
  } })
@@ -31879,7 +32268,7 @@ const noIndeterminateAttribute = defineRule({
31879
32268
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
31880
32269
  return {
31881
32270
  JSXOpeningElement(node) {
31882
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
32271
+ if (resolveJsxElementType(node) !== "input") return;
31883
32272
  let typeAttribute = null;
31884
32273
  let typeAttributeIndex = null;
31885
32274
  let indeterminateAttribute = null;
@@ -32058,11 +32447,12 @@ const isMemoCall = (node) => {
32058
32447
  };
32059
32448
  const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
32060
32449
  const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
32061
- const isInlineReference = (node) => {
32062
- if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "bind") return "functions";
32063
- if (isNodeOfType(node, "ObjectExpression")) return "objects";
32064
- if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
32065
- if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
32450
+ const isInlineReference = (node, scopes) => {
32451
+ const referenceNode = unwrapObjectIntegrityExpression(node, scopes);
32452
+ if (isNodeOfType(referenceNode, "ArrowFunctionExpression") || isNodeOfType(referenceNode, "FunctionExpression") || isNodeOfType(referenceNode, "CallExpression") && isNodeOfType(referenceNode.callee, "MemberExpression") && isNodeOfType(referenceNode.callee.property, "Identifier") && referenceNode.callee.property.name === "bind") return "functions";
32453
+ if (isNodeOfType(referenceNode, "ObjectExpression")) return "objects";
32454
+ if (isNodeOfType(referenceNode, "ArrayExpression")) return "Arrays";
32455
+ if (isNodeOfType(referenceNode, "JSXElement") || isNodeOfType(referenceNode, "JSXFragment")) return "JSX";
32066
32456
  return null;
32067
32457
  };
32068
32458
  const noInlinePropOnMemoComponent = defineRule({
@@ -32092,7 +32482,7 @@ const noInlinePropOnMemoComponent = defineRule({
32092
32482
  let elementName = null;
32093
32483
  if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
32094
32484
  if (!elementName || !memoizedComponentNames.has(elementName)) return;
32095
- const propType = isInlineReference(node.value.expression);
32485
+ const propType = isInlineReference(node.value.expression, context.scopes);
32096
32486
  if (propType) context.report({
32097
32487
  node: node.value.expression,
32098
32488
  message: `This redraws ${elementName} on every render because the prop is ${propType} built right here, so memo() can't skip it. Move it to a stable value with useMemo, useCallback, or module scope`
@@ -32951,11 +33341,13 @@ const noManyBooleanProps = defineRule({
32951
33341
  };
32952
33342
  const checkComponent = (functionNode, param, body, componentName, reportNode) => {
32953
33343
  if (!param) return;
33344
+ const propsBinding = resolveFirstArgumentBinding(param);
33345
+ if (!propsBinding) return;
32954
33346
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
32955
- if (isNodeOfType(param, "ObjectPattern")) {
33347
+ if (isNodeOfType(propsBinding, "ObjectPattern")) {
32956
33348
  const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
32957
33349
  const booleanLikePropNames = [];
32958
- for (const property of param.properties ?? []) {
33350
+ for (const property of propsBinding.properties ?? []) {
32959
33351
  if (!isNodeOfType(property, "Property")) continue;
32960
33352
  const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
32961
33353
  if (!keyName) continue;
@@ -32966,7 +33358,7 @@ const noManyBooleanProps = defineRule({
32966
33358
  reportIfMany(booleanLikePropNames, componentName, reportNode);
32967
33359
  return;
32968
33360
  }
32969
- if (isNodeOfType(param, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, param.name)], componentName, reportNode);
33361
+ if (isNodeOfType(propsBinding, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, propsBinding.name)], componentName, reportNode);
32970
33362
  };
32971
33363
  return {
32972
33364
  FunctionDeclaration(node) {
@@ -33088,7 +33480,7 @@ const noMirrorPropEffect = defineRule({
33088
33480
  if (mirrorBindings.length === 0) return;
33089
33481
  for (const statement of componentBody.body ?? []) {
33090
33482
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
33091
- const effectCall = statement.expression;
33483
+ const effectCall = unwrapDiscardedExpression(statement);
33092
33484
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
33093
33485
  if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
33094
33486
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
@@ -33102,7 +33494,7 @@ const noMirrorPropEffect = defineRule({
33102
33494
  const bodyStatements = getCallbackStatements(callback);
33103
33495
  if (bodyStatements.length !== 1) continue;
33104
33496
  const onlyStatement = bodyStatements[0];
33105
- const expression = isNodeOfType(onlyStatement, "ExpressionStatement") ? onlyStatement.expression : onlyStatement;
33497
+ const expression = unwrapDiscardedExpression(onlyStatement);
33106
33498
  if (!isNodeOfType(expression, "CallExpression")) continue;
33107
33499
  if (!isNodeOfType(expression.callee, "Identifier")) continue;
33108
33500
  if (!isSetterIdentifier(expression.callee.name)) continue;
@@ -35718,7 +36110,7 @@ const noPreventDefault = defineRule({
35718
36110
  const isServerActionsFramework = hasCapability(context.settings, "server-actions");
35719
36111
  const formMessage = isServerActionsFramework ? FORM_MESSAGE_SERVER_CAPABLE : FORM_MESSAGE_GENERIC;
35720
36112
  return { JSXOpeningElement(node) {
35721
- const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
36113
+ const elementName = resolveJsxElementType(node);
35722
36114
  if (!elementName) return;
35723
36115
  const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
35724
36116
  if (!targetEventProps) return;
@@ -36608,28 +37000,85 @@ const noRedundantShouldComponentUpdate = defineRule({
36608
37000
  });
36609
37001
  //#endregion
36610
37002
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
36611
- const resolveReactRefSymbol = (memberExpression, scopes) => {
36612
- const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
36613
- if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.computed || !isNodeOfType(memberExpression.property, "Identifier") || memberExpression.property.name !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
36614
- const symbol = resolveConstIdentifierAlias(receiver, scopes);
36615
- if (!symbol?.initializer) return null;
36616
- const initializer = stripParenExpression(symbol.initializer);
36617
- if (!isNodeOfType(initializer, "CallExpression")) return null;
36618
- return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
36619
- };
37003
+ const REPEATED_ANCESTOR_TYPES = new Set([
37004
+ "DoWhileStatement",
37005
+ "ForInStatement",
37006
+ "ForOfStatement",
37007
+ "ForStatement",
37008
+ "WhileStatement"
37009
+ ]);
36620
37010
  const isSameRefCurrentMember = (node, refSymbol, scopes) => {
36621
- if (!isNodeOfType(node, "MemberExpression") || node.computed || !isNodeOfType(node.property, "Identifier") || node.property.name !== "current") return false;
37011
+ if (!isNodeOfType(node, "MemberExpression") || getStaticPropertyName(node) !== "current") return false;
36622
37012
  const receiver = stripParenExpression(node.object);
36623
37013
  return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
36624
37014
  };
37015
+ const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
37016
+ if (isSameRefCurrentMember(node, refSymbol, scopes)) return true;
37017
+ if (!isNodeOfType(node, "Identifier")) return false;
37018
+ const aliasSymbol = scopes.symbolFor(node);
37019
+ return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
37020
+ };
37021
+ const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
37022
+ const hasRepeatedExecutionAncestor = (node, stop) => {
37023
+ let ancestor = node.parent;
37024
+ while (ancestor && ancestor !== stop) {
37025
+ if (isFunctionLike$1(ancestor) || REPEATED_ANCESTOR_TYPES.has(ancestor.type)) return true;
37026
+ ancestor = ancestor.parent;
37027
+ }
37028
+ return ancestor !== stop;
37029
+ };
37030
+ const getBranchConstraints = (node, branchRoot) => {
37031
+ const constraints = /* @__PURE__ */ new Map();
37032
+ let descendant = node;
37033
+ let ancestor = descendant.parent;
37034
+ while (ancestor && descendant !== branchRoot) {
37035
+ if (isNodeOfType(ancestor, "IfStatement")) {
37036
+ if (ancestor.consequent === descendant) constraints.set(ancestor, true);
37037
+ if (ancestor.alternate === descendant) constraints.set(ancestor, false);
37038
+ }
37039
+ descendant = ancestor;
37040
+ ancestor = ancestor.parent;
37041
+ }
37042
+ return constraints;
37043
+ };
37044
+ const canExecuteTogether = (firstConstraints, secondConstraints) => {
37045
+ for (const [statement, branch] of firstConstraints) {
37046
+ const otherBranch = secondConstraints.get(statement);
37047
+ if (otherBranch !== void 0 && otherBranch !== branch) return false;
37048
+ }
37049
+ return true;
37050
+ };
37051
+ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
37052
+ const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
37053
+ const assignmentStart = getRangeStart(assignmentExpression);
37054
+ let hasCoExecutableWrite = false;
37055
+ walkAst(branchRoot, (child) => {
37056
+ if (hasCoExecutableWrite) return false;
37057
+ const childStart = getRangeStart(child);
37058
+ if (child === assignmentExpression || !isNodeOfType(child, "AssignmentExpression") || assignmentStart === null || childStart === null || childStart >= assignmentStart || resolveReactRefSymbol(child.left, scopes)?.id !== refSymbol.id || hasRepeatedExecutionAncestor(child, branchRoot)) return;
37059
+ if (canExecuteTogether(assignmentConstraints, getBranchConstraints(child, branchRoot))) {
37060
+ hasCoExecutableWrite = true;
37061
+ return false;
37062
+ }
37063
+ });
37064
+ return !hasCoExecutableWrite;
37065
+ };
36625
37066
  const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
36626
- if (assignmentExpression.operator !== "=" || !isNodeOfType(assignmentExpression.right, "NewExpression")) return false;
37067
+ if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
37068
+ if (assignmentExpression.operator !== "=") return false;
36627
37069
  let descendant = assignmentExpression;
36628
37070
  let ancestor = descendant.parent;
36629
37071
  while (ancestor) {
36630
- if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && isNodeOfType(ancestor.test, "BinaryExpression") && (ancestor.test.operator === "===" || ancestor.test.operator === "==")) {
37072
+ if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") && [
37073
+ "===",
37074
+ "==",
37075
+ "!==",
37076
+ "!="
37077
+ ].includes(ancestor.test.operator)) {
36631
37078
  const { left, right } = ancestor.test;
36632
- if (isSameRefCurrentMember(left, refSymbol, scopes) && isNodeOfType(right, "Literal") && right.value === null || isSameRefCurrentMember(right, refSymbol, scopes) && isNodeOfType(left, "Literal") && left.value === null) return true;
37079
+ const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
37080
+ const guardedBranch = ancestor.test.operator === "===" || ancestor.test.operator === "==" ? ancestor.consequent : ancestor.alternate;
37081
+ if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
36633
37082
  }
36634
37083
  descendant = ancestor;
36635
37084
  ancestor = descendant.parent;
@@ -37203,7 +37652,7 @@ const isNonSettlingSetterArgument = (setterCall, stateName) => {
37203
37652
  return expressionReadsStateValue(argument, stateName);
37204
37653
  };
37205
37654
  const getUnconditionalSetterCall = (statement, setterNames) => {
37206
- const expression = stripParenExpression(isNodeOfType(statement, "ExpressionStatement") ? statement.expression : statement);
37655
+ const expression = unwrapDiscardedExpression(statement);
37207
37656
  if (!isNodeOfType(expression, "CallExpression")) return null;
37208
37657
  if (!isNodeOfType(expression.callee, "Identifier")) return null;
37209
37658
  if (!setterNames.has(expression.callee.name)) return null;
@@ -37552,7 +38001,7 @@ const noSelfUpdatingEffect = defineRule({
37552
38001
  const setterNames = new Set(setterNameToStateName.keys());
37553
38002
  for (const statement of functionBody.body ?? []) {
37554
38003
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
37555
- const effectCall = statement.expression;
38004
+ const effectCall = unwrapDiscardedExpression(statement);
37556
38005
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
37557
38006
  if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
37558
38007
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
@@ -38182,9 +38631,10 @@ const noStringFalseOnBooleanAttribute = defineRule({
38182
38631
  recommendation: "Use the boolean form on boolean attributes: `disabled` / `disabled={true}` / `disabled={false}`, not `disabled=\"false\"`. A non-empty string is truthy, so `=\"false\"` actually turns the attribute ON.",
38183
38632
  create: (context) => ({ JSXOpeningElement(node) {
38184
38633
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
38185
- const firstCharacter = node.name.name.charCodeAt(0);
38634
+ const elementName = resolveJsxElementType(node);
38635
+ const firstCharacter = elementName.charCodeAt(0);
38186
38636
  if (firstCharacter < 97 || firstCharacter > 122) return;
38187
- if (node.name.name.includes("-")) return;
38637
+ if (elementName.includes("-")) return;
38188
38638
  for (const attribute of node.attributes) {
38189
38639
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
38190
38640
  if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
@@ -38572,8 +39022,7 @@ const noUncontrolledInput = defineRule({
38572
39022
  const undefinedInitialStateNames = isNodeOfType(componentBody, "BlockStatement") ? collectUndefinedInitialStateNames(componentBody) : /* @__PURE__ */ new Set();
38573
39023
  walkAst(componentBody, (child) => {
38574
39024
  if (!isNodeOfType(child, "JSXOpeningElement")) return;
38575
- if (!isNodeOfType(child.name, "JSXIdentifier")) return;
38576
- const tagName = child.name.name;
39025
+ const tagName = resolveJsxElementType(child);
38577
39026
  if (!UNCONTROLLED_INPUT_TAGS.has(tagName)) return;
38578
39027
  const attributes = child.attributes ?? [];
38579
39028
  if (hasJsxSpreadAttribute(attributes)) return;
@@ -38634,7 +39083,7 @@ const noUndeferredThirdParty = defineRule({
38634
39083
  severity: "warn",
38635
39084
  recommendation: "Use `next/script` with `strategy=\"lazyOnload\"`, or add the `defer` attribute.",
38636
39085
  create: (context) => ({ JSXOpeningElement(node) {
38637
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "script") return;
39086
+ if (resolveJsxElementType(node) !== "script") return;
38638
39087
  const attributes = node.attributes ?? [];
38639
39088
  const srcAttribute = findJsxAttribute(attributes, "src");
38640
39089
  if (!srcAttribute) return;
@@ -39849,7 +40298,7 @@ const noUnknownProperty = defineRule({
39849
40298
  }
39850
40299
  if (fileIsNonReactJsx) return;
39851
40300
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
39852
- const elementType = node.name.name;
40301
+ const elementType = resolveJsxElementType(node);
39853
40302
  const firstCharacter = elementType.charCodeAt(0);
39854
40303
  if (!(firstCharacter >= 97 && firstCharacter <= 122) || elementType === "fbt" || elementType === "fbs") return;
39855
40304
  let isValidHtmlTag = isKnownDomTag(elementType);
@@ -40027,20 +40476,21 @@ const expressionContainsJsxOrCreateElement = (root) => {
40027
40476
  });
40028
40477
  return found;
40029
40478
  };
40479
+ const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
40030
40480
  const isReactClassComponent = (classNode) => {
40031
40481
  if (isEs6Component(classNode)) return true;
40032
40482
  return expressionContainsJsxOrCreateElement(classNode);
40033
40483
  };
40034
- const findEnclosingComponent = (node) => {
40484
+ const findEnclosingComponent = (node, scopes) => {
40035
40485
  let walker = node.parent;
40036
40486
  while (walker) {
40037
40487
  if (isFunctionLike$1(walker)) {
40038
40488
  const componentName = inferFunctionLikeName(walker);
40039
- if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
40489
+ if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
40040
40490
  component: walker,
40041
40491
  name: componentName
40042
40492
  };
40043
- if (!componentName && expressionContainsJsxOrCreateElement(walker) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40493
+ if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40044
40494
  component: walker,
40045
40495
  name: null
40046
40496
  };
@@ -40279,7 +40729,7 @@ const noUnstableNestedComponents = defineRule({
40279
40729
  if (renderPropRegex.test(propInfo.propName)) return;
40280
40730
  if (settings.allowAsProps) return;
40281
40731
  }
40282
- const enclosing = findEnclosingComponent(candidateNode);
40732
+ const enclosing = findEnclosingComponent(candidateNode, context.scopes);
40283
40733
  if (!enclosing) return;
40284
40734
  const gatedName = propInfo ? null : requiredInstantiationName;
40285
40735
  queuedReports.push({
@@ -40290,7 +40740,7 @@ const noUnstableNestedComponents = defineRule({
40290
40740
  });
40291
40741
  };
40292
40742
  const checkFunctionLike = (node) => {
40293
- if (!expressionContainsJsxOrCreateElement(node)) return;
40743
+ if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
40294
40744
  const inferredName = inferFunctionLikeName(node);
40295
40745
  const propInfo = isComponentDeclaredInProp(node);
40296
40746
  const isObjectCallback = isObjectCallbackCandidate(node);
@@ -41550,7 +42000,7 @@ const preactPreferOndblclick = defineRule({
41550
42000
  recommendation: "Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",
41551
42001
  create: (context) => ({ JSXOpeningElement(node) {
41552
42002
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
41553
- const tagName = node.name.name;
42003
+ const tagName = resolveJsxElementType(node);
41554
42004
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
41555
42005
  const onDoubleClickAttribute = findJsxAttribute(node.attributes, "onDoubleClick");
41556
42006
  if (!onDoubleClickAttribute) return;
@@ -41570,7 +42020,7 @@ const COMPAT_EXEMPT_INPUT_TYPES = new Set([
41570
42020
  ]);
41571
42021
  const isTextLikeInput = (openingElement) => {
41572
42022
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
41573
- const tagName = openingElement.name.name;
42023
+ const tagName = resolveJsxElementType(openingElement);
41574
42024
  if (tagName === "textarea") return true;
41575
42025
  if (tagName !== "input") return false;
41576
42026
  const typeAttribute = findJsxAttribute(openingElement.attributes, "type");
@@ -41727,8 +42177,9 @@ const CROSS_CUTTING_STATE_BOOLEAN_NAMES = new Set([
41727
42177
  ]);
41728
42178
  const collectBooleanPropBindings = (param) => {
41729
42179
  const bindings = /* @__PURE__ */ new Set();
41730
- if (!param || !isNodeOfType(param, "ObjectPattern")) return bindings;
41731
- for (const property of param.properties ?? []) {
42180
+ const propsBinding = resolveFirstArgumentBinding(param);
42181
+ if (!isNodeOfType(propsBinding, "ObjectPattern")) return bindings;
42182
+ for (const property of propsBinding.properties ?? []) {
41732
42183
  if (!isNodeOfType(property, "Property")) continue;
41733
42184
  if (property.computed) continue;
41734
42185
  if (!isNodeOfType(property.key, "Identifier")) continue;
@@ -41989,7 +42440,7 @@ const preferHtmlDialog = defineRule({
41989
42440
  },
41990
42441
  JSXOpeningElement(node) {
41991
42442
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
41992
- const tagName = node.name.name;
42443
+ const tagName = resolveJsxElementType(node);
41993
42444
  if (tagName === "dialog") return;
41994
42445
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
41995
42446
  if (!HTML_TAGS.has(tagName)) return;
@@ -43536,13 +43987,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
43536
43987
  }
43537
43988
  return null;
43538
43989
  };
43539
- const resolveHookNameFromInitializer = (initializer) => {
43990
+ const resolveHookNameFromInitializer = (initializer, scopes) => {
43540
43991
  if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
43541
43992
  if (!isNodeOfType(initializer, "Identifier")) return null;
43542
- const binding = findVariableInitializer(initializer, initializer.name);
43543
- if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
43544
- if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
43545
- return resolveTanstackQueryHookName(binding.initializer);
43993
+ const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
43994
+ if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
43995
+ if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
43996
+ return resolveTanstackQueryHookName(resolvedSymbol.initializer);
43546
43997
  };
43547
43998
  const queryNoRestDestructuring = defineRule({
43548
43999
  id: "query-no-rest-destructuring",
@@ -43555,7 +44006,7 @@ const queryNoRestDestructuring = defineRule({
43555
44006
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
43556
44007
  if (!node.init) return;
43557
44008
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
43558
- const hookName = resolveHookNameFromInitializer(node.init);
44009
+ const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
43559
44010
  if (!hookName) return;
43560
44011
  context.report({
43561
44012
  node: node.id,
@@ -44026,7 +44477,7 @@ const renderingAnimateSvgWrapper = defineRule({
44026
44477
  severity: "warn",
44027
44478
  recommendation: "Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.",
44028
44479
  create: (context) => ({ JSXOpeningElement(node) {
44029
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "svg") return;
44480
+ if (resolveJsxElementType(node) !== "svg") return;
44030
44481
  if (node.attributes?.some((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && MOTION_ANIMATE_PROPS.has(attribute.name.name))) context.report({
44031
44482
  node,
44032
44483
  message: "This is slow to render because you animate <svg> directly, so wrap it in a <div> or <motion.div> & animate that instead"
@@ -44910,7 +45361,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
44910
45361
  "useParams",
44911
45362
  "usePathname"
44912
45363
  ]);
44913
- const findHookCallBindings = (componentBody) => {
45364
+ const findHookCallBindings = (componentBody, scopes) => {
44914
45365
  const bindings = [];
44915
45366
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
44916
45367
  for (const statement of componentBody.body ?? []) {
@@ -44921,8 +45372,10 @@ const findHookCallBindings = (componentBody) => {
44921
45372
  const callee = declarator.init.callee;
44922
45373
  if (!isNodeOfType(callee, "Identifier")) continue;
44923
45374
  if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
45375
+ const valueSymbol = scopes.symbolFor(declarator.id);
45376
+ if (!valueSymbol) continue;
44924
45377
  bindings.push({
44925
- valueName: declarator.id.name,
45378
+ valueSymbol,
44926
45379
  hookName: callee.name,
44927
45380
  declarator
44928
45381
  });
@@ -44930,6 +45383,49 @@ const findHookCallBindings = (componentBody) => {
44930
45383
  }
44931
45384
  return bindings;
44932
45385
  };
45386
+ const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
45387
+ const symbols = [sourceSymbol];
45388
+ const symbolIds = new Set([sourceSymbol.id]);
45389
+ const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
45390
+ if (!isNodeOfType(componentBody, "BlockStatement")) return {
45391
+ symbols,
45392
+ aliasSourceIdentifiers
45393
+ };
45394
+ let didFindAlias = true;
45395
+ while (didFindAlias) {
45396
+ didFindAlias = false;
45397
+ for (const statement of componentBody.body ?? []) {
45398
+ if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
45399
+ for (const declarator of statement.declarations ?? []) {
45400
+ let aliasIdentifier = null;
45401
+ let sourceIdentifier = null;
45402
+ const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
45403
+ const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
45404
+ const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
45405
+ const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
45406
+ if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
45407
+ aliasIdentifier = declarator.id;
45408
+ sourceIdentifier = initializer;
45409
+ } else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
45410
+ aliasIdentifier = arrayBinding;
45411
+ sourceIdentifier = arrayValue;
45412
+ }
45413
+ if (!aliasIdentifier || !sourceIdentifier) continue;
45414
+ const referencedSymbol = scopes.symbolFor(sourceIdentifier);
45415
+ const aliasSymbol = scopes.symbolFor(aliasIdentifier);
45416
+ if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
45417
+ symbolIds.add(aliasSymbol.id);
45418
+ symbols.push(aliasSymbol);
45419
+ aliasSourceIdentifiers.add(sourceIdentifier);
45420
+ didFindAlias = true;
45421
+ }
45422
+ }
45423
+ }
45424
+ return {
45425
+ symbols,
45426
+ aliasSourceIdentifiers
45427
+ };
45428
+ };
44933
45429
  const rerenderDeferReadsHook = defineRule({
44934
45430
  id: "rerender-defer-reads-hook",
44935
45431
  title: "URL hook value only read in handlers",
@@ -44940,15 +45436,13 @@ const rerenderDeferReadsHook = defineRule({
44940
45436
  create: (context) => {
44941
45437
  const checkComponent = (componentBody) => {
44942
45438
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
44943
- const bindings = findHookCallBindings(componentBody);
45439
+ const bindings = findHookCallBindings(componentBody, context.scopes);
44944
45440
  if (bindings.length === 0) return;
44945
45441
  const handlerBindingNames = collectHandlerBindingNames(componentBody);
44946
45442
  for (const binding of bindings) {
45443
+ const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
44947
45444
  const referenceLocations = [];
44948
- walkAst(componentBody, (child) => {
44949
- if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
44950
- if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
44951
- });
45445
+ for (const symbol of symbols) for (const reference of symbol.references) if (!aliasSourceIdentifiers.has(reference.identifier)) referenceLocations.push(reference.identifier);
44952
45446
  if (referenceLocations.length === 0) continue;
44953
45447
  if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
44954
45448
  context.report({
@@ -45319,14 +45813,10 @@ const rerenderLazyStateInit = defineRule({
45319
45813
  //#endregion
45320
45814
  //#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
45321
45815
  const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
45322
- const callbackReturnsJsx = (callback) => {
45816
+ const callbackReturnsJsx = (callback, scopes) => {
45323
45817
  if (!callback) return false;
45324
45818
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
45325
- const body = callback.body;
45326
- if (isJsxExpression(body)) return true;
45327
- if (!isNodeOfType(body, "BlockStatement")) return false;
45328
- for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
45329
- return false;
45819
+ return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
45330
45820
  };
45331
45821
  const returnArgumentUsesAnyName = (returnStatement, names) => {
45332
45822
  if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
@@ -45404,7 +45894,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
45404
45894
  if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
45405
45895
  for (const declarator of stmt.declarations ?? []) {
45406
45896
  const init = declarator.init;
45407
- if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
45897
+ if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
45408
45898
  memoNode = declarator;
45409
45899
  callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
45410
45900
  if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
@@ -45470,8 +45960,11 @@ const collectFromObjectPattern = (pattern, bindings) => {
45470
45960
  const collectDefaultedEmptyBindings = (functionNode) => {
45471
45961
  const bindings = /* @__PURE__ */ new Map();
45472
45962
  const params = functionNode.params ?? [];
45473
- for (const param of params) collectFromObjectPattern(param, bindings);
45474
- const propsParam = params[0];
45963
+ for (const [parameterIndex, param] of params.entries()) {
45964
+ const parameterBinding = parameterIndex === 0 ? resolveFirstArgumentBinding(param) : param;
45965
+ if (parameterBinding) collectFromObjectPattern(parameterBinding, bindings);
45966
+ }
45967
+ const propsParam = resolveFirstArgumentBinding(params[0]);
45475
45968
  const body = functionNode.body;
45476
45969
  if (!propsParam || !isNodeOfType(propsParam, "Identifier") || !body) return bindings;
45477
45970
  if (!isNodeOfType(body, "BlockStatement")) return bindings;
@@ -45977,8 +46470,8 @@ const rnAnimationReactionAsDerived = defineRule({
45977
46470
  if (statements.length !== 1) return;
45978
46471
  const onlyStatement = statements[0];
45979
46472
  if (!isNodeOfType(onlyStatement, "ExpressionStatement")) return;
45980
- singleAssignment = onlyStatement.expression;
45981
- } else if (body) singleAssignment = body;
46473
+ singleAssignment = unwrapDiscardedExpression(onlyStatement);
46474
+ } else if (body) singleAssignment = unwrapDiscardedExpression(body);
45982
46475
  if (!singleAssignment) return;
45983
46476
  const isValueAssignment = isNodeOfType(singleAssignment, "AssignmentExpression") && isNodeOfType(singleAssignment.left, "MemberExpression") && isNodeOfType(singleAssignment.left.object, "Identifier") && isNodeOfType(singleAssignment.left.property, "Identifier") && singleAssignment.left.property.name === "value";
45984
46477
  const isSetCall = isNodeOfType(singleAssignment, "CallExpression") && isNodeOfType(singleAssignment.callee, "MemberExpression") && isNodeOfType(singleAssignment.callee.property, "Identifier") && singleAssignment.callee.property.name === "set" && (singleAssignment.arguments?.length ?? 0) === 1;
@@ -52898,7 +53391,8 @@ const serverCacheWithObjectLiteral = defineRule({
52898
53391
  if (!isNodeOfType(node.callee, "Identifier")) return;
52899
53392
  if (!cachedFunctionNames.has(node.callee.name)) return;
52900
53393
  const firstArg = node.arguments?.[0];
52901
- if (!isNodeOfType(firstArg, "ObjectExpression")) return;
53394
+ if (!firstArg || isNodeOfType(firstArg, "SpreadElement")) return;
53395
+ if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
52902
53396
  context.report({
52903
53397
  node,
52904
53398
  message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
@@ -53585,10 +54079,6 @@ const isInvalidStyleExpression = (expression) => {
53585
54079
  }
53586
54080
  return false;
53587
54081
  };
53588
- const getJsxOpeningElementName = (node) => {
53589
- if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
53590
- return null;
53591
- };
53592
54082
  const stylePropObject = defineRule({
53593
54083
  id: "style-prop-object",
53594
54084
  title: "Style prop is not an object",
@@ -53600,7 +54090,8 @@ const stylePropObject = defineRule({
53600
54090
  const allowSet = new Set(allow);
53601
54091
  return {
53602
54092
  JSXOpeningElement(node) {
53603
- const elementName = getJsxOpeningElementName(node);
54093
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
54094
+ const elementName = resolveJsxElementType(node);
53604
54095
  if (elementName && allowSet.has(elementName)) return;
53605
54096
  if (elementName) {
53606
54097
  const firstCharCode = elementName.charCodeAt(0);
@@ -54305,7 +54796,7 @@ const tanstackStartNoAnchorElement = defineRule({
54305
54796
  create: (context) => {
54306
54797
  if (!isInProjectDirectory(context, "routes")) return {};
54307
54798
  return { JSXOpeningElement(node) {
54308
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
54799
+ if (resolveJsxElementType(node) !== "a") return;
54309
54800
  const attributes = node.attributes ?? [];
54310
54801
  const hrefAttribute = findJsxAttribute(attributes, "href");
54311
54802
  if (!hrefAttribute?.value) return;
@@ -54926,8 +55417,7 @@ const voidDomElementsNoChildren = defineRule({
54926
55417
  create: (context) => ({
54927
55418
  JSXElement(node) {
54928
55419
  const openingElement = node.openingElement;
54929
- if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return;
54930
- const tagName = openingElement.name.name;
55420
+ const tagName = resolveJsxElementType(openingElement);
54931
55421
  if (!VOID_DOM_ELEMENTS.has(tagName)) return;
54932
55422
  const hasChildrenContent = node.children.some(isMeaningfulJsxChild);
54933
55423
  const hasChildrenLikeProp = findChildrenLikePropName(openingElement.attributes);