oxlint-plugin-react-doctor 0.7.6-dev.81e6647 → 0.7.6-dev.82e10cd

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 +151 -451
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1518,37 +1518,6 @@ const hasJsxPropIgnoreCase = (attributes, targetProp) => {
1518
1518
  }
1519
1519
  };
1520
1520
  //#endregion
1521
- //#region src/plugin/utils/is-uppercase-name.ts
1522
- const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
1523
- //#endregion
1524
- //#region src/plugin/utils/resolve-jsx-element-type.ts
1525
- const resolveConstantStringBinding = (name) => {
1526
- const binding = findVariableInitializer(name, name.name);
1527
- if (!binding?.initializer) return null;
1528
- const declarator = binding.bindingIdentifier.parent;
1529
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
1530
- if (declarator.id !== binding.bindingIdentifier) return null;
1531
- const declaration = declarator.parent;
1532
- if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
1533
- if (declaration.kind !== "const") return null;
1534
- const initializer = stripParenExpression(binding.initializer);
1535
- return isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
1536
- };
1537
- const flattenJsxName$2 = (name) => {
1538
- if (isNodeOfType(name, "JSXIdentifier")) return name.name;
1539
- if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName$2(name.object)}.${name.property.name}`;
1540
- if (isNodeOfType(name, "JSXNamespacedName")) return `${name.namespace.name}:${name.name.name}`;
1541
- return "";
1542
- };
1543
- const resolveJsxElementType = (openingElement) => {
1544
- const name = openingElement.name;
1545
- if (isNodeOfType(name, "JSXIdentifier")) {
1546
- if (!isUppercaseName(name.name)) return name.name;
1547
- return resolveConstantStringBinding(name) ?? name.name;
1548
- }
1549
- return flattenJsxName$2(name);
1550
- };
1551
- //#endregion
1552
1521
  //#region src/plugin/utils/get-element-type.ts
1553
1522
  const EMPTY_JSX_A11Y_SETTINGS = Object.freeze({});
1554
1523
  const jsxA11ySettingsCache = /* @__PURE__ */ new WeakMap();
@@ -1561,8 +1530,14 @@ const readJsxA11ySettings = (settings) => {
1561
1530
  jsxA11ySettingsCache.set(settings, a11ySettings);
1562
1531
  return a11ySettings;
1563
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
+ };
1564
1539
  const computeElementType = (openingElement, a11ySettings) => {
1565
- const baseName = resolveJsxElementType(openingElement);
1540
+ const baseName = flattenJsxName$2(openingElement.name);
1566
1541
  if (a11ySettings.polymorphicPropName) {
1567
1542
  const polymorphicAttribute = hasJsxPropIgnoreCase(openingElement.attributes, a11ySettings.polymorphicPropName);
1568
1543
  if (polymorphicAttribute) {
@@ -2397,9 +2372,8 @@ const anchorIsValid = defineRule({
2397
2372
  const isTestlikeFile = isTestlikeFilename(context.filename);
2398
2373
  return { JSXOpeningElement(node) {
2399
2374
  if (isTestlikeFile) return;
2400
- const tag = getElementType(node, context.settings);
2401
- if (!fileHasJsxA11ySettings && tag !== "a") return;
2402
- if (tag !== "a") return;
2375
+ if (!fileHasJsxA11ySettings && (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a")) return;
2376
+ if (getElementType(node, context.settings) !== "a") return;
2403
2377
  let hrefAttribute;
2404
2378
  for (const attributeName of settings.hrefAttributeNames) {
2405
2379
  hrefAttribute = hasJsxPropIgnoreCase(node.attributes, attributeName);
@@ -5754,7 +5728,7 @@ const buttonHasType = defineRule({
5754
5728
  return {
5755
5729
  JSXOpeningElement(node) {
5756
5730
  if (isTestlikeFile) return;
5757
- if (resolveJsxElementType(node) !== "button") return;
5731
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "button") return;
5758
5732
  const typeAttr = hasJsxPropIgnoreCase(node.attributes, "type");
5759
5733
  if (!typeAttr) {
5760
5734
  if (hasJsxSpreadAttribute$1(node.attributes)) {
@@ -5944,7 +5918,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5944
5918
  };
5945
5919
  return {
5946
5920
  JSXOpeningElement(node) {
5947
- if (resolveJsxElementType(node) !== "input") return;
5921
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
5948
5922
  reportFromPresence(collectFromJsxAttributes(node.attributes));
5949
5923
  },
5950
5924
  CallExpression(node) {
@@ -6021,7 +5995,7 @@ const isPureEventBlockerHandler = (attribute) => {
6021
5995
  //#endregion
6022
5996
  //#region src/plugin/utils/resolve-const-identifier-alias.ts
6023
5997
  const resolveConstIdentifierAlias = (identifier, scopes) => {
6024
- if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
5998
+ if (!isNodeOfType(identifier, "Identifier")) return null;
6025
5999
  const visitedSymbolIds = /* @__PURE__ */ new Set();
6026
6000
  let symbol = scopes.symbolFor(identifier);
6027
6001
  while (symbol?.kind === "const") {
@@ -6296,131 +6270,6 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
6296
6270
  return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6297
6271
  };
6298
6272
  //#endregion
6299
- //#region src/plugin/utils/collect-safely-validated-local-storage-keys.ts
6300
- const isLocalStorageCall = (node, methodName) => {
6301
- if (!isNodeOfType(node, "CallExpression")) return false;
6302
- const callee = stripParenExpression(node.callee);
6303
- if (!isNodeOfType(callee, "MemberExpression")) return false;
6304
- const receiver = stripParenExpression(callee.object);
6305
- return isNodeOfType(receiver, "Identifier") && receiver.name === "localStorage" && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6306
- };
6307
- const catchReturnsFallback = (tryStatement) => {
6308
- const handler = tryStatement.handler;
6309
- if (!handler) return false;
6310
- let hasReturn = false;
6311
- let hasThrow = false;
6312
- walkAst(handler.body, (child) => {
6313
- if (isFunctionLike$1(child)) return false;
6314
- if (isNodeOfType(child, "ReturnStatement")) hasReturn = true;
6315
- if (isNodeOfType(child, "ThrowStatement")) hasThrow = true;
6316
- });
6317
- return hasReturn && !hasThrow;
6318
- };
6319
- const resolveValidatorFunction = (callee, scopes) => {
6320
- const unwrappedCallee = stripParenExpression(callee);
6321
- if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
6322
- const symbol = scopes.symbolFor(unwrappedCallee);
6323
- if (!symbol) return null;
6324
- const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
6325
- return isFunctionLike$1(candidate) ? candidate : null;
6326
- };
6327
- const validatorChecksPayloadProperties = (validatorFunction, scopes) => {
6328
- if (!isFunctionLike$1(validatorFunction)) return false;
6329
- const firstParameter = validatorFunction.params?.[0];
6330
- if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
6331
- const parameterSymbol = scopes.symbolFor(firstParameter);
6332
- if (!parameterSymbol) return false;
6333
- const payloadSymbolIds = new Set([parameterSymbol.id]);
6334
- let hasPropertyTypeCheck = false;
6335
- walkAst(validatorFunction.body, (child) => {
6336
- if (isFunctionLike$1(child)) return false;
6337
- if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init) {
6338
- const initializer = stripParenExpression(child.init);
6339
- if (isNodeOfType(initializer, "Identifier")) {
6340
- const initializerSymbol = scopes.symbolFor(initializer);
6341
- if (initializerSymbol && payloadSymbolIds.has(initializerSymbol.id)) {
6342
- const aliasSymbol = scopes.symbolFor(child.id);
6343
- if (aliasSymbol) payloadSymbolIds.add(aliasSymbol.id);
6344
- }
6345
- }
6346
- }
6347
- if (!isNodeOfType(child, "UnaryExpression") || child.operator !== "typeof") return;
6348
- const checkedValue = stripParenExpression(child.argument);
6349
- if (!isNodeOfType(checkedValue, "MemberExpression")) return;
6350
- const receiver = stripParenExpression(checkedValue.object);
6351
- if (!isNodeOfType(receiver, "Identifier")) return;
6352
- const receiverSymbol = scopes.symbolFor(receiver);
6353
- if (receiverSymbol && payloadSymbolIds.has(receiverSymbol.id)) hasPropertyTypeCheck = true;
6354
- });
6355
- return hasPropertyTypeCheck;
6356
- };
6357
- const incrementCount = (counts, keyValue) => {
6358
- counts.set(keyValue, (counts.get(keyValue) ?? 0) + 1);
6359
- };
6360
- const isReturnedExpression = (expression) => {
6361
- const parent = expression.parent;
6362
- return Boolean(parent && isNodeOfType(parent, "ReturnStatement") && parent.argument === expression);
6363
- };
6364
- const collectSafelyValidatedLocalStorageKeys = ({ programNode, scopes, resolveKey }) => {
6365
- const readCountsByKey = /* @__PURE__ */ new Map();
6366
- const safeReadCountsByKey = /* @__PURE__ */ new Map();
6367
- const safeReadSymbolIds = /* @__PURE__ */ new Set();
6368
- walkAst(programNode, (child) => {
6369
- if (!isNodeOfType(child, "CallExpression") || !isLocalStorageCall(child, "getItem")) return;
6370
- const keyArgument = child.arguments?.[0];
6371
- if (!keyArgument) return;
6372
- const keyValue = resolveKey(keyArgument);
6373
- if (keyValue !== null) incrementCount(readCountsByKey, keyValue);
6374
- });
6375
- walkAst(programNode, (child) => {
6376
- if (!isNodeOfType(child, "TryStatement") || !catchReturnsFallback(child)) return;
6377
- const rawValueKeys = /* @__PURE__ */ new Map();
6378
- const parsedValueSources = /* @__PURE__ */ new Map();
6379
- walkAst(child.block, (tryChild) => {
6380
- if (isFunctionLike$1(tryChild)) return false;
6381
- if (isNodeOfType(tryChild, "VariableDeclarator") && isNodeOfType(tryChild.id, "Identifier") && tryChild.init) {
6382
- const initializer = stripParenExpression(tryChild.init);
6383
- if (isNodeOfType(initializer, "CallExpression") && isLocalStorageCall(initializer, "getItem")) {
6384
- const keyArgument = initializer.arguments?.[0];
6385
- const bindingSymbol = scopes.symbolFor(tryChild.id);
6386
- if (keyArgument && bindingSymbol) {
6387
- const keyValue = resolveKey(keyArgument);
6388
- if (keyValue !== null) rawValueKeys.set(bindingSymbol.id, keyValue);
6389
- }
6390
- }
6391
- if (isNodeOfType(initializer, "CallExpression") && isGlobalMethodCall(initializer, "JSON", "parse")) {
6392
- const rawValueArgument = initializer.arguments?.[0];
6393
- const parsedValueSymbol = scopes.symbolFor(tryChild.id);
6394
- if (rawValueArgument && isNodeOfType(rawValueArgument, "Identifier") && parsedValueSymbol) {
6395
- const rawValueSymbol = scopes.symbolFor(rawValueArgument);
6396
- if (rawValueSymbol && rawValueKeys.has(rawValueSymbol.id)) parsedValueSources.set(parsedValueSymbol.id, rawValueSymbol.id);
6397
- }
6398
- }
6399
- }
6400
- if (!isNodeOfType(tryChild, "ConditionalExpression") || !isReturnedExpression(tryChild)) return;
6401
- const test = stripParenExpression(tryChild.test);
6402
- if (!isNodeOfType(test, "CallExpression")) return;
6403
- const testedValue = test.arguments?.[0];
6404
- if (!testedValue || !isNodeOfType(testedValue, "Identifier")) return;
6405
- const parsedValueSymbol = scopes.symbolFor(testedValue);
6406
- if (!parsedValueSymbol) return;
6407
- const rawValueSymbolId = parsedValueSources.get(parsedValueSymbol.id);
6408
- if (rawValueSymbolId === void 0) return;
6409
- const returnedValue = stripParenExpression(tryChild.consequent);
6410
- if (!isNodeOfType(returnedValue, "Identifier") || scopes.symbolFor(returnedValue)?.id !== parsedValueSymbol.id) return;
6411
- const validatorFunction = resolveValidatorFunction(test.callee, scopes);
6412
- if (!validatorFunction || !validatorChecksPayloadProperties(validatorFunction, scopes)) return;
6413
- const keyValue = rawValueKeys.get(rawValueSymbolId);
6414
- if (keyValue === void 0 || safeReadSymbolIds.has(rawValueSymbolId)) return;
6415
- safeReadSymbolIds.add(rawValueSymbolId);
6416
- incrementCount(safeReadCountsByKey, keyValue);
6417
- });
6418
- });
6419
- const safeKeys = /* @__PURE__ */ new Set();
6420
- for (const [keyValue, readCount] of readCountsByKey) if (safeReadCountsByKey.get(keyValue) === readCount) safeKeys.add(keyValue);
6421
- return safeKeys;
6422
- };
6423
- //#endregion
6424
6273
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6425
6274
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6426
6275
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
@@ -6442,39 +6291,26 @@ const clientLocalstorageNoVersion = defineRule({
6442
6291
  severity: "warn",
6443
6292
  category: "Correctness",
6444
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.",
6445
- create: (context) => {
6446
- let safelyValidatedStorageKeys = /* @__PURE__ */ new Set();
6447
- return {
6448
- Program(node) {
6449
- safelyValidatedStorageKeys = collectSafelyValidatedLocalStorageKeys({
6450
- programNode: node,
6451
- scopes: context.scopes,
6452
- resolveKey: (keyNode) => resolveStringKey(keyNode, context)
6453
- });
6454
- },
6455
- CallExpression(node) {
6456
- if (!isNodeOfType(node.callee, "MemberExpression")) return;
6457
- const receiver = stripParenExpression(node.callee.object);
6458
- if (!isNodeOfType(receiver, "Identifier")) return;
6459
- if (receiver.name !== "localStorage") return;
6460
- if (!isNodeOfType(node.callee.property, "Identifier")) return;
6461
- if (node.callee.property.name !== "setItem") return;
6462
- const keyArg = node.arguments?.[0];
6463
- if (!keyArg) return;
6464
- const keyValue = resolveStringKey(keyArg, context);
6465
- if (keyValue === null) return;
6466
- if (isVersionedKey(keyValue)) return;
6467
- if (safelyValidatedStorageKeys.has(keyValue)) return;
6468
- const valueArg = node.arguments?.[1];
6469
- if (!valueArg) return;
6470
- if (!isJsonStringifyCall(valueArg)) return;
6471
- context.report({
6472
- node: keyArg,
6473
- 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").`
6474
- });
6475
- }
6476
- };
6477
- }
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
+ } })
6478
6314
  });
6479
6315
  //#endregion
6480
6316
  //#region src/plugin/rules/client/client-passive-event-listeners.ts
@@ -7597,7 +7433,7 @@ const findJsxAttribute = (attributes, attributeName) => attributes?.find((attrib
7597
7433
  const getOpeningElementTagName = (openingElement) => {
7598
7434
  if (!openingElement) return null;
7599
7435
  if (!isNodeOfType(openingElement, "JSXOpeningElement")) return null;
7600
- if (isNodeOfType(openingElement.name, "JSXIdentifier")) return resolveJsxElementType(openingElement);
7436
+ if (isNodeOfType(openingElement.name, "JSXIdentifier")) return openingElement.name.name;
7601
7437
  if (isNodeOfType(openingElement.name, "JSXMemberExpression")) {
7602
7438
  let cursor = openingElement.name;
7603
7439
  while (isNodeOfType(cursor, "JSXMemberExpression")) cursor = cursor.property;
@@ -7849,7 +7685,7 @@ const dialogHasAccessibleName = defineRule({
7849
7685
  if (isTestlikeFilename(context.filename)) return {};
7850
7686
  return { JSXOpeningElement(node) {
7851
7687
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
7852
- const tagName = resolveJsxElementType(node);
7688
+ const tagName = node.name.name;
7853
7689
  if (tagName[0] !== tagName[0]?.toLowerCase()) return;
7854
7690
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
7855
7691
  const roleValue = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
@@ -12081,7 +11917,7 @@ const forbidDomProps = defineRule({
12081
11917
  return { JSXOpeningElement(node) {
12082
11918
  if (forbidMap.size === 0) return;
12083
11919
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12084
- const elementName = resolveJsxElementType(node);
11920
+ const elementName = node.name.name;
12085
11921
  if (isReactComponentName(elementName)) return;
12086
11922
  for (const attribute of node.attributes) {
12087
11923
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -12159,7 +11995,7 @@ const forbidElements = defineRule({
12159
11995
  return {
12160
11996
  JSXOpeningElement(node) {
12161
11997
  if (forbidMap.size === 0) return;
12162
- const fullName = resolveJsxElementType(node);
11998
+ const fullName = flattenJsxName$1(node.name);
12163
11999
  if (!fullName || !forbidMap.has(fullName)) return;
12164
12000
  context.report({
12165
12001
  node: node.name,
@@ -12521,7 +12357,8 @@ const buildMessage$22 = (childTagName) => `Your users get reshuffled HTML becaus
12521
12357
  const isParagraphElement = (candidate) => {
12522
12358
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12523
12359
  const opening = candidate.openingElement;
12524
- return resolveJsxElementType(opening) === "p";
12360
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12361
+ return opening.name.name === "p";
12525
12362
  };
12526
12363
  const findEnclosingParagraph = (openingElement) => {
12527
12364
  const owningElement = openingElement.parent;
@@ -12542,7 +12379,8 @@ const htmlNoInvalidParagraphChild = defineRule({
12542
12379
  severity: "warn",
12543
12380
  recommendation: "Swap the `<p>` for a `<div>`, or move the child outside the paragraph.",
12544
12381
  create: (context) => ({ JSXOpeningElement(node) {
12545
- const childTagName = resolveJsxElementType(node);
12382
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12383
+ const childTagName = node.name.name;
12546
12384
  if (!BLOCK_LEVEL_ELEMENTS.has(childTagName)) return;
12547
12385
  if (!findEnclosingParagraph(node)) return;
12548
12386
  context.report({
@@ -12573,7 +12411,7 @@ const getHostTagName = (jsxElement) => {
12573
12411
  if (!isNodeOfType(jsxElement, "JSXElement")) return null;
12574
12412
  const opening = jsxElement.openingElement;
12575
12413
  if (!isNodeOfType(opening.name, "JSXIdentifier")) return null;
12576
- const tagName = resolveJsxElementType(opening);
12414
+ const tagName = opening.name.name;
12577
12415
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return null;
12578
12416
  return tagName;
12579
12417
  };
@@ -12603,7 +12441,7 @@ const findClosestHostAncestor = (jsxElement) => {
12603
12441
  if (isNodeOfType(ancestor, "JSXElement")) {
12604
12442
  const opening = ancestor.openingElement;
12605
12443
  if (isNodeOfType(opening.name, "JSXIdentifier")) {
12606
- const ancestorTag = resolveJsxElementType(opening);
12444
+ const ancestorTag = opening.name.name;
12607
12445
  if (ancestorTag.length === 0) {
12608
12446
  previous = ancestor;
12609
12447
  ancestor = ancestor.parent ?? null;
@@ -12685,7 +12523,8 @@ const buildMessage$20 = (tagName) => `Your users get broken clicks, focus & scre
12685
12523
  const isJsxElementWithTagName = (candidate, tagName) => {
12686
12524
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12687
12525
  const opening = candidate.openingElement;
12688
- return resolveJsxElementType(opening) === tagName;
12526
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12527
+ return opening.name.name === tagName;
12689
12528
  };
12690
12529
  const findEnclosingSameTag = (openingElement, tagName) => {
12691
12530
  const owningElement = openingElement.parent;
@@ -12706,7 +12545,8 @@ const htmlNoNestedInteractive = defineRule({
12706
12545
  severity: "warn",
12707
12546
  recommendation: "Move the inner `<a>` or `<button>` so it's a sibling, or change the outer one to a plain `<div>` or `<span>`.",
12708
12547
  create: (context) => ({ JSXOpeningElement(node) {
12709
- const tagName = resolveJsxElementType(node);
12548
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12549
+ const tagName = node.name.name;
12710
12550
  if (tagName !== "a" && tagName !== "button") return;
12711
12551
  if (!findEnclosingSameTag(node, tagName)) return;
12712
12552
  context.report({
@@ -12821,7 +12661,7 @@ const iframeMissingSandbox = defineRule({
12821
12661
  matchByOccurrence: true,
12822
12662
  create: skipNonProductionFiles((context) => ({
12823
12663
  JSXOpeningElement(node) {
12824
- if (resolveJsxElementType(node) !== "iframe") return;
12664
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "iframe") return;
12825
12665
  const sandboxAttr = hasJsxPropIgnoreCase(node.attributes, "sandbox");
12826
12666
  if (!sandboxAttr) {
12827
12667
  const hasExplicitSrc = Boolean(hasJsxPropIgnoreCase(node.attributes, "src"));
@@ -14749,18 +14589,6 @@ const jsHoistRegexp = defineRule({
14749
14589
  }, { treatIteratorCallbacksAsLoops: true })
14750
14590
  });
14751
14591
  //#endregion
14752
- //#region src/plugin/utils/resolve-first-argument-binding.ts
14753
- const resolveFirstArgumentBinding = (firstParameter) => {
14754
- if (!firstParameter) return null;
14755
- if (!isNodeOfType(firstParameter, "RestElement")) return firstParameter;
14756
- if (!isNodeOfType(firstParameter.argument, "ArrayPattern")) return null;
14757
- const elements = firstParameter.argument.elements ?? [];
14758
- if (elements.length !== 1) return null;
14759
- const firstBinding = elements[0];
14760
- if (!firstBinding || isNodeOfType(firstBinding, "RestElement")) return null;
14761
- return firstBinding;
14762
- };
14763
- //#endregion
14764
14592
  //#region src/plugin/rules/js-performance/js-index-maps.ts
14765
14593
  const referencesParameter = (expression, parameterName) => {
14766
14594
  if (!expression) return false;
@@ -14771,7 +14599,7 @@ const referencesParameter = (expression, parameterName) => {
14771
14599
  const isSingleFieldEqualityPredicate = (node) => {
14772
14600
  const callback = node.arguments?.[0];
14773
14601
  if (!isInlineFunctionExpression(callback)) return false;
14774
- const firstParameter = resolveFirstArgumentBinding(callback.params?.[0]);
14602
+ const firstParameter = callback.params?.[0];
14775
14603
  if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
14776
14604
  let predicate = null;
14777
14605
  const body = callback.body;
@@ -15463,21 +15291,9 @@ const isSmallFixedListMember = (receiver) => {
15463
15291
  };
15464
15292
  const getResolvedInitializer = (receiver) => {
15465
15293
  if (!isNodeOfType(receiver, "Identifier")) return null;
15466
- const binding = findVariableInitializer(receiver, receiver.name);
15467
- const initializer = binding?.initializer ?? null;
15468
- if (!binding || !initializer) return null;
15469
- const isDefault = isNodeOfType(binding.bindingIdentifier.parent, "AssignmentPattern");
15470
- if (isNodeOfType(initializer, "Identifier")) {
15471
- const aliased = findVariableInitializer(initializer, initializer.name);
15472
- if (aliased?.initializer) return {
15473
- initializer: aliased.initializer,
15474
- isDefault: isDefault || isNodeOfType(aliased.bindingIdentifier.parent, "AssignmentPattern")
15475
- };
15476
- }
15477
- return {
15478
- initializer,
15479
- isDefault
15480
- };
15294
+ const initializer = findVariableInitializer(receiver, receiver.name)?.initializer ?? null;
15295
+ if (initializer && isNodeOfType(initializer, "Identifier")) return findVariableInitializer(initializer, initializer.name)?.initializer ?? initializer;
15296
+ return initializer;
15481
15297
  };
15482
15298
  const isSplitCall = (expression) => {
15483
15299
  if (!expression) return false;
@@ -15643,8 +15459,8 @@ const isBoundedConstantCollection = (collection) => {
15643
15459
  if (isScreamingSnakeCaseConstantReceiver(stripped)) return true;
15644
15460
  if (isSmallInlineLiteralArray(stripped)) return true;
15645
15461
  if (isNodeOfType(stripped, "Identifier")) {
15646
- const resolved = getResolvedInitializer(stripped);
15647
- if (resolved && !resolved.isDefault && isSmallInlineLiteralArray(resolved.initializer)) return true;
15462
+ const initializer = getResolvedInitializer(stripped);
15463
+ if (initializer && isSmallInlineLiteralArray(initializer)) return true;
15648
15464
  }
15649
15465
  return false;
15650
15466
  };
@@ -15696,8 +15512,8 @@ const jsSetMapLookups = defineRule({
15696
15512
  if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
15697
15513
  const resolvedInitializer = getResolvedInitializer(receiver);
15698
15514
  if (resolvedInitializer) {
15699
- if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
15700
- if (!resolvedInitializer.isDefault && isSmallInlineLiteralArray(resolvedInitializer.initializer)) return;
15515
+ if (isLikelyStringReceiver(resolvedInitializer)) return;
15516
+ if (isSmallInlineLiteralArray(resolvedInitializer)) return;
15701
15517
  }
15702
15518
  if (isStringElementOfSplitIteration(receiver)) return;
15703
15519
  if (isReceiverDeclaredInNearestLoop(receiver, node)) return;
@@ -16110,21 +15926,14 @@ const jsxFilenameExtension = defineRule({
16110
15926
  });
16111
15927
  //#endregion
16112
15928
  //#region src/plugin/utils/is-jsx-fragment-element.ts
16113
- const isJsxFragmentElement = (node, scopes) => {
15929
+ const isJsxFragmentElement = (node) => {
16114
15930
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
16115
15931
  const elementName = node.name;
16116
- if (isNodeOfType(elementName, "JSXIdentifier")) {
16117
- if (!scopes) return elementName.name === "Fragment";
16118
- const symbol = resolveConstIdentifierAlias(elementName, scopes);
16119
- if (!symbol) return elementName.name === "Fragment" && scopes.isGlobalReference(elementName);
16120
- return isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment";
16121
- }
15932
+ if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
16122
15933
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
16123
15934
  if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
16124
- if (elementName.property.name !== "Fragment") return false;
16125
- if (!scopes) return elementName.object.name === "React";
16126
- if (isReactNamespaceImport(elementName.object, scopes)) return true;
16127
- return elementName.object.name === "React" && scopes.isGlobalReference(elementName.object);
15935
+ if (elementName.object.name !== "React") return false;
15936
+ return elementName.property.name === "Fragment";
16128
15937
  }
16129
15938
  return false;
16130
15939
  };
@@ -16150,7 +15959,7 @@ const jsxFragments = defineRule({
16150
15959
  if (mode !== "syntax") return;
16151
15960
  if (!node.closingElement) return;
16152
15961
  const openingElement = node.openingElement;
16153
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
15962
+ if (!isJsxFragmentElement(openingElement)) return;
16154
15963
  if (openingElement.attributes.length > 0) return;
16155
15964
  context.report({
16156
15965
  node: openingElement,
@@ -18831,6 +18640,10 @@ const resolveSettings$29 = (settings) => {
18831
18640
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
18832
18641
  return reactDoctor.jsxNoScriptUrl ?? {};
18833
18642
  };
18643
+ const getElementName = (node) => {
18644
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
18645
+ return null;
18646
+ };
18834
18647
  const isLinkPropForElement = (elementName, attributeName, options) => {
18835
18648
  if (elementName === "a" && attributeName === "href") return true;
18836
18649
  const explicit = options.components?.[elementName];
@@ -18850,8 +18663,7 @@ const jsxNoScriptUrl = defineRule({
18850
18663
  create: (context) => {
18851
18664
  const options = resolveSettings$29(context.settings);
18852
18665
  return { JSXOpeningElement(node) {
18853
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18854
- const elementName = resolveJsxElementType(node);
18666
+ const elementName = getElementName(node);
18855
18667
  if (!elementName) return;
18856
18668
  for (const attribute of node.attributes) {
18857
18669
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -19038,6 +18850,11 @@ const checkTarget = (attributeValue) => {
19038
18850
  alternate: false
19039
18851
  };
19040
18852
  };
18853
+ const getOpeningElementName = (node) => {
18854
+ const name = node.name;
18855
+ if (isNodeOfType(name, "JSXIdentifier")) return name.name;
18856
+ return null;
18857
+ };
19041
18858
  const jsxNoTargetBlank = defineRule({
19042
18859
  id: "jsx-no-target-blank",
19043
18860
  title: "Unsafe target=_blank link",
@@ -19057,8 +18874,7 @@ const jsxNoTargetBlank = defineRule({
19057
18874
  return settings.formComponents.has(tagName);
19058
18875
  };
19059
18876
  return { JSXOpeningElement(node) {
19060
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
19061
- const tagName = resolveJsxElementType(node);
18877
+ const tagName = getOpeningElementName(node);
19062
18878
  if (!tagName) return;
19063
18879
  if (!isLink(tagName) && !isForm(tagName)) return;
19064
18880
  const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
@@ -19291,7 +19107,7 @@ const jsxNoUselessFragment = defineRule({
19291
19107
  return {
19292
19108
  JSXElement(node) {
19293
19109
  const openingElement = node.openingElement;
19294
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
19110
+ if (!isJsxFragmentElement(openingElement)) return;
19295
19111
  if (hasJsxKeyAttribute(openingElement)) return;
19296
19112
  if (checkChildren(node, openingElement, node.children)) return;
19297
19113
  if (isChildOfHtmlElement(node)) context.report({
@@ -20533,24 +20349,11 @@ const hasDirective = (programNode, directive) => {
20533
20349
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20534
20350
  };
20535
20351
  //#endregion
20536
- //#region src/plugin/utils/unwrap-object-integrity-expression.ts
20537
- const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
20538
- "freeze",
20539
- "seal",
20540
- "preventExtensions"
20541
- ]);
20542
- const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
20543
- const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
20544
- let expression = stripParenExpression(node);
20545
- while (isNodeOfType(expression, "CallExpression")) {
20546
- const callee = stripParenExpression(expression.callee);
20547
- 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;
20548
- const wrappedExpression = expression.arguments[0];
20549
- if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
20550
- expression = stripParenExpression(wrappedExpression);
20551
- }
20552
- return expression;
20553
- };
20352
+ //#region src/plugin/utils/is-uppercase-name.ts
20353
+ const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
20354
+ //#endregion
20355
+ //#region src/plugin/utils/is-component-assignment.ts
20356
+ 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"));
20554
20357
  //#endregion
20555
20358
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
20556
20359
  const nextjsAsyncClientComponent = defineRule({
@@ -20576,10 +20379,10 @@ const nextjsAsyncClientComponent = defineRule({
20576
20379
  },
20577
20380
  VariableDeclarator(node) {
20578
20381
  if (!fileHasUseClient) return;
20382
+ if (!isComponentAssignment(node)) return;
20383
+ if (!isInlineFunctionExpression(node.init)) return;
20384
+ if (!node.init.async) return;
20579
20385
  if (!isNodeOfType(node.id, "Identifier")) return;
20580
- if (!isUppercaseName(node.id.name) || !node.init) return;
20581
- const componentFunction = unwrapObjectIntegrityExpression(node.init, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES);
20582
- if (!isInlineFunctionExpression(componentFunction) || !componentFunction.async) return;
20583
20386
  context.report({
20584
20387
  node,
20585
20388
  message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
@@ -20881,7 +20684,7 @@ const nextjsNoAElement = defineRule({
20881
20684
  severity: "warn",
20882
20685
  recommendation: "`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",
20883
20686
  create: (context) => ({ JSXOpeningElement(node) {
20884
- if (resolveJsxElementType(node) !== "a") return;
20687
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
20885
20688
  const attributes = node.attributes ?? [];
20886
20689
  const downloadAttribute = findJsxAttribute(attributes, "download");
20887
20690
  if (downloadAttribute) {
@@ -21077,7 +20880,7 @@ const nextjsNoCssLink = defineRule({
21077
20880
  severity: "warn",
21078
20881
  recommendation: "Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.",
21079
20882
  create: (context) => ({ JSXOpeningElement(node) {
21080
- if (resolveJsxElementType(node) !== "link") return;
20883
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21081
20884
  const attributes = node.attributes ?? [];
21082
20885
  const relAttribute = findJsxAttribute(attributes, "rel");
21083
20886
  if (!relAttribute?.value) return;
@@ -21185,7 +20988,7 @@ const nextjsNoFontLink = defineRule({
21185
20988
  severity: "warn",
21186
20989
  recommendation: "`import { Inter } from \"next/font/google\"` for self-hosting, zero layout shift, and no render-blocking requests",
21187
20990
  create: (context) => ({ JSXOpeningElement(node) {
21188
- if (resolveJsxElementType(node) !== "link") return;
20991
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21189
20992
  const attributes = node.attributes ?? [];
21190
20993
  const hrefAttribute = findJsxAttribute(attributes, "href");
21191
20994
  if (!hrefAttribute?.value) return;
@@ -21360,7 +21163,7 @@ const nextjsNoImgElement = defineRule({
21360
21163
  create: (context) => {
21361
21164
  if (isGeneratedImageRenderContext(context)) return {};
21362
21165
  return { JSXOpeningElement(node) {
21363
- if (resolveJsxElementType(node) !== "img") return;
21166
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
21364
21167
  if (isGeneratedImageRenderContext(context, node)) return;
21365
21168
  const programRoot = findProgramRoot(node);
21366
21169
  if (programRoot && hasEmailTemplateImport(programRoot)) return;
@@ -21407,8 +21210,8 @@ const nextjsNoPolyfillScript = defineRule({
21407
21210
  severity: "warn",
21408
21211
  recommendation: "Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically",
21409
21212
  create: (context) => ({ JSXOpeningElement(node) {
21410
- const elementName = resolveJsxElementType(node);
21411
- if (elementName !== "script" && elementName !== "Script") return;
21213
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
21214
+ if (node.name.name !== "script" && node.name.name !== "Script") return;
21412
21215
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21413
21216
  if (!srcAttribute?.value) return;
21414
21217
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
@@ -23216,15 +23019,10 @@ const isProp = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
23216
23019
  if (!declaringNode) return false;
23217
23020
  return isReactFunctionalComponent(declaringNode) && !isReactFunctionalHOC(analysis, declaringNode) || isCustomHook(declaringNode);
23218
23021
  }));
23219
- const isWholePropsParameterBinding = (bindingNode) => {
23220
- if (isFunctionLike$1(bindingNode.parent)) return true;
23221
- let declaringFunction = bindingNode.parent;
23222
- while (declaringFunction && !isFunctionLike$1(declaringFunction)) declaringFunction = declaringFunction.parent;
23223
- return Boolean(declaringFunction && resolveFirstArgumentBinding(declaringFunction.params?.[0]) === bindingNode);
23224
- };
23225
23022
  const isWholePropsObjectReference = (analysis, ref) => isProp(analysis, ref) && Boolean(ref.resolved?.defs.some((def) => {
23226
23023
  if (def.type !== "Parameter") return false;
23227
- return isWholePropsParameterBinding(def.name);
23024
+ const bindingParent = def.name.parent;
23025
+ return isFunctionLike$1(bindingParent);
23228
23026
  }));
23229
23027
  const isIdentifierOrMemberExpression = (node) => isNodeOfType(node, "Identifier") || isNodeOfType(node, "MemberExpression");
23230
23028
  const isPropAlias = (analysis, ref) => {
@@ -26221,56 +26019,6 @@ const noBarrelImport = defineRule({
26221
26019
  }
26222
26020
  });
26223
26021
  //#endregion
26224
- //#region src/plugin/utils/function-returns-matching-expression.ts
26225
- const collectReturnedExpressions = (functionNode) => {
26226
- if (!isFunctionLike$1(functionNode)) return [];
26227
- const body = functionNode.body;
26228
- if (!body) return [];
26229
- if (!isNodeOfType(body, "BlockStatement")) return [body];
26230
- const returnedExpressions = [];
26231
- walkAst(body, (node) => {
26232
- if (node !== body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
26233
- if (isNodeOfType(node, "ReturnStatement") && node.argument) returnedExpressions.push(node.argument);
26234
- });
26235
- return returnedExpressions;
26236
- };
26237
- const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpression) => {
26238
- const visitedExpressions = /* @__PURE__ */ new Set();
26239
- const visitedFunctions = /* @__PURE__ */ new Set();
26240
- const functionMatches = (candidateFunction) => {
26241
- if (visitedFunctions.has(candidateFunction)) return false;
26242
- visitedFunctions.add(candidateFunction);
26243
- return collectReturnedExpressions(candidateFunction).some(expressionMatches);
26244
- };
26245
- const expressionMatches = (expression) => {
26246
- const unwrappedExpression = stripParenExpression(expression);
26247
- if (visitedExpressions.has(unwrappedExpression)) return false;
26248
- visitedExpressions.add(unwrappedExpression);
26249
- if (matchesExpression(unwrappedExpression)) return true;
26250
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
26251
- const symbol = scopes.symbolFor(unwrappedExpression);
26252
- if (!symbol || symbol.kind !== "const" || !symbol.initializer) return false;
26253
- const initializer = stripParenExpression(symbol.initializer);
26254
- if (isFunctionLike$1(initializer)) return false;
26255
- return expressionMatches(initializer);
26256
- }
26257
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
26258
- if (unwrappedExpression.arguments.length !== 0) return false;
26259
- if (!isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
26260
- const symbol = scopes.symbolFor(unwrappedExpression.callee);
26261
- if (!symbol || symbol.kind !== "const" && symbol.kind !== "function") return false;
26262
- const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
26263
- const candidateFunction = isFunctionLike$1(initializer) ? initializer : isFunctionLike$1(symbol.declarationNode) ? symbol.declarationNode : null;
26264
- if (!candidateFunction || candidateFunction.async || candidateFunction.generator || candidateFunction.params.length !== 0) return false;
26265
- return functionMatches(candidateFunction);
26266
- }
26267
- if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return expressionMatches(unwrappedExpression.consequent) || expressionMatches(unwrappedExpression.alternate);
26268
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return expressionMatches(unwrappedExpression.left) || expressionMatches(unwrappedExpression.right);
26269
- return false;
26270
- };
26271
- return functionMatches(functionNode);
26272
- };
26273
- //#endregion
26274
26022
  //#region src/plugin/utils/function-contains-react-render-output.ts
26275
26023
  const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
26276
26024
  "FunctionDeclaration",
@@ -26290,13 +26038,12 @@ const isCallArgumentFunctionExpression = (node) => {
26290
26038
  return parent.arguments.some((argumentNode) => argumentNode === node);
26291
26039
  };
26292
26040
  const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
26293
- const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
26294
26041
  const containsRenderOutput$1 = (rootNode, scopes) => {
26295
26042
  let hasRenderOutput = false;
26296
26043
  walkAst(rootNode, (node) => {
26297
26044
  if (hasRenderOutput) return false;
26298
26045
  if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
26299
- if (isRenderOutputExpression(node, scopes)) {
26046
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
26300
26047
  hasRenderOutput = true;
26301
26048
  return false;
26302
26049
  }
@@ -26307,7 +26054,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
26307
26054
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
26308
26055
  const cachedEntry = renderOutputCache.get(functionNode);
26309
26056
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
26310
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
26057
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
26311
26058
  renderOutputCache.set(functionNode, {
26312
26059
  scopes,
26313
26060
  hasRenderOutput
@@ -26315,9 +26062,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
26315
26062
  return hasRenderOutput;
26316
26063
  };
26317
26064
  //#endregion
26318
- //#region src/plugin/utils/is-component-assignment.ts
26319
- 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"));
26320
- //#endregion
26321
26065
  //#region src/plugin/utils/is-component-declaration.ts
26322
26066
  const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
26323
26067
  //#endregion
@@ -28775,7 +28519,7 @@ const noDisabledZoom = defineRule({
28775
28519
  category: "Accessibility",
28776
28520
  recommendation: "Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If the layout breaks at 200% zoom, fix the layout instead.",
28777
28521
  create: (context) => ({ JSXOpeningElement(node) {
28778
- if (resolveJsxElementType(node) !== "meta") return;
28522
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "meta") return;
28779
28523
  const nameAttr = findJsxAttribute(node.attributes ?? [], "name");
28780
28524
  if (!nameAttr?.value) return;
28781
28525
  if ((isNodeOfType(nameAttr.value, "Literal") ? nameAttr.value.value : null) !== "viewport") return;
@@ -31947,7 +31691,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
31947
31691
  severity: "warn",
31948
31692
  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.",
31949
31693
  create: (context) => ({ JSXOpeningElement(node) {
31950
- if (resolveJsxElementType(node) !== "img") return;
31694
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
31951
31695
  const loadingAttribute = hasJsxPropIgnoreCase(node.attributes, "loading");
31952
31696
  if (!loadingAttribute || getJsxPropStringValue(loadingAttribute)?.toLowerCase() !== "lazy") return;
31953
31697
  const fetchPriorityAttribute = hasJsxPropIgnoreCase(node.attributes, "fetchPriority");
@@ -32188,7 +31932,7 @@ const noIndeterminateAttribute = defineRule({
32188
31932
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
32189
31933
  return {
32190
31934
  JSXOpeningElement(node) {
32191
- if (resolveJsxElementType(node) !== "input") return;
31935
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
32192
31936
  let typeAttribute = null;
32193
31937
  let typeAttributeIndex = null;
32194
31938
  let indeterminateAttribute = null;
@@ -32367,12 +32111,11 @@ const isMemoCall = (node) => {
32367
32111
  };
32368
32112
  const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
32369
32113
  const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
32370
- const isInlineReference = (node, scopes) => {
32371
- const referenceNode = unwrapObjectIntegrityExpression(node, scopes);
32372
- 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";
32373
- if (isNodeOfType(referenceNode, "ObjectExpression")) return "objects";
32374
- if (isNodeOfType(referenceNode, "ArrayExpression")) return "Arrays";
32375
- if (isNodeOfType(referenceNode, "JSXElement") || isNodeOfType(referenceNode, "JSXFragment")) return "JSX";
32114
+ const isInlineReference = (node) => {
32115
+ 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";
32116
+ if (isNodeOfType(node, "ObjectExpression")) return "objects";
32117
+ if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
32118
+ if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
32376
32119
  return null;
32377
32120
  };
32378
32121
  const noInlinePropOnMemoComponent = defineRule({
@@ -32402,7 +32145,7 @@ const noInlinePropOnMemoComponent = defineRule({
32402
32145
  let elementName = null;
32403
32146
  if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
32404
32147
  if (!elementName || !memoizedComponentNames.has(elementName)) return;
32405
- const propType = isInlineReference(node.value.expression, context.scopes);
32148
+ const propType = isInlineReference(node.value.expression);
32406
32149
  if (propType) context.report({
32407
32150
  node: node.value.expression,
32408
32151
  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`
@@ -33261,13 +33004,11 @@ const noManyBooleanProps = defineRule({
33261
33004
  };
33262
33005
  const checkComponent = (functionNode, param, body, componentName, reportNode) => {
33263
33006
  if (!param) return;
33264
- const propsBinding = resolveFirstArgumentBinding(param);
33265
- if (!propsBinding) return;
33266
33007
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
33267
- if (isNodeOfType(propsBinding, "ObjectPattern")) {
33008
+ if (isNodeOfType(param, "ObjectPattern")) {
33268
33009
  const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
33269
33010
  const booleanLikePropNames = [];
33270
- for (const property of propsBinding.properties ?? []) {
33011
+ for (const property of param.properties ?? []) {
33271
33012
  if (!isNodeOfType(property, "Property")) continue;
33272
33013
  const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
33273
33014
  if (!keyName) continue;
@@ -33278,7 +33019,7 @@ const noManyBooleanProps = defineRule({
33278
33019
  reportIfMany(booleanLikePropNames, componentName, reportNode);
33279
33020
  return;
33280
33021
  }
33281
- if (isNodeOfType(propsBinding, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, propsBinding.name)], componentName, reportNode);
33022
+ if (isNodeOfType(param, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, param.name)], componentName, reportNode);
33282
33023
  };
33283
33024
  return {
33284
33025
  FunctionDeclaration(node) {
@@ -36030,7 +35771,7 @@ const noPreventDefault = defineRule({
36030
35771
  const isServerActionsFramework = hasCapability(context.settings, "server-actions");
36031
35772
  const formMessage = isServerActionsFramework ? FORM_MESSAGE_SERVER_CAPABLE : FORM_MESSAGE_GENERIC;
36032
35773
  return { JSXOpeningElement(node) {
36033
- const elementName = resolveJsxElementType(node);
35774
+ const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
36034
35775
  if (!elementName) return;
36035
35776
  const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
36036
35777
  if (!targetEventProps) return;
@@ -38494,10 +38235,9 @@ const noStringFalseOnBooleanAttribute = defineRule({
38494
38235
  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.",
38495
38236
  create: (context) => ({ JSXOpeningElement(node) {
38496
38237
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
38497
- const elementName = resolveJsxElementType(node);
38498
- const firstCharacter = elementName.charCodeAt(0);
38238
+ const firstCharacter = node.name.name.charCodeAt(0);
38499
38239
  if (firstCharacter < 97 || firstCharacter > 122) return;
38500
- if (elementName.includes("-")) return;
38240
+ if (node.name.name.includes("-")) return;
38501
38241
  for (const attribute of node.attributes) {
38502
38242
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
38503
38243
  if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
@@ -38885,7 +38625,8 @@ const noUncontrolledInput = defineRule({
38885
38625
  const undefinedInitialStateNames = isNodeOfType(componentBody, "BlockStatement") ? collectUndefinedInitialStateNames(componentBody) : /* @__PURE__ */ new Set();
38886
38626
  walkAst(componentBody, (child) => {
38887
38627
  if (!isNodeOfType(child, "JSXOpeningElement")) return;
38888
- const tagName = resolveJsxElementType(child);
38628
+ if (!isNodeOfType(child.name, "JSXIdentifier")) return;
38629
+ const tagName = child.name.name;
38889
38630
  if (!UNCONTROLLED_INPUT_TAGS.has(tagName)) return;
38890
38631
  const attributes = child.attributes ?? [];
38891
38632
  if (hasJsxSpreadAttribute(attributes)) return;
@@ -38946,7 +38687,7 @@ const noUndeferredThirdParty = defineRule({
38946
38687
  severity: "warn",
38947
38688
  recommendation: "Use `next/script` with `strategy=\"lazyOnload\"`, or add the `defer` attribute.",
38948
38689
  create: (context) => ({ JSXOpeningElement(node) {
38949
- if (resolveJsxElementType(node) !== "script") return;
38690
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "script") return;
38950
38691
  const attributes = node.attributes ?? [];
38951
38692
  const srcAttribute = findJsxAttribute(attributes, "src");
38952
38693
  if (!srcAttribute) return;
@@ -40161,7 +39902,7 @@ const noUnknownProperty = defineRule({
40161
39902
  }
40162
39903
  if (fileIsNonReactJsx) return;
40163
39904
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
40164
- const elementType = resolveJsxElementType(node);
39905
+ const elementType = node.name.name;
40165
39906
  const firstCharacter = elementType.charCodeAt(0);
40166
39907
  if (!(firstCharacter >= 97 && firstCharacter <= 122) || elementType === "fbt" || elementType === "fbs") return;
40167
39908
  let isValidHtmlTag = isKnownDomTag(elementType);
@@ -40339,21 +40080,20 @@ const expressionContainsJsxOrCreateElement = (root) => {
40339
40080
  });
40340
40081
  return found;
40341
40082
  };
40342
- const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
40343
40083
  const isReactClassComponent = (classNode) => {
40344
40084
  if (isEs6Component(classNode)) return true;
40345
40085
  return expressionContainsJsxOrCreateElement(classNode);
40346
40086
  };
40347
- const findEnclosingComponent = (node, scopes) => {
40087
+ const findEnclosingComponent = (node) => {
40348
40088
  let walker = node.parent;
40349
40089
  while (walker) {
40350
40090
  if (isFunctionLike$1(walker)) {
40351
40091
  const componentName = inferFunctionLikeName(walker);
40352
- if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
40092
+ if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
40353
40093
  component: walker,
40354
40094
  name: componentName
40355
40095
  };
40356
- if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40096
+ if (!componentName && expressionContainsJsxOrCreateElement(walker) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40357
40097
  component: walker,
40358
40098
  name: null
40359
40099
  };
@@ -40592,7 +40332,7 @@ const noUnstableNestedComponents = defineRule({
40592
40332
  if (renderPropRegex.test(propInfo.propName)) return;
40593
40333
  if (settings.allowAsProps) return;
40594
40334
  }
40595
- const enclosing = findEnclosingComponent(candidateNode, context.scopes);
40335
+ const enclosing = findEnclosingComponent(candidateNode);
40596
40336
  if (!enclosing) return;
40597
40337
  const gatedName = propInfo ? null : requiredInstantiationName;
40598
40338
  queuedReports.push({
@@ -40603,7 +40343,7 @@ const noUnstableNestedComponents = defineRule({
40603
40343
  });
40604
40344
  };
40605
40345
  const checkFunctionLike = (node) => {
40606
- if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
40346
+ if (!expressionContainsJsxOrCreateElement(node)) return;
40607
40347
  const inferredName = inferFunctionLikeName(node);
40608
40348
  const propInfo = isComponentDeclaredInProp(node);
40609
40349
  const isObjectCallback = isObjectCallbackCandidate(node);
@@ -41863,7 +41603,7 @@ const preactPreferOndblclick = defineRule({
41863
41603
  recommendation: "Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",
41864
41604
  create: (context) => ({ JSXOpeningElement(node) {
41865
41605
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
41866
- const tagName = resolveJsxElementType(node);
41606
+ const tagName = node.name.name;
41867
41607
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
41868
41608
  const onDoubleClickAttribute = findJsxAttribute(node.attributes, "onDoubleClick");
41869
41609
  if (!onDoubleClickAttribute) return;
@@ -41883,7 +41623,7 @@ const COMPAT_EXEMPT_INPUT_TYPES = new Set([
41883
41623
  ]);
41884
41624
  const isTextLikeInput = (openingElement) => {
41885
41625
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
41886
- const tagName = resolveJsxElementType(openingElement);
41626
+ const tagName = openingElement.name.name;
41887
41627
  if (tagName === "textarea") return true;
41888
41628
  if (tagName !== "input") return false;
41889
41629
  const typeAttribute = findJsxAttribute(openingElement.attributes, "type");
@@ -42040,9 +41780,8 @@ const CROSS_CUTTING_STATE_BOOLEAN_NAMES = new Set([
42040
41780
  ]);
42041
41781
  const collectBooleanPropBindings = (param) => {
42042
41782
  const bindings = /* @__PURE__ */ new Set();
42043
- const propsBinding = resolveFirstArgumentBinding(param);
42044
- if (!isNodeOfType(propsBinding, "ObjectPattern")) return bindings;
42045
- for (const property of propsBinding.properties ?? []) {
41783
+ if (!param || !isNodeOfType(param, "ObjectPattern")) return bindings;
41784
+ for (const property of param.properties ?? []) {
42046
41785
  if (!isNodeOfType(property, "Property")) continue;
42047
41786
  if (property.computed) continue;
42048
41787
  if (!isNodeOfType(property.key, "Identifier")) continue;
@@ -42303,7 +42042,7 @@ const preferHtmlDialog = defineRule({
42303
42042
  },
42304
42043
  JSXOpeningElement(node) {
42305
42044
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
42306
- const tagName = resolveJsxElementType(node);
42045
+ const tagName = node.name.name;
42307
42046
  if (tagName === "dialog") return;
42308
42047
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
42309
42048
  if (!HTML_TAGS.has(tagName)) return;
@@ -43850,13 +43589,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
43850
43589
  }
43851
43590
  return null;
43852
43591
  };
43853
- const resolveHookNameFromInitializer = (initializer, scopes) => {
43592
+ const resolveHookNameFromInitializer = (initializer) => {
43854
43593
  if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
43855
43594
  if (!isNodeOfType(initializer, "Identifier")) return null;
43856
- const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
43857
- if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
43858
- if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
43859
- return resolveTanstackQueryHookName(resolvedSymbol.initializer);
43595
+ const binding = findVariableInitializer(initializer, initializer.name);
43596
+ if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
43597
+ if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
43598
+ return resolveTanstackQueryHookName(binding.initializer);
43860
43599
  };
43861
43600
  const queryNoRestDestructuring = defineRule({
43862
43601
  id: "query-no-rest-destructuring",
@@ -43869,7 +43608,7 @@ const queryNoRestDestructuring = defineRule({
43869
43608
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
43870
43609
  if (!node.init) return;
43871
43610
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
43872
- const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
43611
+ const hookName = resolveHookNameFromInitializer(node.init);
43873
43612
  if (!hookName) return;
43874
43613
  context.report({
43875
43614
  node: node.id,
@@ -44340,7 +44079,7 @@ const renderingAnimateSvgWrapper = defineRule({
44340
44079
  severity: "warn",
44341
44080
  recommendation: "Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.",
44342
44081
  create: (context) => ({ JSXOpeningElement(node) {
44343
- if (resolveJsxElementType(node) !== "svg") return;
44082
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "svg") return;
44344
44083
  if (node.attributes?.some((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && MOTION_ANIMATE_PROPS.has(attribute.name.name))) context.report({
44345
44084
  node,
44346
44085
  message: "This is slow to render because you animate <svg> directly, so wrap it in a <div> or <motion.div> & animate that instead"
@@ -45224,7 +44963,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
45224
44963
  "useParams",
45225
44964
  "usePathname"
45226
44965
  ]);
45227
- const findHookCallBindings = (componentBody, scopes) => {
44966
+ const findHookCallBindings = (componentBody) => {
45228
44967
  const bindings = [];
45229
44968
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
45230
44969
  for (const statement of componentBody.body ?? []) {
@@ -45235,10 +44974,8 @@ const findHookCallBindings = (componentBody, scopes) => {
45235
44974
  const callee = declarator.init.callee;
45236
44975
  if (!isNodeOfType(callee, "Identifier")) continue;
45237
44976
  if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
45238
- const valueSymbol = scopes.symbolFor(declarator.id);
45239
- if (!valueSymbol) continue;
45240
44977
  bindings.push({
45241
- valueSymbol,
44978
+ valueName: declarator.id.name,
45242
44979
  hookName: callee.name,
45243
44980
  declarator
45244
44981
  });
@@ -45246,49 +44983,6 @@ const findHookCallBindings = (componentBody, scopes) => {
45246
44983
  }
45247
44984
  return bindings;
45248
44985
  };
45249
- const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
45250
- const symbols = [sourceSymbol];
45251
- const symbolIds = new Set([sourceSymbol.id]);
45252
- const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
45253
- if (!isNodeOfType(componentBody, "BlockStatement")) return {
45254
- symbols,
45255
- aliasSourceIdentifiers
45256
- };
45257
- let didFindAlias = true;
45258
- while (didFindAlias) {
45259
- didFindAlias = false;
45260
- for (const statement of componentBody.body ?? []) {
45261
- if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
45262
- for (const declarator of statement.declarations ?? []) {
45263
- let aliasIdentifier = null;
45264
- let sourceIdentifier = null;
45265
- const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
45266
- const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
45267
- const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
45268
- const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
45269
- if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
45270
- aliasIdentifier = declarator.id;
45271
- sourceIdentifier = initializer;
45272
- } else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
45273
- aliasIdentifier = arrayBinding;
45274
- sourceIdentifier = arrayValue;
45275
- }
45276
- if (!aliasIdentifier || !sourceIdentifier) continue;
45277
- const referencedSymbol = scopes.symbolFor(sourceIdentifier);
45278
- const aliasSymbol = scopes.symbolFor(aliasIdentifier);
45279
- if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
45280
- symbolIds.add(aliasSymbol.id);
45281
- symbols.push(aliasSymbol);
45282
- aliasSourceIdentifiers.add(sourceIdentifier);
45283
- didFindAlias = true;
45284
- }
45285
- }
45286
- }
45287
- return {
45288
- symbols,
45289
- aliasSourceIdentifiers
45290
- };
45291
- };
45292
44986
  const rerenderDeferReadsHook = defineRule({
45293
44987
  id: "rerender-defer-reads-hook",
45294
44988
  title: "URL hook value only read in handlers",
@@ -45299,13 +44993,15 @@ const rerenderDeferReadsHook = defineRule({
45299
44993
  create: (context) => {
45300
44994
  const checkComponent = (componentBody) => {
45301
44995
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
45302
- const bindings = findHookCallBindings(componentBody, context.scopes);
44996
+ const bindings = findHookCallBindings(componentBody);
45303
44997
  if (bindings.length === 0) return;
45304
44998
  const handlerBindingNames = collectHandlerBindingNames(componentBody);
45305
44999
  for (const binding of bindings) {
45306
- const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
45307
45000
  const referenceLocations = [];
45308
- for (const symbol of symbols) for (const reference of symbol.references) if (!aliasSourceIdentifiers.has(reference.identifier)) referenceLocations.push(reference.identifier);
45001
+ walkAst(componentBody, (child) => {
45002
+ if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
45003
+ if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
45004
+ });
45309
45005
  if (referenceLocations.length === 0) continue;
45310
45006
  if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
45311
45007
  context.report({
@@ -45676,10 +45372,14 @@ const rerenderLazyStateInit = defineRule({
45676
45372
  //#endregion
45677
45373
  //#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
45678
45374
  const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
45679
- const callbackReturnsJsx = (callback, scopes) => {
45375
+ const callbackReturnsJsx = (callback) => {
45680
45376
  if (!callback) return false;
45681
45377
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
45682
- return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
45378
+ const body = callback.body;
45379
+ if (isJsxExpression(body)) return true;
45380
+ if (!isNodeOfType(body, "BlockStatement")) return false;
45381
+ for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
45382
+ return false;
45683
45383
  };
45684
45384
  const returnArgumentUsesAnyName = (returnStatement, names) => {
45685
45385
  if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
@@ -45757,7 +45457,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
45757
45457
  if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
45758
45458
  for (const declarator of stmt.declarations ?? []) {
45759
45459
  const init = declarator.init;
45760
- if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
45460
+ if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
45761
45461
  memoNode = declarator;
45762
45462
  callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
45763
45463
  if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
@@ -45823,11 +45523,8 @@ const collectFromObjectPattern = (pattern, bindings) => {
45823
45523
  const collectDefaultedEmptyBindings = (functionNode) => {
45824
45524
  const bindings = /* @__PURE__ */ new Map();
45825
45525
  const params = functionNode.params ?? [];
45826
- for (const [parameterIndex, param] of params.entries()) {
45827
- const parameterBinding = parameterIndex === 0 ? resolveFirstArgumentBinding(param) : param;
45828
- if (parameterBinding) collectFromObjectPattern(parameterBinding, bindings);
45829
- }
45830
- const propsParam = resolveFirstArgumentBinding(params[0]);
45526
+ for (const param of params) collectFromObjectPattern(param, bindings);
45527
+ const propsParam = params[0];
45831
45528
  const body = functionNode.body;
45832
45529
  if (!propsParam || !isNodeOfType(propsParam, "Identifier") || !body) return bindings;
45833
45530
  if (!isNodeOfType(body, "BlockStatement")) return bindings;
@@ -53254,8 +52951,7 @@ const serverCacheWithObjectLiteral = defineRule({
53254
52951
  if (!isNodeOfType(node.callee, "Identifier")) return;
53255
52952
  if (!cachedFunctionNames.has(node.callee.name)) return;
53256
52953
  const firstArg = node.arguments?.[0];
53257
- if (!firstArg || isNodeOfType(firstArg, "SpreadElement")) return;
53258
- if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
52954
+ if (!isNodeOfType(firstArg, "ObjectExpression")) return;
53259
52955
  context.report({
53260
52956
  node,
53261
52957
  message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
@@ -53942,6 +53638,10 @@ const isInvalidStyleExpression = (expression) => {
53942
53638
  }
53943
53639
  return false;
53944
53640
  };
53641
+ const getJsxOpeningElementName = (node) => {
53642
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
53643
+ return null;
53644
+ };
53945
53645
  const stylePropObject = defineRule({
53946
53646
  id: "style-prop-object",
53947
53647
  title: "Style prop is not an object",
@@ -53953,8 +53653,7 @@ const stylePropObject = defineRule({
53953
53653
  const allowSet = new Set(allow);
53954
53654
  return {
53955
53655
  JSXOpeningElement(node) {
53956
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
53957
- const elementName = resolveJsxElementType(node);
53656
+ const elementName = getJsxOpeningElementName(node);
53958
53657
  if (elementName && allowSet.has(elementName)) return;
53959
53658
  if (elementName) {
53960
53659
  const firstCharCode = elementName.charCodeAt(0);
@@ -54659,7 +54358,7 @@ const tanstackStartNoAnchorElement = defineRule({
54659
54358
  create: (context) => {
54660
54359
  if (!isInProjectDirectory(context, "routes")) return {};
54661
54360
  return { JSXOpeningElement(node) {
54662
- if (resolveJsxElementType(node) !== "a") return;
54361
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
54663
54362
  const attributes = node.attributes ?? [];
54664
54363
  const hrefAttribute = findJsxAttribute(attributes, "href");
54665
54364
  if (!hrefAttribute?.value) return;
@@ -55280,7 +54979,8 @@ const voidDomElementsNoChildren = defineRule({
55280
54979
  create: (context) => ({
55281
54980
  JSXElement(node) {
55282
54981
  const openingElement = node.openingElement;
55283
- const tagName = resolveJsxElementType(openingElement);
54982
+ if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return;
54983
+ const tagName = openingElement.name.name;
55284
54984
  if (!VOID_DOM_ELEMENTS.has(tagName)) return;
55285
54985
  const hasChildrenContent = node.children.some(isMeaningfulJsxChild);
55286
54986
  const hasChildrenLikeProp = findChildrenLikePropName(openingElement.attributes);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.6-dev.81e6647",
3
+ "version": "0.7.6-dev.82e10cd",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",