oxlint-plugin-react-doctor 0.7.6-dev.c5a06bd → 0.7.6-dev.dfd47a7

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 +219 -960
  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.ts
1184
- const getRootIdentifier$1 = (node, options) => {
1183
+ //#region src/plugin/utils/get-root-identifier-name.ts
1184
+ const getRootIdentifierName = (node, options) => {
1185
1185
  if (!node) return null;
1186
1186
  const followCallChains = options?.followCallChains === true;
1187
1187
  let cursor = node;
@@ -1192,18 +1192,16 @@ const getRootIdentifier$1 = (node, options) => {
1192
1192
  continue;
1193
1193
  }
1194
1194
  if (followCallChains && isNodeOfType(cursor, "CallExpression")) {
1195
- if (!isNodeOfType(cursor.callee, "MemberExpression")) return null;
1196
- cursor = cursor.callee.object;
1195
+ const callee = cursor.callee;
1196
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
1197
+ cursor = callee.object;
1197
1198
  continue;
1198
1199
  }
1199
1200
  break;
1200
1201
  }
1201
- return isNodeOfType(cursor, "Identifier") ? cursor : null;
1202
+ return isNodeOfType(cursor, "Identifier") ? cursor.name : null;
1202
1203
  };
1203
1204
  //#endregion
1204
- //#region src/plugin/utils/get-root-identifier-name.ts
1205
- const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
1206
- //#endregion
1207
1205
  //#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
1208
1206
  const STABLE_HANDLER_HOOK_NAMES = new Set([
1209
1207
  "useCallback",
@@ -1520,37 +1518,6 @@ const hasJsxPropIgnoreCase = (attributes, targetProp) => {
1520
1518
  }
1521
1519
  };
1522
1520
  //#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
1554
1521
  //#region src/plugin/utils/get-element-type.ts
1555
1522
  const EMPTY_JSX_A11Y_SETTINGS = Object.freeze({});
1556
1523
  const jsxA11ySettingsCache = /* @__PURE__ */ new WeakMap();
@@ -1563,8 +1530,14 @@ const readJsxA11ySettings = (settings) => {
1563
1530
  jsxA11ySettingsCache.set(settings, a11ySettings);
1564
1531
  return a11ySettings;
1565
1532
  };
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
+ };
1566
1539
  const computeElementType = (openingElement, a11ySettings) => {
1567
- const baseName = resolveJsxElementType(openingElement);
1540
+ const baseName = flattenJsxName$2(openingElement.name);
1568
1541
  if (a11ySettings.polymorphicPropName) {
1569
1542
  const polymorphicAttribute = hasJsxPropIgnoreCase(openingElement.attributes, a11ySettings.polymorphicPropName);
1570
1543
  if (polymorphicAttribute) {
@@ -2399,9 +2372,8 @@ const anchorIsValid = defineRule({
2399
2372
  const isTestlikeFile = isTestlikeFilename(context.filename);
2400
2373
  return { JSXOpeningElement(node) {
2401
2374
  if (isTestlikeFile) return;
2402
- const tag = getElementType(node, context.settings);
2403
- if (!fileHasJsxA11ySettings && tag !== "a") return;
2404
- if (tag !== "a") return;
2375
+ if (!fileHasJsxA11ySettings && (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a")) return;
2376
+ if (getElementType(node, context.settings) !== "a") return;
2405
2377
  let hrefAttribute;
2406
2378
  for (const attributeName of settings.hrefAttributeNames) {
2407
2379
  hrefAttribute = hasJsxPropIgnoreCase(node.attributes, attributeName);
@@ -5756,7 +5728,7 @@ const buttonHasType = defineRule({
5756
5728
  return {
5757
5729
  JSXOpeningElement(node) {
5758
5730
  if (isTestlikeFile) return;
5759
- if (resolveJsxElementType(node) !== "button") return;
5731
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "button") return;
5760
5732
  const typeAttr = hasJsxPropIgnoreCase(node.attributes, "type");
5761
5733
  if (!typeAttr) {
5762
5734
  if (hasJsxSpreadAttribute$1(node.attributes)) {
@@ -5946,7 +5918,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5946
5918
  };
5947
5919
  return {
5948
5920
  JSXOpeningElement(node) {
5949
- if (resolveJsxElementType(node) !== "input") return;
5921
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
5950
5922
  reportFromPresence(collectFromJsxAttributes(node.attributes));
5951
5923
  },
5952
5924
  CallExpression(node) {
@@ -5964,7 +5936,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5964
5936
  //#endregion
5965
5937
  //#region src/plugin/utils/get-static-property-key-name.ts
5966
5938
  const getStaticPropertyKeyName = (node, options = {}) => {
5967
- if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition")) return null;
5939
+ if (!isNodeOfType(node, "Property")) return null;
5968
5940
  if (node.computed) {
5969
5941
  if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
5970
5942
  return null;
@@ -6023,7 +5995,7 @@ const isPureEventBlockerHandler = (attribute) => {
6023
5995
  //#endregion
6024
5996
  //#region src/plugin/utils/resolve-const-identifier-alias.ts
6025
5997
  const resolveConstIdentifierAlias = (identifier, scopes) => {
6026
- if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
5998
+ if (!isNodeOfType(identifier, "Identifier")) return null;
6027
5999
  const visitedSymbolIds = /* @__PURE__ */ new Set();
6028
6000
  let symbol = scopes.symbolFor(identifier);
6029
6001
  while (symbol?.kind === "const") {
@@ -6298,131 +6270,6 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
6298
6270
  return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6299
6271
  };
6300
6272
  //#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
6426
6273
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6427
6274
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6428
6275
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
@@ -6444,39 +6291,26 @@ const clientLocalstorageNoVersion = defineRule({
6444
6291
  severity: "warn",
6445
6292
  category: "Correctness",
6446
6293
  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.",
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
- }
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
+ } })
6480
6314
  });
6481
6315
  //#endregion
6482
6316
  //#region src/plugin/rules/client/client-passive-event-listeners.ts
@@ -7599,7 +7433,7 @@ const findJsxAttribute = (attributes, attributeName) => attributes?.find((attrib
7599
7433
  const getOpeningElementTagName = (openingElement) => {
7600
7434
  if (!openingElement) return null;
7601
7435
  if (!isNodeOfType(openingElement, "JSXOpeningElement")) return null;
7602
- if (isNodeOfType(openingElement.name, "JSXIdentifier")) return resolveJsxElementType(openingElement);
7436
+ if (isNodeOfType(openingElement.name, "JSXIdentifier")) return openingElement.name.name;
7603
7437
  if (isNodeOfType(openingElement.name, "JSXMemberExpression")) {
7604
7438
  let cursor = openingElement.name;
7605
7439
  while (isNodeOfType(cursor, "JSXMemberExpression")) cursor = cursor.property;
@@ -7851,7 +7685,7 @@ const dialogHasAccessibleName = defineRule({
7851
7685
  if (isTestlikeFilename(context.filename)) return {};
7852
7686
  return { JSXOpeningElement(node) {
7853
7687
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
7854
- const tagName = resolveJsxElementType(node);
7688
+ const tagName = node.name.name;
7855
7689
  if (tagName[0] !== tagName[0]?.toLowerCase()) return;
7856
7690
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
7857
7691
  const roleValue = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
@@ -12083,7 +11917,7 @@ const forbidDomProps = defineRule({
12083
11917
  return { JSXOpeningElement(node) {
12084
11918
  if (forbidMap.size === 0) return;
12085
11919
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12086
- const elementName = resolveJsxElementType(node);
11920
+ const elementName = node.name.name;
12087
11921
  if (isReactComponentName(elementName)) return;
12088
11922
  for (const attribute of node.attributes) {
12089
11923
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -12161,7 +11995,7 @@ const forbidElements = defineRule({
12161
11995
  return {
12162
11996
  JSXOpeningElement(node) {
12163
11997
  if (forbidMap.size === 0) return;
12164
- const fullName = resolveJsxElementType(node);
11998
+ const fullName = flattenJsxName$1(node.name);
12165
11999
  if (!fullName || !forbidMap.has(fullName)) return;
12166
12000
  context.report({
12167
12001
  node: node.name,
@@ -12523,7 +12357,8 @@ const buildMessage$22 = (childTagName) => `Your users get reshuffled HTML becaus
12523
12357
  const isParagraphElement = (candidate) => {
12524
12358
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12525
12359
  const opening = candidate.openingElement;
12526
- return resolveJsxElementType(opening) === "p";
12360
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12361
+ return opening.name.name === "p";
12527
12362
  };
12528
12363
  const findEnclosingParagraph = (openingElement) => {
12529
12364
  const owningElement = openingElement.parent;
@@ -12544,7 +12379,8 @@ const htmlNoInvalidParagraphChild = defineRule({
12544
12379
  severity: "warn",
12545
12380
  recommendation: "Swap the `<p>` for a `<div>`, or move the child outside the paragraph.",
12546
12381
  create: (context) => ({ JSXOpeningElement(node) {
12547
- const childTagName = resolveJsxElementType(node);
12382
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12383
+ const childTagName = node.name.name;
12548
12384
  if (!BLOCK_LEVEL_ELEMENTS.has(childTagName)) return;
12549
12385
  if (!findEnclosingParagraph(node)) return;
12550
12386
  context.report({
@@ -12575,7 +12411,7 @@ const getHostTagName = (jsxElement) => {
12575
12411
  if (!isNodeOfType(jsxElement, "JSXElement")) return null;
12576
12412
  const opening = jsxElement.openingElement;
12577
12413
  if (!isNodeOfType(opening.name, "JSXIdentifier")) return null;
12578
- const tagName = resolveJsxElementType(opening);
12414
+ const tagName = opening.name.name;
12579
12415
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return null;
12580
12416
  return tagName;
12581
12417
  };
@@ -12605,7 +12441,7 @@ const findClosestHostAncestor = (jsxElement) => {
12605
12441
  if (isNodeOfType(ancestor, "JSXElement")) {
12606
12442
  const opening = ancestor.openingElement;
12607
12443
  if (isNodeOfType(opening.name, "JSXIdentifier")) {
12608
- const ancestorTag = resolveJsxElementType(opening);
12444
+ const ancestorTag = opening.name.name;
12609
12445
  if (ancestorTag.length === 0) {
12610
12446
  previous = ancestor;
12611
12447
  ancestor = ancestor.parent ?? null;
@@ -12687,7 +12523,8 @@ const buildMessage$20 = (tagName) => `Your users get broken clicks, focus & scre
12687
12523
  const isJsxElementWithTagName = (candidate, tagName) => {
12688
12524
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12689
12525
  const opening = candidate.openingElement;
12690
- return resolveJsxElementType(opening) === tagName;
12526
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12527
+ return opening.name.name === tagName;
12691
12528
  };
12692
12529
  const findEnclosingSameTag = (openingElement, tagName) => {
12693
12530
  const owningElement = openingElement.parent;
@@ -12708,7 +12545,8 @@ const htmlNoNestedInteractive = defineRule({
12708
12545
  severity: "warn",
12709
12546
  recommendation: "Move the inner `<a>` or `<button>` so it's a sibling, or change the outer one to a plain `<div>` or `<span>`.",
12710
12547
  create: (context) => ({ JSXOpeningElement(node) {
12711
- const tagName = resolveJsxElementType(node);
12548
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12549
+ const tagName = node.name.name;
12712
12550
  if (tagName !== "a" && tagName !== "button") return;
12713
12551
  if (!findEnclosingSameTag(node, tagName)) return;
12714
12552
  context.report({
@@ -12823,7 +12661,7 @@ const iframeMissingSandbox = defineRule({
12823
12661
  matchByOccurrence: true,
12824
12662
  create: skipNonProductionFiles((context) => ({
12825
12663
  JSXOpeningElement(node) {
12826
- if (resolveJsxElementType(node) !== "iframe") return;
12664
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "iframe") return;
12827
12665
  const sandboxAttr = hasJsxPropIgnoreCase(node.attributes, "sandbox");
12828
12666
  if (!sandboxAttr) {
12829
12667
  const hasExplicitSrc = Boolean(hasJsxPropIgnoreCase(node.attributes, "src"));
@@ -13815,28 +13653,6 @@ const jsAsyncReduceWithoutAwaitedAcc = defineRule({
13815
13653
  } })
13816
13654
  });
13817
13655
  //#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
13840
13656
  //#region src/plugin/rules/js-performance/js-batch-dom-css.ts
13841
13657
  const ITERATOR_METHOD_NAMES$2 = new Set([
13842
13658
  "forEach",
@@ -14021,19 +13837,9 @@ const hasAttachmentBefore = (scopeOwner, elementName, beforeStart) => {
14021
13837
  });
14022
13838
  return foundAttachment;
14023
13839
  };
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
- };
14033
13840
  const isProvablyDetachedAtWrite = (styleWriteStatement) => {
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;
13841
+ if (!isNodeOfType(styleWriteStatement, "ExpressionStatement") || !isNodeOfType(styleWriteStatement.expression, "AssignmentExpression") || !isNodeOfType(styleWriteStatement.expression.left, "MemberExpression") || !isNodeOfType(styleWriteStatement.expression.left.object, "MemberExpression")) return false;
13842
+ const elementExpression = styleWriteStatement.expression.left.object.object;
14037
13843
  const creationRoot = resolveDetachedCreationRoot(elementExpression, 0);
14038
13844
  if (!creationRoot) return false;
14039
13845
  return !hasAttachmentBefore(creationRoot.scopeOwner, creationRoot.rootName, getNodeStart$1(styleWriteStatement));
@@ -14045,17 +13851,15 @@ const jsBatchDomCss = defineRule({
14045
13851
  severity: "warn",
14046
13852
  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",
14047
13853
  create: (context) => {
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
- };
13854
+ 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";
13855
+ 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);
14052
13856
  return { BlockStatement(node) {
14053
13857
  const perIterationBody = findEnclosingPerIterationBody(node);
14054
13858
  if (!perIterationBody) return;
14055
13859
  const statements = node.body ?? [];
14056
13860
  let layoutReads = null;
14057
13861
  for (let statementIndex = 1; statementIndex < statements.length; statementIndex++) {
14058
- if (getStyleAssignment(statements[statementIndex]) === null || getStyleAssignment(statements[statementIndex - 1]) === null) continue;
13862
+ if (!isStyleAssignment(statements[statementIndex]) || !isStyleAssignment(statements[statementIndex - 1])) continue;
14059
13863
  if (!writesLayoutAffectingProperty(statements[statementIndex]) && !writesLayoutAffectingProperty(statements[statementIndex - 1])) continue;
14060
13864
  layoutReads ??= scanPerIterationLayoutReads(perIterationBody);
14061
13865
  if (!layoutReads.hasUsedLayoutRead || layoutReads.hasDeliberateForcedReflow) return;
@@ -14751,18 +14555,6 @@ const jsHoistRegexp = defineRule({
14751
14555
  }, { treatIteratorCallbacksAsLoops: true })
14752
14556
  });
14753
14557
  //#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
14766
14558
  //#region src/plugin/rules/js-performance/js-index-maps.ts
14767
14559
  const referencesParameter = (expression, parameterName) => {
14768
14560
  if (!expression) return false;
@@ -14773,7 +14565,7 @@ const referencesParameter = (expression, parameterName) => {
14773
14565
  const isSingleFieldEqualityPredicate = (node) => {
14774
14566
  const callback = node.arguments?.[0];
14775
14567
  if (!isInlineFunctionExpression(callback)) return false;
14776
- const firstParameter = resolveFirstArgumentBinding(callback.params?.[0]);
14568
+ const firstParameter = callback.params?.[0];
14777
14569
  if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
14778
14570
  let predicate = null;
14779
14571
  const body = callback.body;
@@ -15465,21 +15257,9 @@ const isSmallFixedListMember = (receiver) => {
15465
15257
  };
15466
15258
  const getResolvedInitializer = (receiver) => {
15467
15259
  if (!isNodeOfType(receiver, "Identifier")) return null;
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
- };
15260
+ const initializer = findVariableInitializer(receiver, receiver.name)?.initializer ?? null;
15261
+ if (initializer && isNodeOfType(initializer, "Identifier")) return findVariableInitializer(initializer, initializer.name)?.initializer ?? initializer;
15262
+ return initializer;
15483
15263
  };
15484
15264
  const isSplitCall = (expression) => {
15485
15265
  if (!expression) return false;
@@ -15645,8 +15425,8 @@ const isBoundedConstantCollection = (collection) => {
15645
15425
  if (isScreamingSnakeCaseConstantReceiver(stripped)) return true;
15646
15426
  if (isSmallInlineLiteralArray(stripped)) return true;
15647
15427
  if (isNodeOfType(stripped, "Identifier")) {
15648
- const resolved = getResolvedInitializer(stripped);
15649
- if (resolved && !resolved.isDefault && isSmallInlineLiteralArray(resolved.initializer)) return true;
15428
+ const initializer = getResolvedInitializer(stripped);
15429
+ if (initializer && isSmallInlineLiteralArray(initializer)) return true;
15650
15430
  }
15651
15431
  return false;
15652
15432
  };
@@ -15698,8 +15478,8 @@ const jsSetMapLookups = defineRule({
15698
15478
  if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
15699
15479
  const resolvedInitializer = getResolvedInitializer(receiver);
15700
15480
  if (resolvedInitializer) {
15701
- if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
15702
- if (!resolvedInitializer.isDefault && isSmallInlineLiteralArray(resolvedInitializer.initializer)) return;
15481
+ if (isLikelyStringReceiver(resolvedInitializer)) return;
15482
+ if (isSmallInlineLiteralArray(resolvedInitializer)) return;
15703
15483
  }
15704
15484
  if (isStringElementOfSplitIteration(receiver)) return;
15705
15485
  if (isReceiverDeclaredInNearestLoop(receiver, node)) return;
@@ -16112,21 +15892,14 @@ const jsxFilenameExtension = defineRule({
16112
15892
  });
16113
15893
  //#endregion
16114
15894
  //#region src/plugin/utils/is-jsx-fragment-element.ts
16115
- const isJsxFragmentElement = (node, scopes) => {
15895
+ const isJsxFragmentElement = (node) => {
16116
15896
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
16117
15897
  const elementName = node.name;
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
- }
15898
+ if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
16124
15899
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
16125
15900
  if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
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);
15901
+ if (elementName.object.name !== "React") return false;
15902
+ return elementName.property.name === "Fragment";
16130
15903
  }
16131
15904
  return false;
16132
15905
  };
@@ -16152,7 +15925,7 @@ const jsxFragments = defineRule({
16152
15925
  if (mode !== "syntax") return;
16153
15926
  if (!node.closingElement) return;
16154
15927
  const openingElement = node.openingElement;
16155
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
15928
+ if (!isJsxFragmentElement(openingElement)) return;
16156
15929
  if (openingElement.attributes.length > 0) return;
16157
15930
  context.report({
16158
15931
  node: openingElement,
@@ -18833,6 +18606,10 @@ const resolveSettings$29 = (settings) => {
18833
18606
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
18834
18607
  return reactDoctor.jsxNoScriptUrl ?? {};
18835
18608
  };
18609
+ const getElementName = (node) => {
18610
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
18611
+ return null;
18612
+ };
18836
18613
  const isLinkPropForElement = (elementName, attributeName, options) => {
18837
18614
  if (elementName === "a" && attributeName === "href") return true;
18838
18615
  const explicit = options.components?.[elementName];
@@ -18852,8 +18629,7 @@ const jsxNoScriptUrl = defineRule({
18852
18629
  create: (context) => {
18853
18630
  const options = resolveSettings$29(context.settings);
18854
18631
  return { JSXOpeningElement(node) {
18855
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18856
- const elementName = resolveJsxElementType(node);
18632
+ const elementName = getElementName(node);
18857
18633
  if (!elementName) return;
18858
18634
  for (const attribute of node.attributes) {
18859
18635
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -19040,6 +18816,11 @@ const checkTarget = (attributeValue) => {
19040
18816
  alternate: false
19041
18817
  };
19042
18818
  };
18819
+ const getOpeningElementName = (node) => {
18820
+ const name = node.name;
18821
+ if (isNodeOfType(name, "JSXIdentifier")) return name.name;
18822
+ return null;
18823
+ };
19043
18824
  const jsxNoTargetBlank = defineRule({
19044
18825
  id: "jsx-no-target-blank",
19045
18826
  title: "Unsafe target=_blank link",
@@ -19059,8 +18840,7 @@ const jsxNoTargetBlank = defineRule({
19059
18840
  return settings.formComponents.has(tagName);
19060
18841
  };
19061
18842
  return { JSXOpeningElement(node) {
19062
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
19063
- const tagName = resolveJsxElementType(node);
18843
+ const tagName = getOpeningElementName(node);
19064
18844
  if (!tagName) return;
19065
18845
  if (!isLink(tagName) && !isForm(tagName)) return;
19066
18846
  const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
@@ -19293,7 +19073,7 @@ const jsxNoUselessFragment = defineRule({
19293
19073
  return {
19294
19074
  JSXElement(node) {
19295
19075
  const openingElement = node.openingElement;
19296
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
19076
+ if (!isJsxFragmentElement(openingElement)) return;
19297
19077
  if (hasJsxKeyAttribute(openingElement)) return;
19298
19078
  if (checkChildren(node, openingElement, node.children)) return;
19299
19079
  if (isChildOfHtmlElement(node)) context.report({
@@ -20535,24 +20315,11 @@ const hasDirective = (programNode, directive) => {
20535
20315
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20536
20316
  };
20537
20317
  //#endregion
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
- };
20318
+ //#region src/plugin/utils/is-uppercase-name.ts
20319
+ const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
20320
+ //#endregion
20321
+ //#region src/plugin/utils/is-component-assignment.ts
20322
+ 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"));
20556
20323
  //#endregion
20557
20324
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
20558
20325
  const nextjsAsyncClientComponent = defineRule({
@@ -20578,10 +20345,10 @@ const nextjsAsyncClientComponent = defineRule({
20578
20345
  },
20579
20346
  VariableDeclarator(node) {
20580
20347
  if (!fileHasUseClient) return;
20348
+ if (!isComponentAssignment(node)) return;
20349
+ if (!isInlineFunctionExpression(node.init)) return;
20350
+ if (!node.init.async) return;
20581
20351
  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;
20585
20352
  context.report({
20586
20353
  node,
20587
20354
  message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
@@ -20883,7 +20650,7 @@ const nextjsNoAElement = defineRule({
20883
20650
  severity: "warn",
20884
20651
  recommendation: "`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",
20885
20652
  create: (context) => ({ JSXOpeningElement(node) {
20886
- if (resolveJsxElementType(node) !== "a") return;
20653
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
20887
20654
  const attributes = node.attributes ?? [];
20888
20655
  const downloadAttribute = findJsxAttribute(attributes, "download");
20889
20656
  if (downloadAttribute) {
@@ -21079,7 +20846,7 @@ const nextjsNoCssLink = defineRule({
21079
20846
  severity: "warn",
21080
20847
  recommendation: "Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.",
21081
20848
  create: (context) => ({ JSXOpeningElement(node) {
21082
- if (resolveJsxElementType(node) !== "link") return;
20849
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21083
20850
  const attributes = node.attributes ?? [];
21084
20851
  const relAttribute = findJsxAttribute(attributes, "rel");
21085
20852
  if (!relAttribute?.value) return;
@@ -21187,7 +20954,7 @@ const nextjsNoFontLink = defineRule({
21187
20954
  severity: "warn",
21188
20955
  recommendation: "`import { Inter } from \"next/font/google\"` for self-hosting, zero layout shift, and no render-blocking requests",
21189
20956
  create: (context) => ({ JSXOpeningElement(node) {
21190
- if (resolveJsxElementType(node) !== "link") return;
20957
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21191
20958
  const attributes = node.attributes ?? [];
21192
20959
  const hrefAttribute = findJsxAttribute(attributes, "href");
21193
20960
  if (!hrefAttribute?.value) return;
@@ -21362,7 +21129,7 @@ const nextjsNoImgElement = defineRule({
21362
21129
  create: (context) => {
21363
21130
  if (isGeneratedImageRenderContext(context)) return {};
21364
21131
  return { JSXOpeningElement(node) {
21365
- if (resolveJsxElementType(node) !== "img") return;
21132
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
21366
21133
  if (isGeneratedImageRenderContext(context, node)) return;
21367
21134
  const programRoot = findProgramRoot(node);
21368
21135
  if (programRoot && hasEmailTemplateImport(programRoot)) return;
@@ -21409,8 +21176,8 @@ const nextjsNoPolyfillScript = defineRule({
21409
21176
  severity: "warn",
21410
21177
  recommendation: "Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically",
21411
21178
  create: (context) => ({ JSXOpeningElement(node) {
21412
- const elementName = resolveJsxElementType(node);
21413
- if (elementName !== "script" && elementName !== "Script") return;
21179
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
21180
+ if (node.name.name !== "script" && node.name.name !== "Script") return;
21414
21181
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21415
21182
  if (!srcAttribute?.value) return;
21416
21183
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
@@ -23218,15 +22985,10 @@ const isProp = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
23218
22985
  if (!declaringNode) return false;
23219
22986
  return isReactFunctionalComponent(declaringNode) && !isReactFunctionalHOC(analysis, declaringNode) || isCustomHook(declaringNode);
23220
22987
  }));
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
- };
23227
22988
  const isWholePropsObjectReference = (analysis, ref) => isProp(analysis, ref) && Boolean(ref.resolved?.defs.some((def) => {
23228
22989
  if (def.type !== "Parameter") return false;
23229
- return isWholePropsParameterBinding(def.name);
22990
+ const bindingParent = def.name.parent;
22991
+ return isFunctionLike$1(bindingParent);
23230
22992
  }));
23231
22993
  const isIdentifierOrMemberExpression = (node) => isNodeOfType(node, "Identifier") || isNodeOfType(node, "MemberExpression");
23232
22994
  const isPropAlias = (analysis, ref) => {
@@ -26223,56 +25985,6 @@ const noBarrelImport = defineRule({
26223
25985
  }
26224
25986
  });
26225
25987
  //#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
26276
25988
  //#region src/plugin/utils/function-contains-react-render-output.ts
26277
25989
  const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
26278
25990
  "FunctionDeclaration",
@@ -26292,13 +26004,12 @@ const isCallArgumentFunctionExpression = (node) => {
26292
26004
  return parent.arguments.some((argumentNode) => argumentNode === node);
26293
26005
  };
26294
26006
  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);
26296
26007
  const containsRenderOutput$1 = (rootNode, scopes) => {
26297
26008
  let hasRenderOutput = false;
26298
26009
  walkAst(rootNode, (node) => {
26299
26010
  if (hasRenderOutput) return false;
26300
26011
  if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
26301
- if (isRenderOutputExpression(node, scopes)) {
26012
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
26302
26013
  hasRenderOutput = true;
26303
26014
  return false;
26304
26015
  }
@@ -26309,7 +26020,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
26309
26020
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
26310
26021
  const cachedEntry = renderOutputCache.get(functionNode);
26311
26022
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
26312
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
26023
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
26313
26024
  renderOutputCache.set(functionNode, {
26314
26025
  scopes,
26315
26026
  hasRenderOutput
@@ -26317,9 +26028,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
26317
26028
  return hasRenderOutput;
26318
26029
  };
26319
26030
  //#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
26323
26031
  //#region src/plugin/utils/is-component-declaration.ts
26324
26032
  const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
26325
26033
  //#endregion
@@ -28777,7 +28485,7 @@ const noDisabledZoom = defineRule({
28777
28485
  category: "Accessibility",
28778
28486
  recommendation: "Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If the layout breaks at 200% zoom, fix the layout instead.",
28779
28487
  create: (context) => ({ JSXOpeningElement(node) {
28780
- if (resolveJsxElementType(node) !== "meta") return;
28488
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "meta") return;
28781
28489
  const nameAttr = findJsxAttribute(node.attributes ?? [], "name");
28782
28490
  if (!nameAttr?.value) return;
28783
28491
  if ((isNodeOfType(nameAttr.value, "Literal") ? nameAttr.value.value : null) !== "viewport") return;
@@ -29167,7 +28875,7 @@ const findTopLevelEffectCalls = (componentBody) => {
29167
28875
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
29168
28876
  for (const statement of componentBody.body ?? []) {
29169
28877
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
29170
- const expression = unwrapDiscardedExpression(statement);
28878
+ const expression = statement.expression;
29171
28879
  if (!isNodeOfType(expression, "CallExpression")) continue;
29172
28880
  if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
29173
28881
  effectCalls.push(expression);
@@ -31949,7 +31657,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
31949
31657
  severity: "warn",
31950
31658
  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.",
31951
31659
  create: (context) => ({ JSXOpeningElement(node) {
31952
- if (resolveJsxElementType(node) !== "img") return;
31660
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
31953
31661
  const loadingAttribute = hasJsxPropIgnoreCase(node.attributes, "loading");
31954
31662
  if (!loadingAttribute || getJsxPropStringValue(loadingAttribute)?.toLowerCase() !== "lazy") return;
31955
31663
  const fetchPriorityAttribute = hasJsxPropIgnoreCase(node.attributes, "fetchPriority");
@@ -31961,26 +31669,6 @@ const noImgLazyWithHighFetchpriority = defineRule({
31961
31669
  } })
31962
31670
  });
31963
31671
  //#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
31984
31672
  //#region src/plugin/rules/state-and-effects/no-impure-state-updater.ts
31985
31673
  const TIMER_FUNCTION_NAMES = new Set([
31986
31674
  "cancelAnimationFrame",
@@ -32012,33 +31700,6 @@ const NOTIFICATION_METHOD_NAMES = new Set([
32012
31700
  "success",
32013
31701
  "warning"
32014
31702
  ]);
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
- };
32042
31703
  const getMemberCall = (node) => {
32043
31704
  if (!isNodeOfType(node, "CallExpression")) return null;
32044
31705
  if (!isNodeOfType(node.callee, "MemberExpression") || node.callee.computed) return null;
@@ -32050,37 +31711,27 @@ const getMemberCall = (node) => {
32050
31711
  };
32051
31712
  const isNotificationReceiver = (receiver, scopes) => {
32052
31713
  if (!isNodeOfType(receiver, "Identifier")) 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));
31714
+ if (!NOTIFICATION_RECEIVER_NAMES.has(receiver.name)) return false;
32055
31715
  const symbol = scopes.symbolFor(receiver);
31716
+ if (symbol?.kind === "import") return true;
32056
31717
  if (!isNodeOfType(symbol?.initializer, "CallExpression")) return false;
32057
31718
  const callee = symbol.initializer.callee;
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;
31719
+ return isNodeOfType(callee, "Identifier") && /^use(?:Message|Notification|Toast)$/.test(callee.name);
32068
31720
  };
32069
31721
  const getKnownImpureCall = (callExpression, scopes) => {
32070
31722
  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}()`;
32072
31723
  const memberCall = getMemberCall(callExpression);
32073
31724
  if (!memberCall) return null;
32074
31725
  const { methodName, receiver } = memberCall;
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}()`;
31726
+ if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && STORAGE_RECEIVER_NAMES.has(receiver.name) && scopes.isGlobalReference(receiver)) return `${receiver.name}.${methodName}()`;
31727
+ if (EXTERNAL_READ_METHOD_NAMES.has(methodName)) return `.${methodName}()`;
31728
+ if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && isNotificationReceiver(receiver, scopes)) return `${receiver.name}.${methodName}()`;
32080
31729
  return null;
32081
31730
  };
32082
31731
  const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) => {
32083
- const rootIdentifier = getRootIdentifier$1(assignmentTarget);
31732
+ let rootIdentifier = null;
31733
+ if (isNodeOfType(assignmentTarget, "Identifier")) rootIdentifier = assignmentTarget;
31734
+ else if (isNodeOfType(assignmentTarget, "MemberExpression") && isNodeOfType(assignmentTarget.object, "Identifier")) rootIdentifier = assignmentTarget.object;
32084
31735
  if (!rootIdentifier) return null;
32085
31736
  const updaterScope = scopes.ownScopeFor(updater);
32086
31737
  if (!updaterScope) return null;
@@ -32089,26 +31740,12 @@ const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) =>
32089
31740
  if (symbol.kind === "parameter" && symbol.scope === updaterScope) return `the updater argument "${rootIdentifier.name}"`;
32090
31741
  return isDescendantScope(symbol.scope, updaterScope) ? null : `the captured value "${rootIdentifier.name}"`;
32091
31742
  };
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
- };
32106
31743
  const findImpureUpdaterOperation = (updater, scopes) => {
32107
31744
  const analysis = getProgramAnalysis(updater);
32108
31745
  let operation = null;
32109
31746
  walkAst(updater, (child) => {
32110
31747
  if (operation) return false;
32111
- if (child !== updater && isFunctionLike$1(child) && !isDefinitelySynchronousCallback(child, scopes)) return false;
31748
+ if (child !== updater && isFunctionLike$1(child) && !executesDuringRender(child, scopes)) return false;
32112
31749
  if (isNodeOfType(child, "CallExpression")) {
32113
31750
  if (isNodeOfType(child.callee, "Identifier") && analysis) {
32114
31751
  const calleeReference = getRef(analysis, child.callee);
@@ -32140,25 +31777,18 @@ const noImpureStateUpdater = defineRule({
32140
31777
  severity: "error",
32141
31778
  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.",
32142
31779
  create: (context) => ({ CallExpression(node) {
32143
- const updaterArgument = node.arguments?.[0];
32144
- if (!updaterArgument) return;
31780
+ const updater = node.arguments?.[0];
31781
+ if (!updater || !isFunctionLike$1(updater)) return;
32145
31782
  const analysis = getProgramAnalysis(node);
32146
31783
  if (!analysis || !isNodeOfType(node.callee, "Identifier")) return;
32147
31784
  const calleeReference = getRef(analysis, node.callee);
32148
31785
  if (!calleeReference || !isStateSetterCall(analysis, calleeReference)) return;
32149
31786
  const stateDeclarator = getUseStateDecl(analysis, calleeReference);
32150
31787
  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;
32158
31788
  const operation = findImpureUpdaterOperation(updater, context.scopes);
32159
31789
  if (!operation) return;
32160
31790
  context.report({
32161
- node: updaterArgument,
31791
+ node: updater,
32162
31792
  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.`
32163
31793
  });
32164
31794
  } })
@@ -32268,7 +31898,7 @@ const noIndeterminateAttribute = defineRule({
32268
31898
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
32269
31899
  return {
32270
31900
  JSXOpeningElement(node) {
32271
- if (resolveJsxElementType(node) !== "input") return;
31901
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
32272
31902
  let typeAttribute = null;
32273
31903
  let typeAttributeIndex = null;
32274
31904
  let indeterminateAttribute = null;
@@ -32447,12 +32077,11 @@ const isMemoCall = (node) => {
32447
32077
  };
32448
32078
  const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
32449
32079
  const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
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";
32080
+ const isInlineReference = (node) => {
32081
+ 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";
32082
+ if (isNodeOfType(node, "ObjectExpression")) return "objects";
32083
+ if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
32084
+ if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
32456
32085
  return null;
32457
32086
  };
32458
32087
  const noInlinePropOnMemoComponent = defineRule({
@@ -32482,7 +32111,7 @@ const noInlinePropOnMemoComponent = defineRule({
32482
32111
  let elementName = null;
32483
32112
  if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
32484
32113
  if (!elementName || !memoizedComponentNames.has(elementName)) return;
32485
- const propType = isInlineReference(node.value.expression, context.scopes);
32114
+ const propType = isInlineReference(node.value.expression);
32486
32115
  if (propType) context.report({
32487
32116
  node: node.value.expression,
32488
32117
  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`
@@ -33003,18 +32632,6 @@ const INTL_FORMAT_METHOD_NAMES = new Set([
33003
32632
  "formatToParts",
33004
32633
  "formatRange"
33005
32634
  ]);
33006
- const ABSENT_PROPERTY_PROOF = { status: "absent" };
33007
- const PRESENT_PROPERTY_PROOF = { status: "present" };
33008
- const UNDEFINED_PROPERTY_PROOF = { status: "undefined" };
33009
- const UNKNOWN_PROPERTY_PROOF = { status: "unknown" };
33010
- const READ_ONLY_OBJECT_METHOD_NAMES = new Set([
33011
- "hasOwnProperty",
33012
- "isPrototypeOf",
33013
- "propertyIsEnumerable",
33014
- "toLocaleString",
33015
- "toString",
33016
- "valueOf"
33017
- ]);
33018
32635
  const isProvableDateExpression = (expression) => {
33019
32636
  if (!expression) return false;
33020
32637
  const unwrapped = stripParenExpression(expression);
@@ -33029,290 +32646,32 @@ const receiverNameLooksDateFlavored = (expression) => {
33029
32646
  if (isNodeOfType(unwrapped, "CallExpression")) return receiverNameLooksDateFlavored(unwrapped.callee);
33030
32647
  return false;
33031
32648
  };
33032
- const isStaticUndefined = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33033
- const expression = stripParenExpression(node);
33034
- if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return true;
33035
- if (!isNodeOfType(expression, "Identifier")) return false;
33036
- if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
33037
- const symbol = scopes.symbolFor(expression);
33038
- if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || visitedSymbolIds.has(symbol.id)) return false;
33039
- visitedSymbolIds.add(symbol.id);
33040
- return isStaticUndefined(symbol.initializer, scopes, visitedSymbolIds);
33041
- };
33042
- const isNestedAssignmentTarget = (expression) => {
33043
- let target = expression;
33044
- let parent = target.parent;
33045
- while (parent) {
33046
- if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === target;
33047
- if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left === target;
33048
- if (isNodeOfType(parent, "AssignmentPattern")) {
33049
- if (parent.left !== target) return false;
33050
- } else if (isNodeOfType(parent, "RestElement")) {
33051
- if (parent.argument !== target) return false;
33052
- } else if (isNodeOfType(parent, "ArrayPattern")) {
33053
- if (!parent.elements?.some((element) => element === target)) return false;
33054
- } else if (isNodeOfType(parent, "Property")) {
33055
- if (parent.value !== target || !isNodeOfType(parent.parent, "ObjectPattern")) return false;
33056
- } else if (isNodeOfType(parent, "ObjectPattern")) {
33057
- if (!parent.properties?.some((property) => property === target)) return false;
33058
- } else return false;
33059
- target = parent;
33060
- parent = target.parent;
33061
- }
33062
- return false;
33063
- };
33064
- const isPotentialMutationReference = (identifier, readNode) => {
33065
- let expression = identifier;
33066
- let parent = expression.parent;
33067
- while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
33068
- expression = parent;
33069
- parent = expression.parent;
33070
- }
33071
- const rootExpression = expression;
33072
- let memberDepth = 0;
33073
- while (parent && isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
33074
- memberDepth += 1;
33075
- expression = parent;
33076
- parent = expression.parent;
33077
- while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
33078
- expression = parent;
33079
- parent = expression.parent;
33080
- }
33081
- }
33082
- if (!parent || isAstDescendant(identifier, readNode)) return false;
33083
- if (isNestedAssignmentTarget(expression)) return true;
33084
- if (isNodeOfType(parent, "AssignmentExpression") && parent.left === expression) return true;
33085
- if (isNodeOfType(parent, "UpdateExpression") && parent.argument === expression) return true;
33086
- if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expression) return true;
33087
- if (isNodeOfType(parent, "CallExpression")) {
33088
- if (parent.callee === expression) {
33089
- if (memberDepth === 0) return true;
33090
- const memberExpression = stripParenExpression(expression);
33091
- return memberDepth === 1 && isNodeOfType(memberExpression, "MemberExpression") && !READ_ONLY_OBJECT_METHOD_NAMES.has(getStaticPropertyName(memberExpression) ?? "");
33092
- }
33093
- return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
33094
- }
33095
- if (isNodeOfType(parent, "NewExpression")) return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
33096
- if (isNodeOfType(parent, "AssignmentExpression") && parent.right === rootExpression) return true;
33097
- return false;
33098
- };
33099
- const getSimpleAlias = (identifier, scopes) => {
33100
- let expression = identifier;
33101
- let parent = expression.parent;
33102
- while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
33103
- expression = parent;
33104
- parent = expression.parent;
33105
- }
33106
- if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== expression || !isNodeOfType(parent.id, "Identifier")) return null;
33107
- const symbol = scopes.symbolFor(parent.id);
33108
- return symbol ? {
33109
- symbol,
33110
- readNode: parent.id
33111
- } : null;
33112
- };
33113
- const getDirectCallForExpression = (expression) => {
33114
- let callee = expression;
33115
- let parent = callee.parent;
33116
- while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === callee) {
33117
- callee = parent;
33118
- parent = callee.parent;
33119
- }
33120
- return isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
33121
- };
33122
- const getMethodOwner = (functionNode, scopes) => {
33123
- const methodNode = functionNode.parent;
33124
- if (isNodeOfType(methodNode, "Property") && methodNode.value === functionNode && isNodeOfType(methodNode.parent, "ObjectExpression")) {
33125
- const objectParent = methodNode.parent.parent;
33126
- if (!isNodeOfType(objectParent, "VariableDeclarator") || objectParent.init !== methodNode.parent || !isNodeOfType(objectParent.id, "Identifier")) return null;
33127
- const ownerSymbol = scopes.symbolFor(objectParent.id);
33128
- const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
33129
- return ownerSymbol && methodName ? {
33130
- methodName,
33131
- ownerKind: "object",
33132
- ownerSymbol,
33133
- isStatic: false
33134
- } : null;
33135
- }
33136
- if (!isNodeOfType(methodNode, "MethodDefinition") || methodNode.value !== functionNode || !isNodeOfType(methodNode.parent, "ClassBody")) return null;
33137
- const classNode = methodNode.parent.parent;
33138
- if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression")) return null;
33139
- let bindingIdentifier = isNodeOfType(classNode.id, "Identifier") ? classNode.id : null;
33140
- if (!bindingIdentifier && isNodeOfType(classNode.parent, "VariableDeclarator")) bindingIdentifier = isNodeOfType(classNode.parent.id, "Identifier") ? classNode.parent.id : null;
33141
- if (!bindingIdentifier) return null;
33142
- const ownerSymbol = scopes.symbolFor(bindingIdentifier);
33143
- const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
33144
- return ownerSymbol && methodName ? {
33145
- methodName,
33146
- ownerKind: "class",
33147
- ownerSymbol,
33148
- isStatic: Boolean(methodNode.static)
33149
- } : null;
33150
- };
33151
- const doesSymbolResolveToOwner = (symbol, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33152
- if (!symbol) return false;
33153
- if (symbol.declarationNode === ownerSymbol.declarationNode) return true;
33154
- if (symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
33155
- visitedSymbolIds.add(symbol.id);
33156
- const initializer = stripParenExpression(symbol.initializer);
33157
- return isNodeOfType(initializer, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(initializer), ownerSymbol, scopes, visitedSymbolIds);
33158
- };
33159
- const isClassInstanceExpression = (expression, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33160
- const candidate = stripParenExpression(expression);
33161
- if (isNodeOfType(candidate, "NewExpression") && isNodeOfType(candidate.callee, "Identifier")) return doesSymbolResolveToOwner(scopes.symbolFor(candidate.callee), ownerSymbol, scopes);
33162
- if (!isNodeOfType(candidate, "Identifier")) return false;
33163
- const symbol = scopes.symbolFor(candidate);
33164
- if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
33165
- visitedSymbolIds.add(symbol.id);
33166
- return isClassInstanceExpression(symbol.initializer, ownerSymbol, scopes, visitedSymbolIds);
33167
- };
33168
- const getMethodCalls = (functionNode, scopes) => {
33169
- const owner = getMethodOwner(functionNode, scopes);
33170
- if (!owner) return [];
33171
- const calls = [];
33172
- walkAst(scopes.rootScope.node, (child) => {
33173
- if (!isNodeOfType(child, "MemberExpression")) return;
33174
- if (getStaticPropertyName(child) !== owner.methodName) return;
33175
- const receiver = stripParenExpression(child.object);
33176
- let doesReceiverMatch = isNodeOfType(receiver, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(receiver), owner.ownerSymbol, scopes);
33177
- if (owner.ownerKind === "class" && !owner.isStatic) doesReceiverMatch = isClassInstanceExpression(receiver, owner.ownerSymbol, scopes);
33178
- if (!doesReceiverMatch) return;
33179
- const call = getDirectCallForExpression(child);
33180
- if (call) calls.push(call);
33181
- });
33182
- return calls;
33183
- };
33184
- const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
33185
- if (visitedFunctionNodes.has(functionNode)) return false;
33186
- visitedFunctionNodes.add(functionNode);
33187
- const usageFunction = findEnclosingFunction(usageNode);
33188
- const immediateCall = getDirectCallForExpression(functionNode);
33189
- if (immediateCall) {
33190
- const immediateCallFunction = findEnclosingFunction(immediateCall);
33191
- if (immediateCallFunction === usageFunction) {
33192
- const immediateCallStart = getRangeStart(immediateCall);
33193
- return immediateCallStart === null || immediateCallStart < usageBoundary;
33194
- }
33195
- if (!immediateCallFunction) return usageFunction !== null;
33196
- return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
33197
- }
33198
- for (const methodCall of getMethodCalls(functionNode, scopes)) {
33199
- const methodCallFunction = findEnclosingFunction(methodCall);
33200
- if (methodCallFunction === usageFunction) {
33201
- const methodCallStart = getRangeStart(methodCall);
33202
- if (methodCallStart === null || methodCallStart < usageBoundary) return true;
33203
- continue;
33204
- }
33205
- if (!methodCallFunction) {
33206
- if (usageFunction) return true;
33207
- continue;
33208
- }
33209
- if (isFunctionInvokedBeforeUsage(methodCallFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes))) return true;
33210
- }
33211
- const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
33212
- if (!bindingIdentifier) return false;
33213
- const symbol = scopes.symbolFor(bindingIdentifier);
33214
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
33215
- visitedSymbolIds.add(symbol.id);
33216
- let wasInvokedBeforeUsage = false;
33217
- walkAst(scopes.rootScope.node, (child) => {
33218
- if (wasInvokedBeforeUsage || !isNodeOfType(child, "Identifier")) return;
33219
- if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
33220
- const call = getDirectCallForExpression(child);
33221
- if (!call) return false;
33222
- const callFunction = findEnclosingFunction(call);
33223
- if (callFunction === usageFunction) {
33224
- const callStart = getRangeStart(call);
33225
- wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
33226
- return;
33227
- }
33228
- if (!callFunction) {
33229
- wasInvokedBeforeUsage = usageFunction !== null;
33230
- return;
33231
- }
33232
- wasInvokedBeforeUsage = isFunctionInvokedBeforeUsage(callFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes));
33233
- });
33234
- return wasInvokedBeforeUsage;
33235
- };
33236
- const getMutationUsageBoundary = (usageNode, readNode) => {
33237
- const readStart = getRangeStart(readNode);
33238
- let readExpression = readNode;
33239
- let readParent = readExpression.parent;
33240
- while (readParent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(readParent.type) && "expression" in readParent && readParent.expression === readExpression) {
33241
- readExpression = readParent;
33242
- readParent = readExpression.parent;
33243
- }
33244
- if ((isNodeOfType(usageNode, "CallExpression") || isNodeOfType(usageNode, "NewExpression")) && usageNode.arguments?.some((argument) => argument === readExpression)) return usageNode.range?.[1] ?? null;
33245
- if (isAstDescendant(readNode, usageNode)) return readStart;
33246
- return getRangeStart(usageNode);
33247
- };
33248
- const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutationSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
33249
- if (visitedMutationSymbolIds.has(symbol.id)) return false;
33250
- visitedMutationSymbolIds.add(symbol.id);
33251
- const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
33252
- if (typeof usageBoundary !== "number") return true;
33253
- const usageFunction = findEnclosingFunction(usageNode);
33254
- return symbol.references.some((reference) => {
33255
- const referenceStart = getRangeStart(reference.identifier);
33256
- const simpleAlias = getSimpleAlias(reference.identifier, scopes);
33257
- if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
33258
- if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
33259
- if (referenceStart === null) return true;
33260
- const mutationFunction = findEnclosingFunction(reference.identifier);
33261
- if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
33262
- if (!mutationFunction) return usageFunction !== null;
33263
- if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
33264
- return isFunctionInvokedBeforeUsage(mutationFunction, usageNode, usageBoundary, scopes, /* @__PURE__ */ new Set());
33265
- });
33266
- };
33267
- const getObjectPropertyProof = (objectExpression, propertyName, scopes, usageNode, visitedSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
33268
- if (!objectExpression) return ABSENT_PROPERTY_PROOF;
32649
+ const objectLiteralHasProperty = (objectExpression, propertyName) => {
32650
+ if (!objectExpression) return false;
33269
32651
  const unwrapped = stripParenExpression(objectExpression);
33270
- if (isNodeOfType(unwrapped, "Literal") || isStaticUndefined(unwrapped, scopes)) return ABSENT_PROPERTY_PROOF;
33271
- if (isNodeOfType(unwrapped, "Identifier")) {
33272
- const symbol = scopes.symbolFor(unwrapped);
33273
- const nextReadNodesBySymbolId = new Map(readNodesBySymbolId);
33274
- if (symbol) nextReadNodesBySymbolId.set(symbol.id, unwrapped);
33275
- const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, unwrapped) : inheritedUsageBoundary;
33276
- if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || visitedSymbolIds.has(symbol.id) || wasMutatedBeforeUsage(symbol, usageNode, unwrapped, scopes, /* @__PURE__ */ new Set(), usageBoundary, nextReadNodesBySymbolId)) return UNKNOWN_PROPERTY_PROOF;
33277
- visitedSymbolIds.add(symbol.id);
33278
- return getObjectPropertyProof(symbol.initializer, propertyName, scopes, usageNode, visitedSymbolIds, usageBoundary, nextReadNodesBySymbolId);
33279
- }
33280
- if (isNodeOfType(unwrapped, "ConditionalExpression")) {
33281
- const consequent = getObjectPropertyProof(unwrapped.consequent, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
33282
- const alternate = getObjectPropertyProof(unwrapped.alternate, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
33283
- return consequent.status === alternate.status ? consequent : UNKNOWN_PROPERTY_PROOF;
33284
- }
33285
- if (isNodeOfType(unwrapped, "CallExpression") && isNodeOfType(unwrapped.callee, "MemberExpression") && !unwrapped.callee.computed && isNodeOfType(unwrapped.callee.object, "Identifier") && unwrapped.callee.object.name === "Object" && scopes.isGlobalReference(unwrapped.callee.object) && isNodeOfType(unwrapped.callee.property, "Identifier") && unwrapped.callee.property.name === "freeze") return getObjectPropertyProof(unwrapped.arguments?.[0], propertyName, scopes, usageNode, visitedSymbolIds, inheritedUsageBoundary, readNodesBySymbolId);
33286
- if (!isNodeOfType(unwrapped, "ObjectExpression")) return UNKNOWN_PROPERTY_PROOF;
33287
- const properties = unwrapped.properties ?? [];
33288
- for (let propertyIndex = properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
33289
- const property = properties[propertyIndex];
33290
- if (!property) continue;
33291
- if (isNodeOfType(property, "SpreadElement")) {
33292
- const spreadProof = getObjectPropertyProof(property.argument, propertyName, scopes, unwrapped, new Set(visitedSymbolIds), void 0);
33293
- if (spreadProof.status !== "absent") return spreadProof;
33294
- continue;
33295
- }
33296
- if (!isNodeOfType(property, "Property") || getStaticPropertyKeyName(property) !== propertyName) continue;
33297
- return isStaticUndefined(property.value, scopes) ? UNDEFINED_PROPERTY_PROOF : PRESENT_PROPERTY_PROOF;
32652
+ if (!isNodeOfType(unwrapped, "ObjectExpression")) return false;
32653
+ for (const property of unwrapped.properties ?? []) {
32654
+ if (!isNodeOfType(property, "Property")) continue;
32655
+ if (property.computed) continue;
32656
+ if (isNodeOfType(property.key, "Identifier") && property.key.name === propertyName) return true;
32657
+ if (isNodeOfType(property.key, "Literal") && property.key.value === propertyName) return true;
33298
32658
  }
33299
- return ABSENT_PROPERTY_PROOF;
32659
+ return false;
33300
32660
  };
33301
- const objectHasExplicitProperty = (objectExpression, propertyName, scopes, usageNode) => getObjectPropertyProof(objectExpression, propertyName, scopes, usageNode).status === "present";
33302
- const hasExplicitLocaleArgument = (argument, scopes) => {
32661
+ const hasExplicitLocaleArgument = (argument) => {
33303
32662
  if (!argument) return false;
33304
32663
  const unwrapped = stripParenExpression(argument);
33305
- if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined" && scopes.isGlobalReference(unwrapped) || isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "void") return false;
32664
+ if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined") return false;
33306
32665
  return true;
33307
32666
  };
33308
- const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate, scopes) => {
32667
+ const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate) => {
33309
32668
  const localeArgument = call.arguments?.[0];
33310
- if (!hasExplicitLocaleArgument(localeArgument, scopes)) return false;
32669
+ if (!hasExplicitLocaleArgument(localeArgument)) return false;
33311
32670
  const optionsArgument = call.arguments?.[1];
33312
- if (objectHasExplicitProperty(optionsArgument, "timeZone", scopes, call)) return true;
32671
+ if (objectLiteralHasProperty(optionsArgument, "timeZone")) return true;
33313
32672
  return !DATE_ONLY_LOCALE_METHOD_NAMES.has(methodName) && !receiverIsProvablyDate;
33314
32673
  };
33315
- const matchLocaleMethodCall = (call, scopes) => {
32674
+ const matchLocaleMethodCall = (call) => {
33316
32675
  const callee = call.callee;
33317
32676
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
33318
32677
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -33320,7 +32679,7 @@ const matchLocaleMethodCall = (call, scopes) => {
33320
32679
  if (!LOCALE_FORMAT_METHOD_NAMES.has(methodName)) return null;
33321
32680
  const receiverIsProvablyDate = isProvableDateExpression(callee.object);
33322
32681
  if (methodName === "toLocaleString" && !receiverIsProvablyDate && !receiverNameLooksDateFlavored(callee.object)) return null;
33323
- if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate, scopes)) return null;
32682
+ if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate)) return null;
33324
32683
  return {
33325
32684
  node: call,
33326
32685
  display: `${methodName}()`
@@ -33336,13 +32695,13 @@ const getIntlFormatterName = (expression) => {
33336
32695
  if (!isNodeOfType(callee.property, "Identifier")) return null;
33337
32696
  return INTL_FORMATTER_NAMES.has(callee.property.name) ? callee.property.name : null;
33338
32697
  };
33339
- const isDeterministicIntlConstruction = (construction, formatterName, scopes) => {
32698
+ const isDeterministicIntlConstruction = (construction, formatterName) => {
33340
32699
  if (!isNodeOfType(construction, "CallExpression") && !isNodeOfType(construction, "NewExpression")) return false;
33341
- if (!hasExplicitLocaleArgument(construction.arguments?.[0], scopes)) return false;
32700
+ if (!hasExplicitLocaleArgument(construction.arguments?.[0])) return false;
33342
32701
  if (formatterName !== "DateTimeFormat") return true;
33343
- return objectHasExplicitProperty(construction.arguments?.[1], "timeZone", scopes, construction);
32702
+ return objectLiteralHasProperty(construction.arguments?.[1], "timeZone");
33344
32703
  };
33345
- const matchIntlFormatCall = (call, scopes) => {
32704
+ const matchIntlFormatCall = (call) => {
33346
32705
  const callee = call.callee;
33347
32706
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
33348
32707
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -33355,7 +32714,7 @@ const matchIntlFormatCall = (call, scopes) => {
33355
32714
  if (!construction) return null;
33356
32715
  const formatterName = getIntlFormatterName(construction);
33357
32716
  if (!formatterName) return null;
33358
- if (isDeterministicIntlConstruction(construction, formatterName, scopes)) return null;
32717
+ if (isDeterministicIntlConstruction(construction, formatterName)) return null;
33359
32718
  return {
33360
32719
  node: call,
33361
32720
  display: `Intl.${formatterName}().${callee.property.name}()`
@@ -33424,7 +32783,7 @@ const noLocaleFormatInRender = defineRule({
33424
32783
  fileIsEmailTemplate = hasEmailTemplateImport(node);
33425
32784
  },
33426
32785
  CallExpression(node) {
33427
- const match = matchLocaleMethodCall(node, context.scopes) ?? matchIntlFormatCall(node, context.scopes) ?? matchDateDefaultStringification(node);
32786
+ const match = matchLocaleMethodCall(node) ?? matchIntlFormatCall(node) ?? matchDateDefaultStringification(node);
33428
32787
  if (match) reportIfRenderPhase(match);
33429
32788
  },
33430
32789
  TemplateLiteral(node) {
@@ -33444,7 +32803,7 @@ const noLocaleFormatInRender = defineRule({
33444
32803
  walkAst(helperNode.body ?? helperNode, (child) => {
33445
32804
  if (isFunctionLike$1(child)) return false;
33446
32805
  if (!isNodeOfType(child, "CallExpression")) return;
33447
- const match = matchLocaleMethodCall(child, context.scopes) ?? matchIntlFormatCall(child, context.scopes);
32806
+ const match = matchLocaleMethodCall(child) ?? matchIntlFormatCall(child);
33448
32807
  if (!match || reportedNodes.has(match.node)) return;
33449
32808
  if (fileIsEmailTemplate) return;
33450
32809
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
@@ -33611,13 +32970,11 @@ const noManyBooleanProps = defineRule({
33611
32970
  };
33612
32971
  const checkComponent = (functionNode, param, body, componentName, reportNode) => {
33613
32972
  if (!param) return;
33614
- const propsBinding = resolveFirstArgumentBinding(param);
33615
- if (!propsBinding) return;
33616
32973
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
33617
- if (isNodeOfType(propsBinding, "ObjectPattern")) {
32974
+ if (isNodeOfType(param, "ObjectPattern")) {
33618
32975
  const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
33619
32976
  const booleanLikePropNames = [];
33620
- for (const property of propsBinding.properties ?? []) {
32977
+ for (const property of param.properties ?? []) {
33621
32978
  if (!isNodeOfType(property, "Property")) continue;
33622
32979
  const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
33623
32980
  if (!keyName) continue;
@@ -33628,7 +32985,7 @@ const noManyBooleanProps = defineRule({
33628
32985
  reportIfMany(booleanLikePropNames, componentName, reportNode);
33629
32986
  return;
33630
32987
  }
33631
- if (isNodeOfType(propsBinding, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, propsBinding.name)], componentName, reportNode);
32988
+ if (isNodeOfType(param, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, param.name)], componentName, reportNode);
33632
32989
  };
33633
32990
  return {
33634
32991
  FunctionDeclaration(node) {
@@ -33750,7 +33107,7 @@ const noMirrorPropEffect = defineRule({
33750
33107
  if (mirrorBindings.length === 0) return;
33751
33108
  for (const statement of componentBody.body ?? []) {
33752
33109
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
33753
- const effectCall = unwrapDiscardedExpression(statement);
33110
+ const effectCall = statement.expression;
33754
33111
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
33755
33112
  if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
33756
33113
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
@@ -33764,7 +33121,7 @@ const noMirrorPropEffect = defineRule({
33764
33121
  const bodyStatements = getCallbackStatements(callback);
33765
33122
  if (bodyStatements.length !== 1) continue;
33766
33123
  const onlyStatement = bodyStatements[0];
33767
- const expression = unwrapDiscardedExpression(onlyStatement);
33124
+ const expression = isNodeOfType(onlyStatement, "ExpressionStatement") ? onlyStatement.expression : onlyStatement;
33768
33125
  if (!isNodeOfType(expression, "CallExpression")) continue;
33769
33126
  if (!isNodeOfType(expression.callee, "Identifier")) continue;
33770
33127
  if (!isSetterIdentifier(expression.callee.name)) continue;
@@ -36380,7 +35737,7 @@ const noPreventDefault = defineRule({
36380
35737
  const isServerActionsFramework = hasCapability(context.settings, "server-actions");
36381
35738
  const formMessage = isServerActionsFramework ? FORM_MESSAGE_SERVER_CAPABLE : FORM_MESSAGE_GENERIC;
36382
35739
  return { JSXOpeningElement(node) {
36383
- const elementName = resolveJsxElementType(node);
35740
+ const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
36384
35741
  if (!elementName) return;
36385
35742
  const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
36386
35743
  if (!targetEventProps) return;
@@ -37270,85 +36627,28 @@ const noRedundantShouldComponentUpdate = defineRule({
37270
36627
  });
37271
36628
  //#endregion
37272
36629
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
37273
- const REPEATED_ANCESTOR_TYPES = new Set([
37274
- "DoWhileStatement",
37275
- "ForInStatement",
37276
- "ForOfStatement",
37277
- "ForStatement",
37278
- "WhileStatement"
37279
- ]);
36630
+ const resolveReactRefSymbol = (memberExpression, scopes) => {
36631
+ const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
36632
+ if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.computed || !isNodeOfType(memberExpression.property, "Identifier") || memberExpression.property.name !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
36633
+ const symbol = resolveConstIdentifierAlias(receiver, scopes);
36634
+ if (!symbol?.initializer) return null;
36635
+ const initializer = stripParenExpression(symbol.initializer);
36636
+ if (!isNodeOfType(initializer, "CallExpression")) return null;
36637
+ return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
36638
+ };
37280
36639
  const isSameRefCurrentMember = (node, refSymbol, scopes) => {
37281
- if (!isNodeOfType(node, "MemberExpression") || getStaticPropertyName(node) !== "current") return false;
36640
+ if (!isNodeOfType(node, "MemberExpression") || node.computed || !isNodeOfType(node.property, "Identifier") || node.property.name !== "current") return false;
37282
36641
  const receiver = stripParenExpression(node.object);
37283
36642
  return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
37284
36643
  };
37285
- const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
37286
- if (isSameRefCurrentMember(node, refSymbol, scopes)) return true;
37287
- if (!isNodeOfType(node, "Identifier")) return false;
37288
- const aliasSymbol = scopes.symbolFor(node);
37289
- return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
37290
- };
37291
- const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
37292
- const hasRepeatedExecutionAncestor = (node, stop) => {
37293
- let ancestor = node.parent;
37294
- while (ancestor && ancestor !== stop) {
37295
- if (isFunctionLike$1(ancestor) || REPEATED_ANCESTOR_TYPES.has(ancestor.type)) return true;
37296
- ancestor = ancestor.parent;
37297
- }
37298
- return ancestor !== stop;
37299
- };
37300
- const getBranchConstraints = (node, branchRoot) => {
37301
- const constraints = /* @__PURE__ */ new Map();
37302
- let descendant = node;
37303
- let ancestor = descendant.parent;
37304
- while (ancestor && descendant !== branchRoot) {
37305
- if (isNodeOfType(ancestor, "IfStatement")) {
37306
- if (ancestor.consequent === descendant) constraints.set(ancestor, true);
37307
- if (ancestor.alternate === descendant) constraints.set(ancestor, false);
37308
- }
37309
- descendant = ancestor;
37310
- ancestor = ancestor.parent;
37311
- }
37312
- return constraints;
37313
- };
37314
- const canExecuteTogether = (firstConstraints, secondConstraints) => {
37315
- for (const [statement, branch] of firstConstraints) {
37316
- const otherBranch = secondConstraints.get(statement);
37317
- if (otherBranch !== void 0 && otherBranch !== branch) return false;
37318
- }
37319
- return true;
37320
- };
37321
- const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
37322
- const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
37323
- const assignmentStart = getRangeStart(assignmentExpression);
37324
- let hasCoExecutableWrite = false;
37325
- walkAst(branchRoot, (child) => {
37326
- if (hasCoExecutableWrite) return false;
37327
- const childStart = getRangeStart(child);
37328
- if (child === assignmentExpression || !isNodeOfType(child, "AssignmentExpression") || assignmentStart === null || childStart === null || childStart >= assignmentStart || resolveReactRefSymbol(child.left, scopes)?.id !== refSymbol.id || hasRepeatedExecutionAncestor(child, branchRoot)) return;
37329
- if (canExecuteTogether(assignmentConstraints, getBranchConstraints(child, branchRoot))) {
37330
- hasCoExecutableWrite = true;
37331
- return false;
37332
- }
37333
- });
37334
- return !hasCoExecutableWrite;
37335
- };
37336
36644
  const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
37337
- if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
37338
- if (assignmentExpression.operator !== "=") return false;
36645
+ if (assignmentExpression.operator !== "=" || !isNodeOfType(assignmentExpression.right, "NewExpression")) return false;
37339
36646
  let descendant = assignmentExpression;
37340
36647
  let ancestor = descendant.parent;
37341
36648
  while (ancestor) {
37342
- if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") && [
37343
- "===",
37344
- "==",
37345
- "!==",
37346
- "!="
37347
- ].includes(ancestor.test.operator)) {
36649
+ if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && isNodeOfType(ancestor.test, "BinaryExpression") && (ancestor.test.operator === "===" || ancestor.test.operator === "==")) {
37348
36650
  const { left, right } = ancestor.test;
37349
- const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
37350
- const guardedBranch = ancestor.test.operator === "===" || ancestor.test.operator === "==" ? ancestor.consequent : ancestor.alternate;
37351
- if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
36651
+ if (isSameRefCurrentMember(left, refSymbol, scopes) && isNodeOfType(right, "Literal") && right.value === null || isSameRefCurrentMember(right, refSymbol, scopes) && isNodeOfType(left, "Literal") && left.value === null) return true;
37352
36652
  }
37353
36653
  descendant = ancestor;
37354
36654
  ancestor = descendant.parent;
@@ -37922,7 +37222,7 @@ const isNonSettlingSetterArgument = (setterCall, stateName) => {
37922
37222
  return expressionReadsStateValue(argument, stateName);
37923
37223
  };
37924
37224
  const getUnconditionalSetterCall = (statement, setterNames) => {
37925
- const expression = unwrapDiscardedExpression(statement);
37225
+ const expression = stripParenExpression(isNodeOfType(statement, "ExpressionStatement") ? statement.expression : statement);
37926
37226
  if (!isNodeOfType(expression, "CallExpression")) return null;
37927
37227
  if (!isNodeOfType(expression.callee, "Identifier")) return null;
37928
37228
  if (!setterNames.has(expression.callee.name)) return null;
@@ -38271,7 +37571,7 @@ const noSelfUpdatingEffect = defineRule({
38271
37571
  const setterNames = new Set(setterNameToStateName.keys());
38272
37572
  for (const statement of functionBody.body ?? []) {
38273
37573
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
38274
- const effectCall = unwrapDiscardedExpression(statement);
37574
+ const effectCall = statement.expression;
38275
37575
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
38276
37576
  if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
38277
37577
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
@@ -38901,10 +38201,9 @@ const noStringFalseOnBooleanAttribute = defineRule({
38901
38201
  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.",
38902
38202
  create: (context) => ({ JSXOpeningElement(node) {
38903
38203
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
38904
- const elementName = resolveJsxElementType(node);
38905
- const firstCharacter = elementName.charCodeAt(0);
38204
+ const firstCharacter = node.name.name.charCodeAt(0);
38906
38205
  if (firstCharacter < 97 || firstCharacter > 122) return;
38907
- if (elementName.includes("-")) return;
38206
+ if (node.name.name.includes("-")) return;
38908
38207
  for (const attribute of node.attributes) {
38909
38208
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
38910
38209
  if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
@@ -39292,7 +38591,8 @@ const noUncontrolledInput = defineRule({
39292
38591
  const undefinedInitialStateNames = isNodeOfType(componentBody, "BlockStatement") ? collectUndefinedInitialStateNames(componentBody) : /* @__PURE__ */ new Set();
39293
38592
  walkAst(componentBody, (child) => {
39294
38593
  if (!isNodeOfType(child, "JSXOpeningElement")) return;
39295
- const tagName = resolveJsxElementType(child);
38594
+ if (!isNodeOfType(child.name, "JSXIdentifier")) return;
38595
+ const tagName = child.name.name;
39296
38596
  if (!UNCONTROLLED_INPUT_TAGS.has(tagName)) return;
39297
38597
  const attributes = child.attributes ?? [];
39298
38598
  if (hasJsxSpreadAttribute(attributes)) return;
@@ -39353,7 +38653,7 @@ const noUndeferredThirdParty = defineRule({
39353
38653
  severity: "warn",
39354
38654
  recommendation: "Use `next/script` with `strategy=\"lazyOnload\"`, or add the `defer` attribute.",
39355
38655
  create: (context) => ({ JSXOpeningElement(node) {
39356
- if (resolveJsxElementType(node) !== "script") return;
38656
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "script") return;
39357
38657
  const attributes = node.attributes ?? [];
39358
38658
  const srcAttribute = findJsxAttribute(attributes, "src");
39359
38659
  if (!srcAttribute) return;
@@ -40568,7 +39868,7 @@ const noUnknownProperty = defineRule({
40568
39868
  }
40569
39869
  if (fileIsNonReactJsx) return;
40570
39870
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
40571
- const elementType = resolveJsxElementType(node);
39871
+ const elementType = node.name.name;
40572
39872
  const firstCharacter = elementType.charCodeAt(0);
40573
39873
  if (!(firstCharacter >= 97 && firstCharacter <= 122) || elementType === "fbt" || elementType === "fbs") return;
40574
39874
  let isValidHtmlTag = isKnownDomTag(elementType);
@@ -40746,21 +40046,20 @@ const expressionContainsJsxOrCreateElement = (root) => {
40746
40046
  });
40747
40047
  return found;
40748
40048
  };
40749
- const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
40750
40049
  const isReactClassComponent = (classNode) => {
40751
40050
  if (isEs6Component(classNode)) return true;
40752
40051
  return expressionContainsJsxOrCreateElement(classNode);
40753
40052
  };
40754
- const findEnclosingComponent = (node, scopes) => {
40053
+ const findEnclosingComponent = (node) => {
40755
40054
  let walker = node.parent;
40756
40055
  while (walker) {
40757
40056
  if (isFunctionLike$1(walker)) {
40758
40057
  const componentName = inferFunctionLikeName(walker);
40759
- if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
40058
+ if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
40760
40059
  component: walker,
40761
40060
  name: componentName
40762
40061
  };
40763
- if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40062
+ if (!componentName && expressionContainsJsxOrCreateElement(walker) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40764
40063
  component: walker,
40765
40064
  name: null
40766
40065
  };
@@ -40999,7 +40298,7 @@ const noUnstableNestedComponents = defineRule({
40999
40298
  if (renderPropRegex.test(propInfo.propName)) return;
41000
40299
  if (settings.allowAsProps) return;
41001
40300
  }
41002
- const enclosing = findEnclosingComponent(candidateNode, context.scopes);
40301
+ const enclosing = findEnclosingComponent(candidateNode);
41003
40302
  if (!enclosing) return;
41004
40303
  const gatedName = propInfo ? null : requiredInstantiationName;
41005
40304
  queuedReports.push({
@@ -41010,7 +40309,7 @@ const noUnstableNestedComponents = defineRule({
41010
40309
  });
41011
40310
  };
41012
40311
  const checkFunctionLike = (node) => {
41013
- if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
40312
+ if (!expressionContainsJsxOrCreateElement(node)) return;
41014
40313
  const inferredName = inferFunctionLikeName(node);
41015
40314
  const propInfo = isComponentDeclaredInProp(node);
41016
40315
  const isObjectCallback = isObjectCallbackCandidate(node);
@@ -42270,7 +41569,7 @@ const preactPreferOndblclick = defineRule({
42270
41569
  recommendation: "Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",
42271
41570
  create: (context) => ({ JSXOpeningElement(node) {
42272
41571
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
42273
- const tagName = resolveJsxElementType(node);
41572
+ const tagName = node.name.name;
42274
41573
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
42275
41574
  const onDoubleClickAttribute = findJsxAttribute(node.attributes, "onDoubleClick");
42276
41575
  if (!onDoubleClickAttribute) return;
@@ -42290,7 +41589,7 @@ const COMPAT_EXEMPT_INPUT_TYPES = new Set([
42290
41589
  ]);
42291
41590
  const isTextLikeInput = (openingElement) => {
42292
41591
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
42293
- const tagName = resolveJsxElementType(openingElement);
41592
+ const tagName = openingElement.name.name;
42294
41593
  if (tagName === "textarea") return true;
42295
41594
  if (tagName !== "input") return false;
42296
41595
  const typeAttribute = findJsxAttribute(openingElement.attributes, "type");
@@ -42447,9 +41746,8 @@ const CROSS_CUTTING_STATE_BOOLEAN_NAMES = new Set([
42447
41746
  ]);
42448
41747
  const collectBooleanPropBindings = (param) => {
42449
41748
  const bindings = /* @__PURE__ */ new Set();
42450
- const propsBinding = resolveFirstArgumentBinding(param);
42451
- if (!isNodeOfType(propsBinding, "ObjectPattern")) return bindings;
42452
- for (const property of propsBinding.properties ?? []) {
41749
+ if (!param || !isNodeOfType(param, "ObjectPattern")) return bindings;
41750
+ for (const property of param.properties ?? []) {
42453
41751
  if (!isNodeOfType(property, "Property")) continue;
42454
41752
  if (property.computed) continue;
42455
41753
  if (!isNodeOfType(property.key, "Identifier")) continue;
@@ -42710,7 +42008,7 @@ const preferHtmlDialog = defineRule({
42710
42008
  },
42711
42009
  JSXOpeningElement(node) {
42712
42010
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
42713
- const tagName = resolveJsxElementType(node);
42011
+ const tagName = node.name.name;
42714
42012
  if (tagName === "dialog") return;
42715
42013
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
42716
42014
  if (!HTML_TAGS.has(tagName)) return;
@@ -44257,13 +43555,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
44257
43555
  }
44258
43556
  return null;
44259
43557
  };
44260
- const resolveHookNameFromInitializer = (initializer, scopes) => {
43558
+ const resolveHookNameFromInitializer = (initializer) => {
44261
43559
  if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
44262
43560
  if (!isNodeOfType(initializer, "Identifier")) return null;
44263
- const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
44264
- if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
44265
- if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
44266
- return resolveTanstackQueryHookName(resolvedSymbol.initializer);
43561
+ const binding = findVariableInitializer(initializer, initializer.name);
43562
+ if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
43563
+ if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
43564
+ return resolveTanstackQueryHookName(binding.initializer);
44267
43565
  };
44268
43566
  const queryNoRestDestructuring = defineRule({
44269
43567
  id: "query-no-rest-destructuring",
@@ -44276,7 +43574,7 @@ const queryNoRestDestructuring = defineRule({
44276
43574
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
44277
43575
  if (!node.init) return;
44278
43576
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
44279
- const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
43577
+ const hookName = resolveHookNameFromInitializer(node.init);
44280
43578
  if (!hookName) return;
44281
43579
  context.report({
44282
43580
  node: node.id,
@@ -44747,7 +44045,7 @@ const renderingAnimateSvgWrapper = defineRule({
44747
44045
  severity: "warn",
44748
44046
  recommendation: "Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.",
44749
44047
  create: (context) => ({ JSXOpeningElement(node) {
44750
- if (resolveJsxElementType(node) !== "svg") return;
44048
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "svg") return;
44751
44049
  if (node.attributes?.some((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && MOTION_ANIMATE_PROPS.has(attribute.name.name))) context.report({
44752
44050
  node,
44753
44051
  message: "This is slow to render because you animate <svg> directly, so wrap it in a <div> or <motion.div> & animate that instead"
@@ -45631,7 +44929,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
45631
44929
  "useParams",
45632
44930
  "usePathname"
45633
44931
  ]);
45634
- const findHookCallBindings = (componentBody, scopes) => {
44932
+ const findHookCallBindings = (componentBody) => {
45635
44933
  const bindings = [];
45636
44934
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
45637
44935
  for (const statement of componentBody.body ?? []) {
@@ -45642,10 +44940,8 @@ const findHookCallBindings = (componentBody, scopes) => {
45642
44940
  const callee = declarator.init.callee;
45643
44941
  if (!isNodeOfType(callee, "Identifier")) continue;
45644
44942
  if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
45645
- const valueSymbol = scopes.symbolFor(declarator.id);
45646
- if (!valueSymbol) continue;
45647
44943
  bindings.push({
45648
- valueSymbol,
44944
+ valueName: declarator.id.name,
45649
44945
  hookName: callee.name,
45650
44946
  declarator
45651
44947
  });
@@ -45653,49 +44949,6 @@ const findHookCallBindings = (componentBody, scopes) => {
45653
44949
  }
45654
44950
  return bindings;
45655
44951
  };
45656
- const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
45657
- const symbols = [sourceSymbol];
45658
- const symbolIds = new Set([sourceSymbol.id]);
45659
- const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
45660
- if (!isNodeOfType(componentBody, "BlockStatement")) return {
45661
- symbols,
45662
- aliasSourceIdentifiers
45663
- };
45664
- let didFindAlias = true;
45665
- while (didFindAlias) {
45666
- didFindAlias = false;
45667
- for (const statement of componentBody.body ?? []) {
45668
- if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
45669
- for (const declarator of statement.declarations ?? []) {
45670
- let aliasIdentifier = null;
45671
- let sourceIdentifier = null;
45672
- const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
45673
- const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
45674
- const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
45675
- const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
45676
- if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
45677
- aliasIdentifier = declarator.id;
45678
- sourceIdentifier = initializer;
45679
- } else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
45680
- aliasIdentifier = arrayBinding;
45681
- sourceIdentifier = arrayValue;
45682
- }
45683
- if (!aliasIdentifier || !sourceIdentifier) continue;
45684
- const referencedSymbol = scopes.symbolFor(sourceIdentifier);
45685
- const aliasSymbol = scopes.symbolFor(aliasIdentifier);
45686
- if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
45687
- symbolIds.add(aliasSymbol.id);
45688
- symbols.push(aliasSymbol);
45689
- aliasSourceIdentifiers.add(sourceIdentifier);
45690
- didFindAlias = true;
45691
- }
45692
- }
45693
- }
45694
- return {
45695
- symbols,
45696
- aliasSourceIdentifiers
45697
- };
45698
- };
45699
44952
  const rerenderDeferReadsHook = defineRule({
45700
44953
  id: "rerender-defer-reads-hook",
45701
44954
  title: "URL hook value only read in handlers",
@@ -45706,13 +44959,15 @@ const rerenderDeferReadsHook = defineRule({
45706
44959
  create: (context) => {
45707
44960
  const checkComponent = (componentBody) => {
45708
44961
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
45709
- const bindings = findHookCallBindings(componentBody, context.scopes);
44962
+ const bindings = findHookCallBindings(componentBody);
45710
44963
  if (bindings.length === 0) return;
45711
44964
  const handlerBindingNames = collectHandlerBindingNames(componentBody);
45712
44965
  for (const binding of bindings) {
45713
- const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
45714
44966
  const referenceLocations = [];
45715
- for (const symbol of symbols) for (const reference of symbol.references) if (!aliasSourceIdentifiers.has(reference.identifier)) referenceLocations.push(reference.identifier);
44967
+ walkAst(componentBody, (child) => {
44968
+ if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
44969
+ if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
44970
+ });
45716
44971
  if (referenceLocations.length === 0) continue;
45717
44972
  if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
45718
44973
  context.report({
@@ -46083,10 +45338,14 @@ const rerenderLazyStateInit = defineRule({
46083
45338
  //#endregion
46084
45339
  //#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
46085
45340
  const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
46086
- const callbackReturnsJsx = (callback, scopes) => {
45341
+ const callbackReturnsJsx = (callback) => {
46087
45342
  if (!callback) return false;
46088
45343
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
46089
- return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
45344
+ const body = callback.body;
45345
+ if (isJsxExpression(body)) return true;
45346
+ if (!isNodeOfType(body, "BlockStatement")) return false;
45347
+ for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
45348
+ return false;
46090
45349
  };
46091
45350
  const returnArgumentUsesAnyName = (returnStatement, names) => {
46092
45351
  if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
@@ -46164,7 +45423,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
46164
45423
  if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
46165
45424
  for (const declarator of stmt.declarations ?? []) {
46166
45425
  const init = declarator.init;
46167
- if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
45426
+ if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
46168
45427
  memoNode = declarator;
46169
45428
  callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
46170
45429
  if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
@@ -46230,11 +45489,8 @@ const collectFromObjectPattern = (pattern, bindings) => {
46230
45489
  const collectDefaultedEmptyBindings = (functionNode) => {
46231
45490
  const bindings = /* @__PURE__ */ new Map();
46232
45491
  const params = functionNode.params ?? [];
46233
- for (const [parameterIndex, param] of params.entries()) {
46234
- const parameterBinding = parameterIndex === 0 ? resolveFirstArgumentBinding(param) : param;
46235
- if (parameterBinding) collectFromObjectPattern(parameterBinding, bindings);
46236
- }
46237
- const propsParam = resolveFirstArgumentBinding(params[0]);
45492
+ for (const param of params) collectFromObjectPattern(param, bindings);
45493
+ const propsParam = params[0];
46238
45494
  const body = functionNode.body;
46239
45495
  if (!propsParam || !isNodeOfType(propsParam, "Identifier") || !body) return bindings;
46240
45496
  if (!isNodeOfType(body, "BlockStatement")) return bindings;
@@ -46740,8 +45996,8 @@ const rnAnimationReactionAsDerived = defineRule({
46740
45996
  if (statements.length !== 1) return;
46741
45997
  const onlyStatement = statements[0];
46742
45998
  if (!isNodeOfType(onlyStatement, "ExpressionStatement")) return;
46743
- singleAssignment = unwrapDiscardedExpression(onlyStatement);
46744
- } else if (body) singleAssignment = unwrapDiscardedExpression(body);
45999
+ singleAssignment = onlyStatement.expression;
46000
+ } else if (body) singleAssignment = body;
46745
46001
  if (!singleAssignment) return;
46746
46002
  const isValueAssignment = isNodeOfType(singleAssignment, "AssignmentExpression") && isNodeOfType(singleAssignment.left, "MemberExpression") && isNodeOfType(singleAssignment.left.object, "Identifier") && isNodeOfType(singleAssignment.left.property, "Identifier") && singleAssignment.left.property.name === "value";
46747
46003
  const isSetCall = isNodeOfType(singleAssignment, "CallExpression") && isNodeOfType(singleAssignment.callee, "MemberExpression") && isNodeOfType(singleAssignment.callee.property, "Identifier") && singleAssignment.callee.property.name === "set" && (singleAssignment.arguments?.length ?? 0) === 1;
@@ -53661,8 +52917,7 @@ const serverCacheWithObjectLiteral = defineRule({
53661
52917
  if (!isNodeOfType(node.callee, "Identifier")) return;
53662
52918
  if (!cachedFunctionNames.has(node.callee.name)) return;
53663
52919
  const firstArg = node.arguments?.[0];
53664
- if (!firstArg || isNodeOfType(firstArg, "SpreadElement")) return;
53665
- if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
52920
+ if (!isNodeOfType(firstArg, "ObjectExpression")) return;
53666
52921
  context.report({
53667
52922
  node,
53668
52923
  message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
@@ -54349,6 +53604,10 @@ const isInvalidStyleExpression = (expression) => {
54349
53604
  }
54350
53605
  return false;
54351
53606
  };
53607
+ const getJsxOpeningElementName = (node) => {
53608
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
53609
+ return null;
53610
+ };
54352
53611
  const stylePropObject = defineRule({
54353
53612
  id: "style-prop-object",
54354
53613
  title: "Style prop is not an object",
@@ -54360,8 +53619,7 @@ const stylePropObject = defineRule({
54360
53619
  const allowSet = new Set(allow);
54361
53620
  return {
54362
53621
  JSXOpeningElement(node) {
54363
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
54364
- const elementName = resolveJsxElementType(node);
53622
+ const elementName = getJsxOpeningElementName(node);
54365
53623
  if (elementName && allowSet.has(elementName)) return;
54366
53624
  if (elementName) {
54367
53625
  const firstCharCode = elementName.charCodeAt(0);
@@ -55066,7 +54324,7 @@ const tanstackStartNoAnchorElement = defineRule({
55066
54324
  create: (context) => {
55067
54325
  if (!isInProjectDirectory(context, "routes")) return {};
55068
54326
  return { JSXOpeningElement(node) {
55069
- if (resolveJsxElementType(node) !== "a") return;
54327
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
55070
54328
  const attributes = node.attributes ?? [];
55071
54329
  const hrefAttribute = findJsxAttribute(attributes, "href");
55072
54330
  if (!hrefAttribute?.value) return;
@@ -55687,7 +54945,8 @@ const voidDomElementsNoChildren = defineRule({
55687
54945
  create: (context) => ({
55688
54946
  JSXElement(node) {
55689
54947
  const openingElement = node.openingElement;
55690
- const tagName = resolveJsxElementType(openingElement);
54948
+ if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return;
54949
+ const tagName = openingElement.name.name;
55691
54950
  if (!VOID_DOM_ELEMENTS.has(tagName)) return;
55692
54951
  const hasChildrenContent = node.children.some(isMeaningfulJsxChild);
55693
54952
  const hasChildrenLikeProp = findChildrenLikePropName(openingElement.attributes);