oxlint-plugin-react-doctor 0.7.6-dev.e141d90 → 0.7.6-dev.e877ca7

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 -636
  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
@@ -7093,30 +6929,6 @@ const corsCookieTrustRisk = defineRule({
7093
6929
  })
7094
6930
  });
7095
6931
  //#endregion
7096
- //#region src/plugin/rules/security-scan/utils/find-matching-bracket.ts
7097
- const findMatchingBracket = (content, openIndex) => {
7098
- const open = content[openIndex];
7099
- const close = open === "(" ? ")" : open === "{" ? "}" : open === "[" ? "]" : "";
7100
- if (close === "") return -1;
7101
- let depth = 0;
7102
- let stringDelimiter = null;
7103
- for (let index = openIndex; index < content.length; index += 1) {
7104
- const character = content[index];
7105
- if (stringDelimiter !== null) {
7106
- if (character === "\\") index += 1;
7107
- else if (character === stringDelimiter) stringDelimiter = null;
7108
- continue;
7109
- }
7110
- if (character === "\"" || character === "'" || character === "`") stringDelimiter = character;
7111
- else if (character === open) depth += 1;
7112
- else if (character === close) {
7113
- depth -= 1;
7114
- if (depth === 0) return index;
7115
- }
7116
- }
7117
- return -1;
7118
- };
7119
- //#endregion
7120
6932
  //#region src/plugin/rules/security-scan/dangerous-html-sink.ts
7121
6933
  const DANGEROUS_HTML_PATTERN = /dangerouslySetInnerHTML|(?:\.(?:inner|outer)HTML|\[\s*["'](?:inner|outer)HTML["']\s*\])\s*[+]?=(?!=)|\.insertAdjacentHTML\s*\(|\bdocument\.write(?:ln)?\s*\(|\.(?:createContextualFragment|setHTMLUnsafe)\s*\(/;
7122
6934
  const HTML_VALUE_START_PATTERN = /(?:__html\s*:|(?:\.(?:inner|outer)HTML|\[\s*["'](?:inner|outer)HTML["']\s*\])\s*[+]?=(?!=)|\.insertAdjacentHTML\s*\(\s*[^,]*,|\bdocument\.write(?:ln)?\s*\(|\.(?:createContextualFragment|setHTMLUnsafe)\s*\()\s*([\s\S]*)/;
@@ -7132,7 +6944,6 @@ const I18N_VALUE_PATTERN = /\b(?:t|i18n|translate|formatMessage|intl)\s*[.(]/;
7132
6944
  const ESCAPING_SERIALIZER_CALL_PATTERN = /^(?:[\w$.]+\.)?(?:toHtml|render[A-Za-z]*(?:Html|HTML)|renderToString|renderToStaticMarkup|codeToHtml|codeToHast|highlight[A-Za-z]*)\s*\(/;
7133
6945
  const HIGHLIGHTER_LIBRARY_PATTERN = /\b(?:shiki|prism|hljs|highlightjs|getHighlighter|codeToHtml|codeToHast|refractor|lowlight|starry-night)\b|highlight\.js/i;
7134
6946
  const SERIALIZER_ASSIGNMENT_PATTERN = /[:=]\s*[^\n;]*(?:\b(?:katex|shiki|hljs|prism|mermaid)\b|hast-util-to-html|renderHtmlFromRichText|(?:toHtml|render[A-Za-z]*(?:Html|HTML)|renderToString|renderToStaticMarkup|codeToHtml|codeToHast)\s*\()/i;
7135
- const SERIALIZER_CALL_PROVENANCE_PATTERN = /\b(?:(?:katex|shiki|hljs|prism|mermaid|highlighter)[\w$]*\.(?:render\w*|highlight\w*|codeTo(?:Html|Hast))|(?:toHtml|render(?:Html|HTML)[A-Za-z]*|render[A-Za-z]*(?:Html|HTML)|renderToString|renderToStaticMarkup|codeToHtml|codeToHast|highlight[A-Za-z]*))\s*\(/i;
7136
6947
  const BARE_IDENTIFIER_VALUE_PATTERN = /^[\w$]+\s*(?:[;,})\n]|$)/;
7137
6948
  const IDENTIFIER_WITH_LITERAL_FALLBACK_PATTERN = /^[\w$]+\s*(?:\|\||\?\?)\s*(?:"[^"\n]*"|'[^'\n]*'|`[^`$]*`)\s*(?:[;,})\n]|$)/;
7138
6949
  const MEMBER_OR_INDEX_ACCESS_VALUE_PATTERN = /^[\w$]+(?:\.[\w$]+|\[[^\]]*\])+\s*(?:[;,})\n]|$)/;
@@ -7292,66 +7103,22 @@ const findContainingBlockEndIndex = (fileContent, targetIndex) => {
7292
7103
  return openingBraceIndex === void 0 ? fileContent.length : findMatchingBraceIndex(fileContent, openingBraceIndex);
7293
7104
  };
7294
7105
  const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) => {
7295
- const initializerPattern = new RegExp(`(const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
7106
+ const initializerPattern = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
7296
7107
  let nearestDeclaration = null;
7297
7108
  let nearestDeclarationIndex = -1;
7298
7109
  for (const match of fileContent.matchAll(initializerPattern)) {
7299
7110
  const declarationIndex = match.index;
7300
7111
  if (declarationIndex === void 0 || declarationIndex >= sinkIndex || declarationIndex <= nearestDeclarationIndex || findContainingBlockEndIndex(fileContent, declarationIndex) < sinkIndex) continue;
7301
7112
  nearestDeclarationIndex = declarationIndex;
7302
- const initializer = match[2];
7113
+ const initializer = match[1];
7303
7114
  if (initializer === void 0) continue;
7304
7115
  nearestDeclaration = {
7305
- declarationIndex,
7306
7116
  initializer,
7307
- initializerStartIndex: declarationIndex + match[0].length - initializer.length,
7308
- isImmutable: match[1] === "const"
7117
+ initializerStartIndex: declarationIndex + match[0].length - initializer.length
7309
7118
  };
7310
7119
  }
7311
7120
  return nearestDeclaration;
7312
7121
  };
7313
- const isDeclarationStable = (identifier, declaration, usageIndex, fileContent) => {
7314
- if (declaration.isImmutable) return true;
7315
- const textAfterInitializer = fileContent.slice(declaration.initializerStartIndex + declaration.initializer.length, usageIndex);
7316
- return !new RegExp(`(?:^|[^\\w$.])${escapeRegExp(identifier)}\\s*=(?!=)`).test(textAfterInitializer);
7317
- };
7318
- const isTrustedHighlighterValue = (valueExpression, fileContent, sinkIndex) => {
7319
- if (!/highlight/i.test(valueExpression)) return false;
7320
- const identifier = valueExpression.match(/^([\w$]+)/)?.[1];
7321
- if (identifier === void 0) return false;
7322
- const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
7323
- if (declaration !== null) return isDeclarationStable(identifier, declaration, sinkIndex, fileContent) && SERIALIZER_CALL_PROVENANCE_PATTERN.test(declaration.initializer);
7324
- return /highlighted/i.test(valueExpression) || HIGHLIGHTER_LIBRARY_PATTERN.test(fileContent);
7325
- };
7326
- const isExplicitlyTrustedHtmlValue = (valueExpression, fileContent, sinkIndex, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
7327
- const trimmedExpression = valueExpression.trim();
7328
- const concatenatedParts = splitTopLevelByPlus(trimmedExpression);
7329
- if (concatenatedParts.length > 1) return concatenatedParts.every((part) => isExplicitlyTrustedHtmlValue(part, fileContent, sinkIndex, visitedIdentifiers));
7330
- const interpolationParts = [...trimmedExpression.matchAll(/\$\{([^}]*)\}/g)].flatMap((match) => match[1] === void 0 ? [] : [match[1]]);
7331
- if (interpolationParts.length > 1) return interpolationParts.every((part) => isExplicitlyTrustedHtmlValue(part, fileContent, sinkIndex, visitedIdentifiers));
7332
- const identifier = trimmedExpression.match(/^([\w$]+)(?:(?:\??\.[\w$]+)|(?:\[\s*(?:"[^"\n]*"|'[^'\n]*'|\d+)\s*\]))*$/)?.[1];
7333
- if (identifier && !visitedIdentifiers.has(identifier)) {
7334
- const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
7335
- if (declaration && isDeclarationStable(identifier, declaration, sinkIndex, fileContent)) {
7336
- const nextVisitedIdentifiers = new Set(visitedIdentifiers);
7337
- nextVisitedIdentifiers.add(identifier);
7338
- return isExplicitlyTrustedHtmlValue(declaration.initializer, fileContent, declaration.initializerStartIndex, nextVisitedIdentifiers);
7339
- }
7340
- }
7341
- if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`) || MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`) || SANITIZER_PATTERN.test(trimmedExpression) || ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression) || I18N_VALUE_PATTERN.test(trimmedExpression) || ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression) || isTrustedHighlighterValue(trimmedExpression, fileContent, sinkIndex)) return true;
7342
- return false;
7343
- };
7344
- const doesExpressionAliasIdentifier = (expression, targetIdentifier, expressionIndex, fileContent, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
7345
- const identifier = expression.trim().match(/^([\w$]+)$/)?.[1];
7346
- if (!identifier) return false;
7347
- if (identifier === targetIdentifier) return true;
7348
- if (visitedIdentifiers.has(identifier)) return false;
7349
- const declaration = findVisibleIdentifierDeclaration(identifier, expressionIndex, fileContent);
7350
- if (!declaration?.isImmutable) return false;
7351
- const nextVisitedIdentifiers = new Set(visitedIdentifiers);
7352
- nextVisitedIdentifiers.add(identifier);
7353
- return doesExpressionAliasIdentifier(declaration.initializer, targetIdentifier, declaration.initializerStartIndex, fileContent, nextVisitedIdentifiers);
7354
- };
7355
7122
  const findContainingFunctionParameterSource = (identifier, sinkIndex, fileContent) => {
7356
7123
  const patterns = [/function\s+([\w$]+)\s*\(([^)]*)\)\s*\{/g, /(?:const|let|var)\s+([\w$]+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g];
7357
7124
  let closestSource = null;
@@ -7359,16 +7126,12 @@ const findContainingFunctionParameterSource = (identifier, sinkIndex, fileConten
7359
7126
  for (const pattern of patterns) for (const match of fileContent.matchAll(pattern)) {
7360
7127
  const matchIndex = match.index;
7361
7128
  if (matchIndex === void 0 || matchIndex >= sinkIndex || matchIndex < closestStartIndex) continue;
7362
- const bodyEndIndex = findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{"));
7363
- if (bodyEndIndex < sinkIndex) continue;
7129
+ if (findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{")) < sinkIndex) continue;
7364
7130
  const parameterIndex = (match[2] ?? "").split(",").findIndex((parameter) => parameter.trim().match(/^(?:\.\.\.)?([\w$]+)/)?.[1] === identifier);
7365
7131
  if (parameterIndex < 0) continue;
7366
7132
  closestStartIndex = matchIndex;
7367
- const functionName = match[1] ?? "";
7368
7133
  closestSource = {
7369
- bodyEndIndex,
7370
- declarationNameIndex: matchIndex + match[0].indexOf(functionName),
7371
- functionName,
7134
+ functionName: match[1] ?? "",
7372
7135
  parameterIndex
7373
7136
  };
7374
7137
  }
@@ -7399,56 +7162,28 @@ const splitTopLevelArguments = (argumentText) => {
7399
7162
  argumentsList.push(argumentText.slice(startIndex).trim());
7400
7163
  return argumentsList;
7401
7164
  };
7402
- const isBackedByFunctionParameter = (expression, expressionIndex, fileContent, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
7403
- const identifier = expression.trim().match(/^([\w$]+)$/)?.[1];
7404
- if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
7405
- if (findContainingFunctionParameterSource(identifier, expressionIndex, fileContent) !== null) return true;
7406
- const declaration = findVisibleIdentifierDeclaration(identifier, expressionIndex, fileContent);
7407
- if (!declaration || !isDeclarationStable(identifier, declaration, expressionIndex, fileContent)) return false;
7408
- const nextVisitedIdentifiers = new Set(visitedIdentifiers);
7409
- nextVisitedIdentifiers.add(identifier);
7410
- return isBackedByFunctionParameter(declaration.initializer, declaration.initializerStartIndex, fileContent, nextVisitedIdentifiers);
7411
- };
7412
- const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers, visitedCallSites) => {
7165
+ const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers) => {
7413
7166
  const trimmedExpression = expression.trim();
7414
- if (isExplicitlyTrustedHtmlValue(trimmedExpression, fileContent, sinkIndex)) return false;
7415
- const identifier = trimmedExpression.match(/^([\w$]+)(?:\.|\s*(?:[;,})\n]|$))/)?.[1];
7416
- if (identifier === void 0) return HTML_TAINT_PATTERN.test(trimmedExpression);
7417
- if (visitedIdentifiers.has(identifier)) return false;
7167
+ if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
7168
+ if (MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
7169
+ if (SANITIZER_PATTERN.test(trimmedExpression)) return false;
7170
+ if (ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression)) return false;
7171
+ if (I18N_VALUE_PATTERN.test(trimmedExpression)) return false;
7172
+ if (ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression)) return false;
7173
+ if (HTML_TAINT_PATTERN.test(trimmedExpression)) return true;
7174
+ const identifier = trimmedExpression.match(/^([\w$]+)\s*(?:[;,})\n]|$)/)?.[1];
7175
+ if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
7418
7176
  visitedIdentifiers.add(identifier);
7419
7177
  const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
7178
+ if (declaration !== null && isHtmlTainted(declaration.initializer, fileContent, sinkIndex, visitedIdentifiers)) return true;
7420
7179
  const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
7421
- if (declaration !== null && (parameterSource === null || declaration.declarationIndex > parameterSource.declarationNameIndex)) {
7422
- if (isDeclarationStable(identifier, declaration, sinkIndex, fileContent) && isExplicitlyTrustedHtmlValue(declaration.initializer, fileContent, declaration.initializerStartIndex)) return false;
7423
- if (!isDeclarationStable(identifier, declaration, sinkIndex, fileContent)) return true;
7424
- if (isHtmlTainted(declaration.initializer, fileContent, declaration.initializerStartIndex, visitedIdentifiers, visitedCallSites)) return true;
7425
- if (isBackedByFunctionParameter(declaration.initializer, declaration.initializerStartIndex, fileContent)) return false;
7426
- return HTML_TAINT_PATTERN.test(trimmedExpression);
7427
- }
7428
- if (parameterSource !== null && parameterSource.functionName.length > 0) {
7429
- const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(`, "g");
7430
- let didInspectCallArgument = false;
7431
- let didInspectOnlyExplicitlyTrustedArguments = true;
7432
- for (const callMatch of fileContent.matchAll(callPattern)) {
7433
- if (callMatch.index === parameterSource.declarationNameIndex || visitedCallSites.has(callMatch.index)) continue;
7434
- const openingParenthesisIndex = callMatch.index + callMatch[0].lastIndexOf("(");
7435
- const closingParenthesisIndex = findMatchingBracket(fileContent, openingParenthesisIndex);
7436
- if (closingParenthesisIndex < 0) continue;
7437
- const argument = splitTopLevelArguments(fileContent.slice(openingParenthesisIndex + 1, closingParenthesisIndex))[parameterSource.parameterIndex];
7438
- if (argument === void 0) continue;
7439
- if (callMatch.index >= parameterSource.declarationNameIndex && callMatch.index <= parameterSource.bodyEndIndex && doesExpressionAliasIdentifier(argument, identifier, callMatch.index, fileContent)) continue;
7440
- didInspectCallArgument = true;
7441
- didInspectOnlyExplicitlyTrustedArguments &&= isExplicitlyTrustedHtmlValue(argument, fileContent, callMatch.index);
7442
- const callVisitedIdentifiers = new Set(visitedIdentifiers);
7443
- callVisitedIdentifiers.delete(identifier);
7444
- const nextVisitedCallSites = new Set(visitedCallSites);
7445
- nextVisitedCallSites.add(callMatch.index);
7446
- if (isHtmlTainted(argument, fileContent, callMatch.index, callVisitedIdentifiers, nextVisitedCallSites)) return true;
7447
- }
7448
- if (didInspectCallArgument) return !didInspectOnlyExplicitlyTrustedArguments && HTML_TAINT_PATTERN.test(trimmedExpression);
7449
- return HTML_TAINT_PATTERN.test(trimmedExpression);
7450
- }
7451
- return HTML_TAINT_PATTERN.test(trimmedExpression);
7180
+ if (parameterSource === null || parameterSource.functionName.length === 0) return false;
7181
+ const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(([^)]*)\\)`, "g");
7182
+ for (const callMatch of fileContent.matchAll(callPattern)) {
7183
+ const argument = splitTopLevelArguments(callMatch[1] ?? "")[parameterSource.parameterIndex];
7184
+ if (argument !== void 0 && isHtmlTainted(argument, fileContent, sinkIndex, new Set(visitedIdentifiers))) return true;
7185
+ }
7186
+ return false;
7452
7187
  };
7453
7188
  const dangerousHtmlSink = defineRule({
7454
7189
  id: "dangerous-html-sink",
@@ -7487,7 +7222,7 @@ const dangerousHtmlSink = defineRule({
7487
7222
  const longValueTail = HTML_VALUE_START_PATTERN.exec(lines.slice(lineIndex, lineIndex + 1 + STATIC_TEMPLATE_LOOKAHEAD_LINES).join("\n"))?.[1]?.trimStart();
7488
7223
  let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
7489
7224
  const valueIdentifier = BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression) || MEMBER_OR_INDEX_ACCESS_VALUE_PATTERN.test(valueExpression) || IDENTIFIER_WITH_LITERAL_FALLBACK_PATTERN.test(valueExpression) ? valueExpression.match(/^[\w$]+/)?.[0] : void 0;
7490
- if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression) && findContainingFunctionParameterSource(valueIdentifier, sinkIndex, file.content) === null) {
7225
+ if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
7491
7226
  const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, sinkIndex, file.content);
7492
7227
  if (declarationInitializer !== null) {
7493
7228
  if (isPureStringLiteralConcat(declarationInitializer)) continue;
@@ -7496,23 +7231,17 @@ const dangerousHtmlSink = defineRule({
7496
7231
  }
7497
7232
  if (templateInterpolations === "") continue;
7498
7233
  const judgedExpression = templateInterpolations ?? valueExpression;
7499
- const doesJudgedExpressionCombineValues = splitTopLevelByPlus(judgedExpression).length > 1 || (templateInterpolations?.match(/\$\{/g)?.length ?? 0) > 1;
7500
- if (!doesJudgedExpressionCombineValues && SANITIZER_PATTERN.test(judgedExpression)) continue;
7501
- if (!doesJudgedExpressionCombineValues && ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
7502
- if (!doesJudgedExpressionCombineValues && I18N_VALUE_PATTERN.test(judgedExpression)) continue;
7503
- if (!isHtmlTainted(judgedExpression, file.content, sinkIndex, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set())) continue;
7234
+ if (SANITIZER_PATTERN.test(judgedExpression)) continue;
7235
+ if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
7236
+ if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
7237
+ if (!isHtmlTainted(judgedExpression, file.content, sinkIndex, /* @__PURE__ */ new Set())) continue;
7504
7238
  if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
7505
- if (isTrustedHighlighterValue(valueExpression, file.content, sinkIndex)) continue;
7239
+ if (/highlighted/i.test(valueExpression)) continue;
7240
+ if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
7506
7241
  if (valueIdentifier !== void 0) {
7507
7242
  const escapedIdentifier = escapeRegExp(valueIdentifier);
7508
- const visibleDeclaration = findVisibleIdentifierDeclaration(valueIdentifier, sinkIndex, file.content);
7509
- const visibleInitializer = visibleDeclaration?.initializer;
7510
- if (!(visibleInitializer !== void 0 && (splitTopLevelByPlus(visibleInitializer).length > 1 || (visibleInitializer.match(/\$\{/g)?.length ?? 0) > 1))) {
7511
- const fromSerializer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SERIALIZER_ASSIGNMENT_PATTERN.source}`, "i");
7512
- if (visibleInitializer === void 0 ? fromSerializer.test(file.content) : visibleDeclaration !== null && isDeclarationStable(valueIdentifier, visibleDeclaration, sinkIndex, file.content) && SERIALIZER_CALL_PROVENANCE_PATTERN.test(visibleInitializer)) continue;
7513
- const fromSanitizer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SANITIZED_ASSIGNMENT_PATTERN.source}`, "i");
7514
- if (visibleInitializer === void 0 ? fromSanitizer.test(file.content) : visibleDeclaration !== null && isDeclarationStable(valueIdentifier, visibleDeclaration, sinkIndex, file.content) && SANITIZED_ASSIGNMENT_PATTERN.test(`=${visibleInitializer}`)) continue;
7515
- }
7243
+ if (new RegExp(`\\b${escapedIdentifier}\\b\\s*${SERIALIZER_ASSIGNMENT_PATTERN.source}`, "i").test(file.content)) continue;
7244
+ if (new RegExp(`\\b${escapedIdentifier}\\b\\s*${SANITIZED_ASSIGNMENT_PATTERN.source}`, "i").test(file.content)) continue;
7516
7245
  if (new RegExp(`\\b${escapedIdentifier}\\b\\s*${DOM_CONTENT_ASSIGNMENT_PATTERN.source}`).test(file.content)) continue;
7517
7246
  if (new RegExp(`highlight[\\w$]*\\s*\\.map\\(\\s*(?:async\\s+)?\\(?\\s*${escapedIdentifier}\\b`, "i").test(file.content) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
7518
7247
  }
@@ -7597,7 +7326,7 @@ const findJsxAttribute = (attributes, attributeName) => attributes?.find((attrib
7597
7326
  const getOpeningElementTagName = (openingElement) => {
7598
7327
  if (!openingElement) return null;
7599
7328
  if (!isNodeOfType(openingElement, "JSXOpeningElement")) return null;
7600
- if (isNodeOfType(openingElement.name, "JSXIdentifier")) return resolveJsxElementType(openingElement);
7329
+ if (isNodeOfType(openingElement.name, "JSXIdentifier")) return openingElement.name.name;
7601
7330
  if (isNodeOfType(openingElement.name, "JSXMemberExpression")) {
7602
7331
  let cursor = openingElement.name;
7603
7332
  while (isNodeOfType(cursor, "JSXMemberExpression")) cursor = cursor.property;
@@ -7849,7 +7578,7 @@ const dialogHasAccessibleName = defineRule({
7849
7578
  if (isTestlikeFilename(context.filename)) return {};
7850
7579
  return { JSXOpeningElement(node) {
7851
7580
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
7852
- const tagName = resolveJsxElementType(node);
7581
+ const tagName = node.name.name;
7853
7582
  if (tagName[0] !== tagName[0]?.toLowerCase()) return;
7854
7583
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
7855
7584
  const roleValue = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
@@ -12081,7 +11810,7 @@ const forbidDomProps = defineRule({
12081
11810
  return { JSXOpeningElement(node) {
12082
11811
  if (forbidMap.size === 0) return;
12083
11812
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12084
- const elementName = resolveJsxElementType(node);
11813
+ const elementName = node.name.name;
12085
11814
  if (isReactComponentName(elementName)) return;
12086
11815
  for (const attribute of node.attributes) {
12087
11816
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -12159,7 +11888,7 @@ const forbidElements = defineRule({
12159
11888
  return {
12160
11889
  JSXOpeningElement(node) {
12161
11890
  if (forbidMap.size === 0) return;
12162
- const fullName = resolveJsxElementType(node);
11891
+ const fullName = flattenJsxName$1(node.name);
12163
11892
  if (!fullName || !forbidMap.has(fullName)) return;
12164
11893
  context.report({
12165
11894
  node: node.name,
@@ -12521,7 +12250,8 @@ const buildMessage$22 = (childTagName) => `Your users get reshuffled HTML becaus
12521
12250
  const isParagraphElement = (candidate) => {
12522
12251
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12523
12252
  const opening = candidate.openingElement;
12524
- return resolveJsxElementType(opening) === "p";
12253
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12254
+ return opening.name.name === "p";
12525
12255
  };
12526
12256
  const findEnclosingParagraph = (openingElement) => {
12527
12257
  const owningElement = openingElement.parent;
@@ -12542,7 +12272,8 @@ const htmlNoInvalidParagraphChild = defineRule({
12542
12272
  severity: "warn",
12543
12273
  recommendation: "Swap the `<p>` for a `<div>`, or move the child outside the paragraph.",
12544
12274
  create: (context) => ({ JSXOpeningElement(node) {
12545
- const childTagName = resolveJsxElementType(node);
12275
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12276
+ const childTagName = node.name.name;
12546
12277
  if (!BLOCK_LEVEL_ELEMENTS.has(childTagName)) return;
12547
12278
  if (!findEnclosingParagraph(node)) return;
12548
12279
  context.report({
@@ -12573,7 +12304,7 @@ const getHostTagName = (jsxElement) => {
12573
12304
  if (!isNodeOfType(jsxElement, "JSXElement")) return null;
12574
12305
  const opening = jsxElement.openingElement;
12575
12306
  if (!isNodeOfType(opening.name, "JSXIdentifier")) return null;
12576
- const tagName = resolveJsxElementType(opening);
12307
+ const tagName = opening.name.name;
12577
12308
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return null;
12578
12309
  return tagName;
12579
12310
  };
@@ -12603,7 +12334,7 @@ const findClosestHostAncestor = (jsxElement) => {
12603
12334
  if (isNodeOfType(ancestor, "JSXElement")) {
12604
12335
  const opening = ancestor.openingElement;
12605
12336
  if (isNodeOfType(opening.name, "JSXIdentifier")) {
12606
- const ancestorTag = resolveJsxElementType(opening);
12337
+ const ancestorTag = opening.name.name;
12607
12338
  if (ancestorTag.length === 0) {
12608
12339
  previous = ancestor;
12609
12340
  ancestor = ancestor.parent ?? null;
@@ -12685,7 +12416,8 @@ const buildMessage$20 = (tagName) => `Your users get broken clicks, focus & scre
12685
12416
  const isJsxElementWithTagName = (candidate, tagName) => {
12686
12417
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12687
12418
  const opening = candidate.openingElement;
12688
- return resolveJsxElementType(opening) === tagName;
12419
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12420
+ return opening.name.name === tagName;
12689
12421
  };
12690
12422
  const findEnclosingSameTag = (openingElement, tagName) => {
12691
12423
  const owningElement = openingElement.parent;
@@ -12706,7 +12438,8 @@ const htmlNoNestedInteractive = defineRule({
12706
12438
  severity: "warn",
12707
12439
  recommendation: "Move the inner `<a>` or `<button>` so it's a sibling, or change the outer one to a plain `<div>` or `<span>`.",
12708
12440
  create: (context) => ({ JSXOpeningElement(node) {
12709
- const tagName = resolveJsxElementType(node);
12441
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12442
+ const tagName = node.name.name;
12710
12443
  if (tagName !== "a" && tagName !== "button") return;
12711
12444
  if (!findEnclosingSameTag(node, tagName)) return;
12712
12445
  context.report({
@@ -12821,7 +12554,7 @@ const iframeMissingSandbox = defineRule({
12821
12554
  matchByOccurrence: true,
12822
12555
  create: skipNonProductionFiles((context) => ({
12823
12556
  JSXOpeningElement(node) {
12824
- if (resolveJsxElementType(node) !== "iframe") return;
12557
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "iframe") return;
12825
12558
  const sandboxAttr = hasJsxPropIgnoreCase(node.attributes, "sandbox");
12826
12559
  if (!sandboxAttr) {
12827
12560
  const hasExplicitSrc = Boolean(hasJsxPropIgnoreCase(node.attributes, "src"));
@@ -13057,6 +12790,30 @@ const insecureCryptoRisk = defineRule({
13057
12790
  }
13058
12791
  });
13059
12792
  //#endregion
12793
+ //#region src/plugin/rules/security-scan/utils/find-matching-bracket.ts
12794
+ const findMatchingBracket = (content, openIndex) => {
12795
+ const open = content[openIndex];
12796
+ const close = open === "(" ? ")" : open === "{" ? "}" : open === "[" ? "]" : "";
12797
+ if (close === "") return -1;
12798
+ let depth = 0;
12799
+ let stringDelimiter = null;
12800
+ for (let index = openIndex; index < content.length; index += 1) {
12801
+ const character = content[index];
12802
+ if (stringDelimiter !== null) {
12803
+ if (character === "\\") index += 1;
12804
+ else if (character === stringDelimiter) stringDelimiter = null;
12805
+ continue;
12806
+ }
12807
+ if (character === "\"" || character === "'" || character === "`") stringDelimiter = character;
12808
+ else if (character === open) depth += 1;
12809
+ else if (character === close) {
12810
+ depth -= 1;
12811
+ if (depth === 0) return index;
12812
+ }
12813
+ }
12814
+ return -1;
12815
+ };
12816
+ //#endregion
13060
12817
  //#region src/plugin/rules/security-scan/insecure-session-cookie.ts
13061
12818
  const AUTH_COOKIE_NAME_TOKEN = `(?<![A-Za-z0-9])(?:session|sess|sid|connect\\.sid|auth|jwt|access[_-]?token|refresh[_-]?token|id[_-]?token)(?![A-Za-z0-9])`;
13062
12819
  const AUTH_COOKIE_NAME_LITERAL = `[\`"'][^\`"']*?${AUTH_COOKIE_NAME_TOKEN}[^\`"']*[\`"']`;
@@ -13813,28 +13570,6 @@ const jsAsyncReduceWithoutAwaitedAcc = defineRule({
13813
13570
  } })
13814
13571
  });
13815
13572
  //#endregion
13816
- //#region src/plugin/utils/unwrap-discarded-expression.ts
13817
- const unwrapDiscardedExpression = (node) => {
13818
- let expression = isNodeOfType(node, "ExpressionStatement") ? node.expression : node;
13819
- for (;;) {
13820
- expression = stripParenExpression(expression);
13821
- if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") {
13822
- expression = expression.argument;
13823
- continue;
13824
- }
13825
- if (isNodeOfType(expression, "SequenceExpression")) {
13826
- const expressions = expression.expressions ?? [];
13827
- const finalExpression = expressions.at(-1);
13828
- const prefixExpressions = expressions.slice(0, -1);
13829
- if (finalExpression && prefixExpressions.length > 0 && prefixExpressions.every((prefixExpression) => isNodeOfType(stripParenExpression(prefixExpression), "Literal"))) {
13830
- expression = finalExpression;
13831
- continue;
13832
- }
13833
- }
13834
- return expression;
13835
- }
13836
- };
13837
- //#endregion
13838
13573
  //#region src/plugin/rules/js-performance/js-batch-dom-css.ts
13839
13574
  const ITERATOR_METHOD_NAMES$2 = new Set([
13840
13575
  "forEach",
@@ -14019,19 +13754,9 @@ const hasAttachmentBefore = (scopeOwner, elementName, beforeStart) => {
14019
13754
  });
14020
13755
  return foundAttachment;
14021
13756
  };
14022
- const getStyleAssignment = (node) => {
14023
- if (!isNodeOfType(node, "ExpressionStatement")) return null;
14024
- const expression = unwrapDiscardedExpression(node);
14025
- if (!isNodeOfType(expression, "AssignmentExpression")) return null;
14026
- if (!isNodeOfType(expression.left, "MemberExpression")) return null;
14027
- if (!isNodeOfType(expression.left.object, "MemberExpression")) return null;
14028
- if (!isNodeOfType(expression.left.object.property, "Identifier")) return null;
14029
- return expression.left.object.property.name === "style" ? expression : null;
14030
- };
14031
13757
  const isProvablyDetachedAtWrite = (styleWriteStatement) => {
14032
- const assignment = getStyleAssignment(styleWriteStatement);
14033
- if (!assignment || !isNodeOfType(assignment.left, "MemberExpression") || !isNodeOfType(assignment.left.object, "MemberExpression")) return false;
14034
- const elementExpression = assignment.left.object.object;
13758
+ if (!isNodeOfType(styleWriteStatement, "ExpressionStatement") || !isNodeOfType(styleWriteStatement.expression, "AssignmentExpression") || !isNodeOfType(styleWriteStatement.expression.left, "MemberExpression") || !isNodeOfType(styleWriteStatement.expression.left.object, "MemberExpression")) return false;
13759
+ const elementExpression = styleWriteStatement.expression.left.object.object;
14035
13760
  const creationRoot = resolveDetachedCreationRoot(elementExpression, 0);
14036
13761
  if (!creationRoot) return false;
14037
13762
  return !hasAttachmentBefore(creationRoot.scopeOwner, creationRoot.rootName, getNodeStart$1(styleWriteStatement));
@@ -14043,17 +13768,15 @@ const jsBatchDomCss = defineRule({
14043
13768
  severity: "warn",
14044
13769
  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",
14045
13770
  create: (context) => {
14046
- const writesLayoutAffectingProperty = (node) => {
14047
- const assignment = getStyleAssignment(node);
14048
- return assignment !== null && isNodeOfType(assignment.left, "MemberExpression") && isNodeOfType(assignment.left.property, "Identifier") && !LAYOUT_NEUTRAL_STYLE_PROPERTY_NAMES.has(assignment.left.property.name);
14049
- };
13771
+ 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";
13772
+ 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);
14050
13773
  return { BlockStatement(node) {
14051
13774
  const perIterationBody = findEnclosingPerIterationBody(node);
14052
13775
  if (!perIterationBody) return;
14053
13776
  const statements = node.body ?? [];
14054
13777
  let layoutReads = null;
14055
13778
  for (let statementIndex = 1; statementIndex < statements.length; statementIndex++) {
14056
- if (getStyleAssignment(statements[statementIndex]) === null || getStyleAssignment(statements[statementIndex - 1]) === null) continue;
13779
+ if (!isStyleAssignment(statements[statementIndex]) || !isStyleAssignment(statements[statementIndex - 1])) continue;
14057
13780
  if (!writesLayoutAffectingProperty(statements[statementIndex]) && !writesLayoutAffectingProperty(statements[statementIndex - 1])) continue;
14058
13781
  layoutReads ??= scanPerIterationLayoutReads(perIterationBody);
14059
13782
  if (!layoutReads.hasUsedLayoutRead || layoutReads.hasDeliberateForcedReflow) return;
@@ -14749,18 +14472,6 @@ const jsHoistRegexp = defineRule({
14749
14472
  }, { treatIteratorCallbacksAsLoops: true })
14750
14473
  });
14751
14474
  //#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
14475
  //#region src/plugin/rules/js-performance/js-index-maps.ts
14765
14476
  const referencesParameter = (expression, parameterName) => {
14766
14477
  if (!expression) return false;
@@ -14771,7 +14482,7 @@ const referencesParameter = (expression, parameterName) => {
14771
14482
  const isSingleFieldEqualityPredicate = (node) => {
14772
14483
  const callback = node.arguments?.[0];
14773
14484
  if (!isInlineFunctionExpression(callback)) return false;
14774
- const firstParameter = resolveFirstArgumentBinding(callback.params?.[0]);
14485
+ const firstParameter = callback.params?.[0];
14775
14486
  if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
14776
14487
  let predicate = null;
14777
14488
  const body = callback.body;
@@ -15463,21 +15174,9 @@ const isSmallFixedListMember = (receiver) => {
15463
15174
  };
15464
15175
  const getResolvedInitializer = (receiver) => {
15465
15176
  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
- };
15177
+ const initializer = findVariableInitializer(receiver, receiver.name)?.initializer ?? null;
15178
+ if (initializer && isNodeOfType(initializer, "Identifier")) return findVariableInitializer(initializer, initializer.name)?.initializer ?? initializer;
15179
+ return initializer;
15481
15180
  };
15482
15181
  const isSplitCall = (expression) => {
15483
15182
  if (!expression) return false;
@@ -15643,8 +15342,8 @@ const isBoundedConstantCollection = (collection) => {
15643
15342
  if (isScreamingSnakeCaseConstantReceiver(stripped)) return true;
15644
15343
  if (isSmallInlineLiteralArray(stripped)) return true;
15645
15344
  if (isNodeOfType(stripped, "Identifier")) {
15646
- const resolved = getResolvedInitializer(stripped);
15647
- if (resolved && !resolved.isDefault && isSmallInlineLiteralArray(resolved.initializer)) return true;
15345
+ const initializer = getResolvedInitializer(stripped);
15346
+ if (initializer && isSmallInlineLiteralArray(initializer)) return true;
15648
15347
  }
15649
15348
  return false;
15650
15349
  };
@@ -15696,8 +15395,8 @@ const jsSetMapLookups = defineRule({
15696
15395
  if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
15697
15396
  const resolvedInitializer = getResolvedInitializer(receiver);
15698
15397
  if (resolvedInitializer) {
15699
- if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
15700
- if (!resolvedInitializer.isDefault && isSmallInlineLiteralArray(resolvedInitializer.initializer)) return;
15398
+ if (isLikelyStringReceiver(resolvedInitializer)) return;
15399
+ if (isSmallInlineLiteralArray(resolvedInitializer)) return;
15701
15400
  }
15702
15401
  if (isStringElementOfSplitIteration(receiver)) return;
15703
15402
  if (isReceiverDeclaredInNearestLoop(receiver, node)) return;
@@ -16110,21 +15809,14 @@ const jsxFilenameExtension = defineRule({
16110
15809
  });
16111
15810
  //#endregion
16112
15811
  //#region src/plugin/utils/is-jsx-fragment-element.ts
16113
- const isJsxFragmentElement = (node, scopes) => {
15812
+ const isJsxFragmentElement = (node) => {
16114
15813
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
16115
15814
  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
- }
15815
+ if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
16122
15816
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
16123
15817
  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);
15818
+ if (elementName.object.name !== "React") return false;
15819
+ return elementName.property.name === "Fragment";
16128
15820
  }
16129
15821
  return false;
16130
15822
  };
@@ -16150,7 +15842,7 @@ const jsxFragments = defineRule({
16150
15842
  if (mode !== "syntax") return;
16151
15843
  if (!node.closingElement) return;
16152
15844
  const openingElement = node.openingElement;
16153
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
15845
+ if (!isJsxFragmentElement(openingElement)) return;
16154
15846
  if (openingElement.attributes.length > 0) return;
16155
15847
  context.report({
16156
15848
  node: openingElement,
@@ -18831,6 +18523,10 @@ const resolveSettings$29 = (settings) => {
18831
18523
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
18832
18524
  return reactDoctor.jsxNoScriptUrl ?? {};
18833
18525
  };
18526
+ const getElementName = (node) => {
18527
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
18528
+ return null;
18529
+ };
18834
18530
  const isLinkPropForElement = (elementName, attributeName, options) => {
18835
18531
  if (elementName === "a" && attributeName === "href") return true;
18836
18532
  const explicit = options.components?.[elementName];
@@ -18850,8 +18546,7 @@ const jsxNoScriptUrl = defineRule({
18850
18546
  create: (context) => {
18851
18547
  const options = resolveSettings$29(context.settings);
18852
18548
  return { JSXOpeningElement(node) {
18853
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18854
- const elementName = resolveJsxElementType(node);
18549
+ const elementName = getElementName(node);
18855
18550
  if (!elementName) return;
18856
18551
  for (const attribute of node.attributes) {
18857
18552
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -19038,6 +18733,11 @@ const checkTarget = (attributeValue) => {
19038
18733
  alternate: false
19039
18734
  };
19040
18735
  };
18736
+ const getOpeningElementName = (node) => {
18737
+ const name = node.name;
18738
+ if (isNodeOfType(name, "JSXIdentifier")) return name.name;
18739
+ return null;
18740
+ };
19041
18741
  const jsxNoTargetBlank = defineRule({
19042
18742
  id: "jsx-no-target-blank",
19043
18743
  title: "Unsafe target=_blank link",
@@ -19057,8 +18757,7 @@ const jsxNoTargetBlank = defineRule({
19057
18757
  return settings.formComponents.has(tagName);
19058
18758
  };
19059
18759
  return { JSXOpeningElement(node) {
19060
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
19061
- const tagName = resolveJsxElementType(node);
18760
+ const tagName = getOpeningElementName(node);
19062
18761
  if (!tagName) return;
19063
18762
  if (!isLink(tagName) && !isForm(tagName)) return;
19064
18763
  const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
@@ -19291,7 +18990,7 @@ const jsxNoUselessFragment = defineRule({
19291
18990
  return {
19292
18991
  JSXElement(node) {
19293
18992
  const openingElement = node.openingElement;
19294
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
18993
+ if (!isJsxFragmentElement(openingElement)) return;
19295
18994
  if (hasJsxKeyAttribute(openingElement)) return;
19296
18995
  if (checkChildren(node, openingElement, node.children)) return;
19297
18996
  if (isChildOfHtmlElement(node)) context.report({
@@ -20533,24 +20232,11 @@ const hasDirective = (programNode, directive) => {
20533
20232
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20534
20233
  };
20535
20234
  //#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
- };
20235
+ //#region src/plugin/utils/is-uppercase-name.ts
20236
+ const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
20237
+ //#endregion
20238
+ //#region src/plugin/utils/is-component-assignment.ts
20239
+ 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
20240
  //#endregion
20555
20241
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
20556
20242
  const nextjsAsyncClientComponent = defineRule({
@@ -20576,10 +20262,10 @@ const nextjsAsyncClientComponent = defineRule({
20576
20262
  },
20577
20263
  VariableDeclarator(node) {
20578
20264
  if (!fileHasUseClient) return;
20265
+ if (!isComponentAssignment(node)) return;
20266
+ if (!isInlineFunctionExpression(node.init)) return;
20267
+ if (!node.init.async) return;
20579
20268
  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
20269
  context.report({
20584
20270
  node,
20585
20271
  message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
@@ -20881,7 +20567,7 @@ const nextjsNoAElement = defineRule({
20881
20567
  severity: "warn",
20882
20568
  recommendation: "`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",
20883
20569
  create: (context) => ({ JSXOpeningElement(node) {
20884
- if (resolveJsxElementType(node) !== "a") return;
20570
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
20885
20571
  const attributes = node.attributes ?? [];
20886
20572
  const downloadAttribute = findJsxAttribute(attributes, "download");
20887
20573
  if (downloadAttribute) {
@@ -21077,7 +20763,7 @@ const nextjsNoCssLink = defineRule({
21077
20763
  severity: "warn",
21078
20764
  recommendation: "Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.",
21079
20765
  create: (context) => ({ JSXOpeningElement(node) {
21080
- if (resolveJsxElementType(node) !== "link") return;
20766
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21081
20767
  const attributes = node.attributes ?? [];
21082
20768
  const relAttribute = findJsxAttribute(attributes, "rel");
21083
20769
  if (!relAttribute?.value) return;
@@ -21185,7 +20871,7 @@ const nextjsNoFontLink = defineRule({
21185
20871
  severity: "warn",
21186
20872
  recommendation: "`import { Inter } from \"next/font/google\"` for self-hosting, zero layout shift, and no render-blocking requests",
21187
20873
  create: (context) => ({ JSXOpeningElement(node) {
21188
- if (resolveJsxElementType(node) !== "link") return;
20874
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21189
20875
  const attributes = node.attributes ?? [];
21190
20876
  const hrefAttribute = findJsxAttribute(attributes, "href");
21191
20877
  if (!hrefAttribute?.value) return;
@@ -21360,7 +21046,7 @@ const nextjsNoImgElement = defineRule({
21360
21046
  create: (context) => {
21361
21047
  if (isGeneratedImageRenderContext(context)) return {};
21362
21048
  return { JSXOpeningElement(node) {
21363
- if (resolveJsxElementType(node) !== "img") return;
21049
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
21364
21050
  if (isGeneratedImageRenderContext(context, node)) return;
21365
21051
  const programRoot = findProgramRoot(node);
21366
21052
  if (programRoot && hasEmailTemplateImport(programRoot)) return;
@@ -21407,8 +21093,8 @@ const nextjsNoPolyfillScript = defineRule({
21407
21093
  severity: "warn",
21408
21094
  recommendation: "Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically",
21409
21095
  create: (context) => ({ JSXOpeningElement(node) {
21410
- const elementName = resolveJsxElementType(node);
21411
- if (elementName !== "script" && elementName !== "Script") return;
21096
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
21097
+ if (node.name.name !== "script" && node.name.name !== "Script") return;
21412
21098
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21413
21099
  if (!srcAttribute?.value) return;
21414
21100
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
@@ -23216,15 +22902,10 @@ const isProp = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
23216
22902
  if (!declaringNode) return false;
23217
22903
  return isReactFunctionalComponent(declaringNode) && !isReactFunctionalHOC(analysis, declaringNode) || isCustomHook(declaringNode);
23218
22904
  }));
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
22905
  const isWholePropsObjectReference = (analysis, ref) => isProp(analysis, ref) && Boolean(ref.resolved?.defs.some((def) => {
23226
22906
  if (def.type !== "Parameter") return false;
23227
- return isWholePropsParameterBinding(def.name);
22907
+ const bindingParent = def.name.parent;
22908
+ return isFunctionLike$1(bindingParent);
23228
22909
  }));
23229
22910
  const isIdentifierOrMemberExpression = (node) => isNodeOfType(node, "Identifier") || isNodeOfType(node, "MemberExpression");
23230
22911
  const isPropAlias = (analysis, ref) => {
@@ -26221,56 +25902,6 @@ const noBarrelImport = defineRule({
26221
25902
  }
26222
25903
  });
26223
25904
  //#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
25905
  //#region src/plugin/utils/function-contains-react-render-output.ts
26275
25906
  const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
26276
25907
  "FunctionDeclaration",
@@ -26290,13 +25921,12 @@ const isCallArgumentFunctionExpression = (node) => {
26290
25921
  return parent.arguments.some((argumentNode) => argumentNode === node);
26291
25922
  };
26292
25923
  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
25924
  const containsRenderOutput$1 = (rootNode, scopes) => {
26295
25925
  let hasRenderOutput = false;
26296
25926
  walkAst(rootNode, (node) => {
26297
25927
  if (hasRenderOutput) return false;
26298
25928
  if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
26299
- if (isRenderOutputExpression(node, scopes)) {
25929
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
26300
25930
  hasRenderOutput = true;
26301
25931
  return false;
26302
25932
  }
@@ -26307,7 +25937,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
26307
25937
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
26308
25938
  const cachedEntry = renderOutputCache.get(functionNode);
26309
25939
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
26310
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
25940
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
26311
25941
  renderOutputCache.set(functionNode, {
26312
25942
  scopes,
26313
25943
  hasRenderOutput
@@ -26315,9 +25945,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
26315
25945
  return hasRenderOutput;
26316
25946
  };
26317
25947
  //#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
25948
  //#region src/plugin/utils/is-component-declaration.ts
26322
25949
  const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
26323
25950
  //#endregion
@@ -28775,7 +28402,7 @@ const noDisabledZoom = defineRule({
28775
28402
  category: "Accessibility",
28776
28403
  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
28404
  create: (context) => ({ JSXOpeningElement(node) {
28778
- if (resolveJsxElementType(node) !== "meta") return;
28405
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "meta") return;
28779
28406
  const nameAttr = findJsxAttribute(node.attributes ?? [], "name");
28780
28407
  if (!nameAttr?.value) return;
28781
28408
  if ((isNodeOfType(nameAttr.value, "Literal") ? nameAttr.value.value : null) !== "viewport") return;
@@ -29165,7 +28792,7 @@ const findTopLevelEffectCalls = (componentBody) => {
29165
28792
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
29166
28793
  for (const statement of componentBody.body ?? []) {
29167
28794
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
29168
- const expression = unwrapDiscardedExpression(statement);
28795
+ const expression = statement.expression;
29169
28796
  if (!isNodeOfType(expression, "CallExpression")) continue;
29170
28797
  if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
29171
28798
  effectCalls.push(expression);
@@ -31947,7 +31574,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
31947
31574
  severity: "warn",
31948
31575
  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
31576
  create: (context) => ({ JSXOpeningElement(node) {
31950
- if (resolveJsxElementType(node) !== "img") return;
31577
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
31951
31578
  const loadingAttribute = hasJsxPropIgnoreCase(node.attributes, "loading");
31952
31579
  if (!loadingAttribute || getJsxPropStringValue(loadingAttribute)?.toLowerCase() !== "lazy") return;
31953
31580
  const fetchPriorityAttribute = hasJsxPropIgnoreCase(node.attributes, "fetchPriority");
@@ -32188,7 +31815,7 @@ const noIndeterminateAttribute = defineRule({
32188
31815
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
32189
31816
  return {
32190
31817
  JSXOpeningElement(node) {
32191
- if (resolveJsxElementType(node) !== "input") return;
31818
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
32192
31819
  let typeAttribute = null;
32193
31820
  let typeAttributeIndex = null;
32194
31821
  let indeterminateAttribute = null;
@@ -32367,12 +31994,11 @@ const isMemoCall = (node) => {
32367
31994
  };
32368
31995
  const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
32369
31996
  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";
31997
+ const isInlineReference = (node) => {
31998
+ 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";
31999
+ if (isNodeOfType(node, "ObjectExpression")) return "objects";
32000
+ if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
32001
+ if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
32376
32002
  return null;
32377
32003
  };
32378
32004
  const noInlinePropOnMemoComponent = defineRule({
@@ -32402,7 +32028,7 @@ const noInlinePropOnMemoComponent = defineRule({
32402
32028
  let elementName = null;
32403
32029
  if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
32404
32030
  if (!elementName || !memoizedComponentNames.has(elementName)) return;
32405
- const propType = isInlineReference(node.value.expression, context.scopes);
32031
+ const propType = isInlineReference(node.value.expression);
32406
32032
  if (propType) context.report({
32407
32033
  node: node.value.expression,
32408
32034
  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 +32887,11 @@ const noManyBooleanProps = defineRule({
33261
32887
  };
33262
32888
  const checkComponent = (functionNode, param, body, componentName, reportNode) => {
33263
32889
  if (!param) return;
33264
- const propsBinding = resolveFirstArgumentBinding(param);
33265
- if (!propsBinding) return;
33266
32890
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
33267
- if (isNodeOfType(propsBinding, "ObjectPattern")) {
32891
+ if (isNodeOfType(param, "ObjectPattern")) {
33268
32892
  const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
33269
32893
  const booleanLikePropNames = [];
33270
- for (const property of propsBinding.properties ?? []) {
32894
+ for (const property of param.properties ?? []) {
33271
32895
  if (!isNodeOfType(property, "Property")) continue;
33272
32896
  const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
33273
32897
  if (!keyName) continue;
@@ -33278,7 +32902,7 @@ const noManyBooleanProps = defineRule({
33278
32902
  reportIfMany(booleanLikePropNames, componentName, reportNode);
33279
32903
  return;
33280
32904
  }
33281
- if (isNodeOfType(propsBinding, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, propsBinding.name)], componentName, reportNode);
32905
+ if (isNodeOfType(param, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, param.name)], componentName, reportNode);
33282
32906
  };
33283
32907
  return {
33284
32908
  FunctionDeclaration(node) {
@@ -33400,7 +33024,7 @@ const noMirrorPropEffect = defineRule({
33400
33024
  if (mirrorBindings.length === 0) return;
33401
33025
  for (const statement of componentBody.body ?? []) {
33402
33026
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
33403
- const effectCall = unwrapDiscardedExpression(statement);
33027
+ const effectCall = statement.expression;
33404
33028
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
33405
33029
  if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
33406
33030
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
@@ -33414,7 +33038,7 @@ const noMirrorPropEffect = defineRule({
33414
33038
  const bodyStatements = getCallbackStatements(callback);
33415
33039
  if (bodyStatements.length !== 1) continue;
33416
33040
  const onlyStatement = bodyStatements[0];
33417
- const expression = unwrapDiscardedExpression(onlyStatement);
33041
+ const expression = isNodeOfType(onlyStatement, "ExpressionStatement") ? onlyStatement.expression : onlyStatement;
33418
33042
  if (!isNodeOfType(expression, "CallExpression")) continue;
33419
33043
  if (!isNodeOfType(expression.callee, "Identifier")) continue;
33420
33044
  if (!isSetterIdentifier(expression.callee.name)) continue;
@@ -36030,7 +35654,7 @@ const noPreventDefault = defineRule({
36030
35654
  const isServerActionsFramework = hasCapability(context.settings, "server-actions");
36031
35655
  const formMessage = isServerActionsFramework ? FORM_MESSAGE_SERVER_CAPABLE : FORM_MESSAGE_GENERIC;
36032
35656
  return { JSXOpeningElement(node) {
36033
- const elementName = resolveJsxElementType(node);
35657
+ const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
36034
35658
  if (!elementName) return;
36035
35659
  const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
36036
35660
  if (!targetEventProps) return;
@@ -37515,7 +37139,7 @@ const isNonSettlingSetterArgument = (setterCall, stateName) => {
37515
37139
  return expressionReadsStateValue(argument, stateName);
37516
37140
  };
37517
37141
  const getUnconditionalSetterCall = (statement, setterNames) => {
37518
- const expression = unwrapDiscardedExpression(statement);
37142
+ const expression = stripParenExpression(isNodeOfType(statement, "ExpressionStatement") ? statement.expression : statement);
37519
37143
  if (!isNodeOfType(expression, "CallExpression")) return null;
37520
37144
  if (!isNodeOfType(expression.callee, "Identifier")) return null;
37521
37145
  if (!setterNames.has(expression.callee.name)) return null;
@@ -37864,7 +37488,7 @@ const noSelfUpdatingEffect = defineRule({
37864
37488
  const setterNames = new Set(setterNameToStateName.keys());
37865
37489
  for (const statement of functionBody.body ?? []) {
37866
37490
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
37867
- const effectCall = unwrapDiscardedExpression(statement);
37491
+ const effectCall = statement.expression;
37868
37492
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
37869
37493
  if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
37870
37494
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
@@ -38494,10 +38118,9 @@ const noStringFalseOnBooleanAttribute = defineRule({
38494
38118
  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
38119
  create: (context) => ({ JSXOpeningElement(node) {
38496
38120
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
38497
- const elementName = resolveJsxElementType(node);
38498
- const firstCharacter = elementName.charCodeAt(0);
38121
+ const firstCharacter = node.name.name.charCodeAt(0);
38499
38122
  if (firstCharacter < 97 || firstCharacter > 122) return;
38500
- if (elementName.includes("-")) return;
38123
+ if (node.name.name.includes("-")) return;
38501
38124
  for (const attribute of node.attributes) {
38502
38125
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
38503
38126
  if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
@@ -38885,7 +38508,8 @@ const noUncontrolledInput = defineRule({
38885
38508
  const undefinedInitialStateNames = isNodeOfType(componentBody, "BlockStatement") ? collectUndefinedInitialStateNames(componentBody) : /* @__PURE__ */ new Set();
38886
38509
  walkAst(componentBody, (child) => {
38887
38510
  if (!isNodeOfType(child, "JSXOpeningElement")) return;
38888
- const tagName = resolveJsxElementType(child);
38511
+ if (!isNodeOfType(child.name, "JSXIdentifier")) return;
38512
+ const tagName = child.name.name;
38889
38513
  if (!UNCONTROLLED_INPUT_TAGS.has(tagName)) return;
38890
38514
  const attributes = child.attributes ?? [];
38891
38515
  if (hasJsxSpreadAttribute(attributes)) return;
@@ -38946,7 +38570,7 @@ const noUndeferredThirdParty = defineRule({
38946
38570
  severity: "warn",
38947
38571
  recommendation: "Use `next/script` with `strategy=\"lazyOnload\"`, or add the `defer` attribute.",
38948
38572
  create: (context) => ({ JSXOpeningElement(node) {
38949
- if (resolveJsxElementType(node) !== "script") return;
38573
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "script") return;
38950
38574
  const attributes = node.attributes ?? [];
38951
38575
  const srcAttribute = findJsxAttribute(attributes, "src");
38952
38576
  if (!srcAttribute) return;
@@ -40161,7 +39785,7 @@ const noUnknownProperty = defineRule({
40161
39785
  }
40162
39786
  if (fileIsNonReactJsx) return;
40163
39787
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
40164
- const elementType = resolveJsxElementType(node);
39788
+ const elementType = node.name.name;
40165
39789
  const firstCharacter = elementType.charCodeAt(0);
40166
39790
  if (!(firstCharacter >= 97 && firstCharacter <= 122) || elementType === "fbt" || elementType === "fbs") return;
40167
39791
  let isValidHtmlTag = isKnownDomTag(elementType);
@@ -40339,21 +39963,20 @@ const expressionContainsJsxOrCreateElement = (root) => {
40339
39963
  });
40340
39964
  return found;
40341
39965
  };
40342
- const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
40343
39966
  const isReactClassComponent = (classNode) => {
40344
39967
  if (isEs6Component(classNode)) return true;
40345
39968
  return expressionContainsJsxOrCreateElement(classNode);
40346
39969
  };
40347
- const findEnclosingComponent = (node, scopes) => {
39970
+ const findEnclosingComponent = (node) => {
40348
39971
  let walker = node.parent;
40349
39972
  while (walker) {
40350
39973
  if (isFunctionLike$1(walker)) {
40351
39974
  const componentName = inferFunctionLikeName(walker);
40352
- if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
39975
+ if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
40353
39976
  component: walker,
40354
39977
  name: componentName
40355
39978
  };
40356
- if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
39979
+ if (!componentName && expressionContainsJsxOrCreateElement(walker) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40357
39980
  component: walker,
40358
39981
  name: null
40359
39982
  };
@@ -40592,7 +40215,7 @@ const noUnstableNestedComponents = defineRule({
40592
40215
  if (renderPropRegex.test(propInfo.propName)) return;
40593
40216
  if (settings.allowAsProps) return;
40594
40217
  }
40595
- const enclosing = findEnclosingComponent(candidateNode, context.scopes);
40218
+ const enclosing = findEnclosingComponent(candidateNode);
40596
40219
  if (!enclosing) return;
40597
40220
  const gatedName = propInfo ? null : requiredInstantiationName;
40598
40221
  queuedReports.push({
@@ -40603,7 +40226,7 @@ const noUnstableNestedComponents = defineRule({
40603
40226
  });
40604
40227
  };
40605
40228
  const checkFunctionLike = (node) => {
40606
- if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
40229
+ if (!expressionContainsJsxOrCreateElement(node)) return;
40607
40230
  const inferredName = inferFunctionLikeName(node);
40608
40231
  const propInfo = isComponentDeclaredInProp(node);
40609
40232
  const isObjectCallback = isObjectCallbackCandidate(node);
@@ -41863,7 +41486,7 @@ const preactPreferOndblclick = defineRule({
41863
41486
  recommendation: "Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",
41864
41487
  create: (context) => ({ JSXOpeningElement(node) {
41865
41488
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
41866
- const tagName = resolveJsxElementType(node);
41489
+ const tagName = node.name.name;
41867
41490
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
41868
41491
  const onDoubleClickAttribute = findJsxAttribute(node.attributes, "onDoubleClick");
41869
41492
  if (!onDoubleClickAttribute) return;
@@ -41883,7 +41506,7 @@ const COMPAT_EXEMPT_INPUT_TYPES = new Set([
41883
41506
  ]);
41884
41507
  const isTextLikeInput = (openingElement) => {
41885
41508
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
41886
- const tagName = resolveJsxElementType(openingElement);
41509
+ const tagName = openingElement.name.name;
41887
41510
  if (tagName === "textarea") return true;
41888
41511
  if (tagName !== "input") return false;
41889
41512
  const typeAttribute = findJsxAttribute(openingElement.attributes, "type");
@@ -42040,9 +41663,8 @@ const CROSS_CUTTING_STATE_BOOLEAN_NAMES = new Set([
42040
41663
  ]);
42041
41664
  const collectBooleanPropBindings = (param) => {
42042
41665
  const bindings = /* @__PURE__ */ new Set();
42043
- const propsBinding = resolveFirstArgumentBinding(param);
42044
- if (!isNodeOfType(propsBinding, "ObjectPattern")) return bindings;
42045
- for (const property of propsBinding.properties ?? []) {
41666
+ if (!param || !isNodeOfType(param, "ObjectPattern")) return bindings;
41667
+ for (const property of param.properties ?? []) {
42046
41668
  if (!isNodeOfType(property, "Property")) continue;
42047
41669
  if (property.computed) continue;
42048
41670
  if (!isNodeOfType(property.key, "Identifier")) continue;
@@ -42303,7 +41925,7 @@ const preferHtmlDialog = defineRule({
42303
41925
  },
42304
41926
  JSXOpeningElement(node) {
42305
41927
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
42306
- const tagName = resolveJsxElementType(node);
41928
+ const tagName = node.name.name;
42307
41929
  if (tagName === "dialog") return;
42308
41930
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
42309
41931
  if (!HTML_TAGS.has(tagName)) return;
@@ -43850,13 +43472,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
43850
43472
  }
43851
43473
  return null;
43852
43474
  };
43853
- const resolveHookNameFromInitializer = (initializer, scopes) => {
43475
+ const resolveHookNameFromInitializer = (initializer) => {
43854
43476
  if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
43855
43477
  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);
43478
+ const binding = findVariableInitializer(initializer, initializer.name);
43479
+ if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
43480
+ if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
43481
+ return resolveTanstackQueryHookName(binding.initializer);
43860
43482
  };
43861
43483
  const queryNoRestDestructuring = defineRule({
43862
43484
  id: "query-no-rest-destructuring",
@@ -43869,7 +43491,7 @@ const queryNoRestDestructuring = defineRule({
43869
43491
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
43870
43492
  if (!node.init) return;
43871
43493
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
43872
- const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
43494
+ const hookName = resolveHookNameFromInitializer(node.init);
43873
43495
  if (!hookName) return;
43874
43496
  context.report({
43875
43497
  node: node.id,
@@ -44340,7 +43962,7 @@ const renderingAnimateSvgWrapper = defineRule({
44340
43962
  severity: "warn",
44341
43963
  recommendation: "Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.",
44342
43964
  create: (context) => ({ JSXOpeningElement(node) {
44343
- if (resolveJsxElementType(node) !== "svg") return;
43965
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "svg") return;
44344
43966
  if (node.attributes?.some((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && MOTION_ANIMATE_PROPS.has(attribute.name.name))) context.report({
44345
43967
  node,
44346
43968
  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 +44846,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
45224
44846
  "useParams",
45225
44847
  "usePathname"
45226
44848
  ]);
45227
- const findHookCallBindings = (componentBody, scopes) => {
44849
+ const findHookCallBindings = (componentBody) => {
45228
44850
  const bindings = [];
45229
44851
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
45230
44852
  for (const statement of componentBody.body ?? []) {
@@ -45235,10 +44857,8 @@ const findHookCallBindings = (componentBody, scopes) => {
45235
44857
  const callee = declarator.init.callee;
45236
44858
  if (!isNodeOfType(callee, "Identifier")) continue;
45237
44859
  if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
45238
- const valueSymbol = scopes.symbolFor(declarator.id);
45239
- if (!valueSymbol) continue;
45240
44860
  bindings.push({
45241
- valueSymbol,
44861
+ valueName: declarator.id.name,
45242
44862
  hookName: callee.name,
45243
44863
  declarator
45244
44864
  });
@@ -45246,49 +44866,6 @@ const findHookCallBindings = (componentBody, scopes) => {
45246
44866
  }
45247
44867
  return bindings;
45248
44868
  };
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
44869
  const rerenderDeferReadsHook = defineRule({
45293
44870
  id: "rerender-defer-reads-hook",
45294
44871
  title: "URL hook value only read in handlers",
@@ -45299,13 +44876,15 @@ const rerenderDeferReadsHook = defineRule({
45299
44876
  create: (context) => {
45300
44877
  const checkComponent = (componentBody) => {
45301
44878
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
45302
- const bindings = findHookCallBindings(componentBody, context.scopes);
44879
+ const bindings = findHookCallBindings(componentBody);
45303
44880
  if (bindings.length === 0) return;
45304
44881
  const handlerBindingNames = collectHandlerBindingNames(componentBody);
45305
44882
  for (const binding of bindings) {
45306
- const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
45307
44883
  const referenceLocations = [];
45308
- for (const symbol of symbols) for (const reference of symbol.references) if (!aliasSourceIdentifiers.has(reference.identifier)) referenceLocations.push(reference.identifier);
44884
+ walkAst(componentBody, (child) => {
44885
+ if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
44886
+ if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
44887
+ });
45309
44888
  if (referenceLocations.length === 0) continue;
45310
44889
  if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
45311
44890
  context.report({
@@ -45676,10 +45255,14 @@ const rerenderLazyStateInit = defineRule({
45676
45255
  //#endregion
45677
45256
  //#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
45678
45257
  const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
45679
- const callbackReturnsJsx = (callback, scopes) => {
45258
+ const callbackReturnsJsx = (callback) => {
45680
45259
  if (!callback) return false;
45681
45260
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
45682
- return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
45261
+ const body = callback.body;
45262
+ if (isJsxExpression(body)) return true;
45263
+ if (!isNodeOfType(body, "BlockStatement")) return false;
45264
+ for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
45265
+ return false;
45683
45266
  };
45684
45267
  const returnArgumentUsesAnyName = (returnStatement, names) => {
45685
45268
  if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
@@ -45757,7 +45340,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
45757
45340
  if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
45758
45341
  for (const declarator of stmt.declarations ?? []) {
45759
45342
  const init = declarator.init;
45760
- if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
45343
+ if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
45761
45344
  memoNode = declarator;
45762
45345
  callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
45763
45346
  if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
@@ -45823,11 +45406,8 @@ const collectFromObjectPattern = (pattern, bindings) => {
45823
45406
  const collectDefaultedEmptyBindings = (functionNode) => {
45824
45407
  const bindings = /* @__PURE__ */ new Map();
45825
45408
  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]);
45409
+ for (const param of params) collectFromObjectPattern(param, bindings);
45410
+ const propsParam = params[0];
45831
45411
  const body = functionNode.body;
45832
45412
  if (!propsParam || !isNodeOfType(propsParam, "Identifier") || !body) return bindings;
45833
45413
  if (!isNodeOfType(body, "BlockStatement")) return bindings;
@@ -46333,8 +45913,8 @@ const rnAnimationReactionAsDerived = defineRule({
46333
45913
  if (statements.length !== 1) return;
46334
45914
  const onlyStatement = statements[0];
46335
45915
  if (!isNodeOfType(onlyStatement, "ExpressionStatement")) return;
46336
- singleAssignment = unwrapDiscardedExpression(onlyStatement);
46337
- } else if (body) singleAssignment = unwrapDiscardedExpression(body);
45916
+ singleAssignment = onlyStatement.expression;
45917
+ } else if (body) singleAssignment = body;
46338
45918
  if (!singleAssignment) return;
46339
45919
  const isValueAssignment = isNodeOfType(singleAssignment, "AssignmentExpression") && isNodeOfType(singleAssignment.left, "MemberExpression") && isNodeOfType(singleAssignment.left.object, "Identifier") && isNodeOfType(singleAssignment.left.property, "Identifier") && singleAssignment.left.property.name === "value";
46340
45920
  const isSetCall = isNodeOfType(singleAssignment, "CallExpression") && isNodeOfType(singleAssignment.callee, "MemberExpression") && isNodeOfType(singleAssignment.callee.property, "Identifier") && singleAssignment.callee.property.name === "set" && (singleAssignment.arguments?.length ?? 0) === 1;
@@ -53254,8 +52834,7 @@ const serverCacheWithObjectLiteral = defineRule({
53254
52834
  if (!isNodeOfType(node.callee, "Identifier")) return;
53255
52835
  if (!cachedFunctionNames.has(node.callee.name)) return;
53256
52836
  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;
52837
+ if (!isNodeOfType(firstArg, "ObjectExpression")) return;
53259
52838
  context.report({
53260
52839
  node,
53261
52840
  message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
@@ -53942,6 +53521,10 @@ const isInvalidStyleExpression = (expression) => {
53942
53521
  }
53943
53522
  return false;
53944
53523
  };
53524
+ const getJsxOpeningElementName = (node) => {
53525
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
53526
+ return null;
53527
+ };
53945
53528
  const stylePropObject = defineRule({
53946
53529
  id: "style-prop-object",
53947
53530
  title: "Style prop is not an object",
@@ -53953,8 +53536,7 @@ const stylePropObject = defineRule({
53953
53536
  const allowSet = new Set(allow);
53954
53537
  return {
53955
53538
  JSXOpeningElement(node) {
53956
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
53957
- const elementName = resolveJsxElementType(node);
53539
+ const elementName = getJsxOpeningElementName(node);
53958
53540
  if (elementName && allowSet.has(elementName)) return;
53959
53541
  if (elementName) {
53960
53542
  const firstCharCode = elementName.charCodeAt(0);
@@ -54659,7 +54241,7 @@ const tanstackStartNoAnchorElement = defineRule({
54659
54241
  create: (context) => {
54660
54242
  if (!isInProjectDirectory(context, "routes")) return {};
54661
54243
  return { JSXOpeningElement(node) {
54662
- if (resolveJsxElementType(node) !== "a") return;
54244
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
54663
54245
  const attributes = node.attributes ?? [];
54664
54246
  const hrefAttribute = findJsxAttribute(attributes, "href");
54665
54247
  if (!hrefAttribute?.value) return;
@@ -55280,7 +54862,8 @@ const voidDomElementsNoChildren = defineRule({
55280
54862
  create: (context) => ({
55281
54863
  JSXElement(node) {
55282
54864
  const openingElement = node.openingElement;
55283
- const tagName = resolveJsxElementType(openingElement);
54865
+ if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return;
54866
+ const tagName = openingElement.name.name;
55284
54867
  if (!VOID_DOM_ELEMENTS.has(tagName)) return;
55285
54868
  const hasChildrenContent = node.children.some(isMeaningfulJsxChild);
55286
54869
  const hasChildrenLikeProp = findChildrenLikePropName(openingElement.attributes);