oxlint-plugin-react-doctor 0.7.6-dev.c6f996e → 0.7.6-dev.e026a23

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 +125 -349
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1518,37 +1518,6 @@ const hasJsxPropIgnoreCase = (attributes, targetProp) => {
1518
1518
  }
1519
1519
  };
1520
1520
  //#endregion
1521
- //#region src/plugin/utils/is-uppercase-name.ts
1522
- const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
1523
- //#endregion
1524
- //#region src/plugin/utils/resolve-jsx-element-type.ts
1525
- const resolveConstantStringBinding = (name) => {
1526
- const binding = findVariableInitializer(name, name.name);
1527
- if (!binding?.initializer) return null;
1528
- const declarator = binding.bindingIdentifier.parent;
1529
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
1530
- if (declarator.id !== binding.bindingIdentifier) return null;
1531
- const declaration = declarator.parent;
1532
- if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
1533
- if (declaration.kind !== "const") return null;
1534
- const initializer = stripParenExpression(binding.initializer);
1535
- return isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
1536
- };
1537
- const flattenJsxName$2 = (name) => {
1538
- if (isNodeOfType(name, "JSXIdentifier")) return name.name;
1539
- if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName$2(name.object)}.${name.property.name}`;
1540
- if (isNodeOfType(name, "JSXNamespacedName")) return `${name.namespace.name}:${name.name.name}`;
1541
- return "";
1542
- };
1543
- const resolveJsxElementType = (openingElement) => {
1544
- const name = openingElement.name;
1545
- if (isNodeOfType(name, "JSXIdentifier")) {
1546
- if (!isUppercaseName(name.name)) return name.name;
1547
- return resolveConstantStringBinding(name) ?? name.name;
1548
- }
1549
- return flattenJsxName$2(name);
1550
- };
1551
- //#endregion
1552
1521
  //#region src/plugin/utils/get-element-type.ts
1553
1522
  const EMPTY_JSX_A11Y_SETTINGS = Object.freeze({});
1554
1523
  const jsxA11ySettingsCache = /* @__PURE__ */ new WeakMap();
@@ -1561,8 +1530,14 @@ const readJsxA11ySettings = (settings) => {
1561
1530
  jsxA11ySettingsCache.set(settings, a11ySettings);
1562
1531
  return a11ySettings;
1563
1532
  };
1533
+ const flattenJsxName$2 = (name) => {
1534
+ if (isNodeOfType(name, "JSXIdentifier")) return name.name;
1535
+ if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName$2(name.object)}.${name.property.name}`;
1536
+ if (isNodeOfType(name, "JSXNamespacedName")) return `${name.namespace.name}:${name.name.name}`;
1537
+ return "";
1538
+ };
1564
1539
  const computeElementType = (openingElement, a11ySettings) => {
1565
- const baseName = resolveJsxElementType(openingElement);
1540
+ const baseName = flattenJsxName$2(openingElement.name);
1566
1541
  if (a11ySettings.polymorphicPropName) {
1567
1542
  const polymorphicAttribute = hasJsxPropIgnoreCase(openingElement.attributes, a11ySettings.polymorphicPropName);
1568
1543
  if (polymorphicAttribute) {
@@ -2397,9 +2372,8 @@ const anchorIsValid = defineRule({
2397
2372
  const isTestlikeFile = isTestlikeFilename(context.filename);
2398
2373
  return { JSXOpeningElement(node) {
2399
2374
  if (isTestlikeFile) return;
2400
- const tag = getElementType(node, context.settings);
2401
- if (!fileHasJsxA11ySettings && tag !== "a") return;
2402
- if (tag !== "a") return;
2375
+ if (!fileHasJsxA11ySettings && (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a")) return;
2376
+ if (getElementType(node, context.settings) !== "a") return;
2403
2377
  let hrefAttribute;
2404
2378
  for (const attributeName of settings.hrefAttributeNames) {
2405
2379
  hrefAttribute = hasJsxPropIgnoreCase(node.attributes, attributeName);
@@ -5754,7 +5728,7 @@ const buttonHasType = defineRule({
5754
5728
  return {
5755
5729
  JSXOpeningElement(node) {
5756
5730
  if (isTestlikeFile) return;
5757
- if (resolveJsxElementType(node) !== "button") return;
5731
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "button") return;
5758
5732
  const typeAttr = hasJsxPropIgnoreCase(node.attributes, "type");
5759
5733
  if (!typeAttr) {
5760
5734
  if (hasJsxSpreadAttribute$1(node.attributes)) {
@@ -5944,7 +5918,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5944
5918
  };
5945
5919
  return {
5946
5920
  JSXOpeningElement(node) {
5947
- if (resolveJsxElementType(node) !== "input") return;
5921
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
5948
5922
  reportFromPresence(collectFromJsxAttributes(node.attributes));
5949
5923
  },
5950
5924
  CallExpression(node) {
@@ -6021,7 +5995,7 @@ const isPureEventBlockerHandler = (attribute) => {
6021
5995
  //#endregion
6022
5996
  //#region src/plugin/utils/resolve-const-identifier-alias.ts
6023
5997
  const resolveConstIdentifierAlias = (identifier, scopes) => {
6024
- if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
5998
+ if (!isNodeOfType(identifier, "Identifier")) return null;
6025
5999
  const visitedSymbolIds = /* @__PURE__ */ new Set();
6026
6000
  let symbol = scopes.symbolFor(identifier);
6027
6001
  while (symbol?.kind === "const") {
@@ -6296,131 +6270,6 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
6296
6270
  return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6297
6271
  };
6298
6272
  //#endregion
6299
- //#region src/plugin/utils/collect-safely-validated-local-storage-keys.ts
6300
- const isLocalStorageCall = (node, methodName) => {
6301
- if (!isNodeOfType(node, "CallExpression")) return false;
6302
- const callee = stripParenExpression(node.callee);
6303
- if (!isNodeOfType(callee, "MemberExpression")) return false;
6304
- const receiver = stripParenExpression(callee.object);
6305
- return isNodeOfType(receiver, "Identifier") && receiver.name === "localStorage" && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6306
- };
6307
- const catchReturnsFallback = (tryStatement) => {
6308
- const handler = tryStatement.handler;
6309
- if (!handler) return false;
6310
- let hasReturn = false;
6311
- let hasThrow = false;
6312
- walkAst(handler.body, (child) => {
6313
- if (isFunctionLike$1(child)) return false;
6314
- if (isNodeOfType(child, "ReturnStatement")) hasReturn = true;
6315
- if (isNodeOfType(child, "ThrowStatement")) hasThrow = true;
6316
- });
6317
- return hasReturn && !hasThrow;
6318
- };
6319
- const resolveValidatorFunction = (callee, scopes) => {
6320
- const unwrappedCallee = stripParenExpression(callee);
6321
- if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
6322
- const symbol = scopes.symbolFor(unwrappedCallee);
6323
- if (!symbol) return null;
6324
- const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
6325
- return isFunctionLike$1(candidate) ? candidate : null;
6326
- };
6327
- const validatorChecksPayloadProperties = (validatorFunction, scopes) => {
6328
- if (!isFunctionLike$1(validatorFunction)) return false;
6329
- const firstParameter = validatorFunction.params?.[0];
6330
- if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
6331
- const parameterSymbol = scopes.symbolFor(firstParameter);
6332
- if (!parameterSymbol) return false;
6333
- const payloadSymbolIds = new Set([parameterSymbol.id]);
6334
- let hasPropertyTypeCheck = false;
6335
- walkAst(validatorFunction.body, (child) => {
6336
- if (isFunctionLike$1(child)) return false;
6337
- if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init) {
6338
- const initializer = stripParenExpression(child.init);
6339
- if (isNodeOfType(initializer, "Identifier")) {
6340
- const initializerSymbol = scopes.symbolFor(initializer);
6341
- if (initializerSymbol && payloadSymbolIds.has(initializerSymbol.id)) {
6342
- const aliasSymbol = scopes.symbolFor(child.id);
6343
- if (aliasSymbol) payloadSymbolIds.add(aliasSymbol.id);
6344
- }
6345
- }
6346
- }
6347
- if (!isNodeOfType(child, "UnaryExpression") || child.operator !== "typeof") return;
6348
- const checkedValue = stripParenExpression(child.argument);
6349
- if (!isNodeOfType(checkedValue, "MemberExpression")) return;
6350
- const receiver = stripParenExpression(checkedValue.object);
6351
- if (!isNodeOfType(receiver, "Identifier")) return;
6352
- const receiverSymbol = scopes.symbolFor(receiver);
6353
- if (receiverSymbol && payloadSymbolIds.has(receiverSymbol.id)) hasPropertyTypeCheck = true;
6354
- });
6355
- return hasPropertyTypeCheck;
6356
- };
6357
- const incrementCount = (counts, keyValue) => {
6358
- counts.set(keyValue, (counts.get(keyValue) ?? 0) + 1);
6359
- };
6360
- const isReturnedExpression = (expression) => {
6361
- const parent = expression.parent;
6362
- return Boolean(parent && isNodeOfType(parent, "ReturnStatement") && parent.argument === expression);
6363
- };
6364
- const collectSafelyValidatedLocalStorageKeys = ({ programNode, scopes, resolveKey }) => {
6365
- const readCountsByKey = /* @__PURE__ */ new Map();
6366
- const safeReadCountsByKey = /* @__PURE__ */ new Map();
6367
- const safeReadSymbolIds = /* @__PURE__ */ new Set();
6368
- walkAst(programNode, (child) => {
6369
- if (!isNodeOfType(child, "CallExpression") || !isLocalStorageCall(child, "getItem")) return;
6370
- const keyArgument = child.arguments?.[0];
6371
- if (!keyArgument) return;
6372
- const keyValue = resolveKey(keyArgument);
6373
- if (keyValue !== null) incrementCount(readCountsByKey, keyValue);
6374
- });
6375
- walkAst(programNode, (child) => {
6376
- if (!isNodeOfType(child, "TryStatement") || !catchReturnsFallback(child)) return;
6377
- const rawValueKeys = /* @__PURE__ */ new Map();
6378
- const parsedValueSources = /* @__PURE__ */ new Map();
6379
- walkAst(child.block, (tryChild) => {
6380
- if (isFunctionLike$1(tryChild)) return false;
6381
- if (isNodeOfType(tryChild, "VariableDeclarator") && isNodeOfType(tryChild.id, "Identifier") && tryChild.init) {
6382
- const initializer = stripParenExpression(tryChild.init);
6383
- if (isNodeOfType(initializer, "CallExpression") && isLocalStorageCall(initializer, "getItem")) {
6384
- const keyArgument = initializer.arguments?.[0];
6385
- const bindingSymbol = scopes.symbolFor(tryChild.id);
6386
- if (keyArgument && bindingSymbol) {
6387
- const keyValue = resolveKey(keyArgument);
6388
- if (keyValue !== null) rawValueKeys.set(bindingSymbol.id, keyValue);
6389
- }
6390
- }
6391
- if (isNodeOfType(initializer, "CallExpression") && isGlobalMethodCall(initializer, "JSON", "parse")) {
6392
- const rawValueArgument = initializer.arguments?.[0];
6393
- const parsedValueSymbol = scopes.symbolFor(tryChild.id);
6394
- if (rawValueArgument && isNodeOfType(rawValueArgument, "Identifier") && parsedValueSymbol) {
6395
- const rawValueSymbol = scopes.symbolFor(rawValueArgument);
6396
- if (rawValueSymbol && rawValueKeys.has(rawValueSymbol.id)) parsedValueSources.set(parsedValueSymbol.id, rawValueSymbol.id);
6397
- }
6398
- }
6399
- }
6400
- if (!isNodeOfType(tryChild, "ConditionalExpression") || !isReturnedExpression(tryChild)) return;
6401
- const test = stripParenExpression(tryChild.test);
6402
- if (!isNodeOfType(test, "CallExpression")) return;
6403
- const testedValue = test.arguments?.[0];
6404
- if (!testedValue || !isNodeOfType(testedValue, "Identifier")) return;
6405
- const parsedValueSymbol = scopes.symbolFor(testedValue);
6406
- if (!parsedValueSymbol) return;
6407
- const rawValueSymbolId = parsedValueSources.get(parsedValueSymbol.id);
6408
- if (rawValueSymbolId === void 0) return;
6409
- const returnedValue = stripParenExpression(tryChild.consequent);
6410
- if (!isNodeOfType(returnedValue, "Identifier") || scopes.symbolFor(returnedValue)?.id !== parsedValueSymbol.id) return;
6411
- const validatorFunction = resolveValidatorFunction(test.callee, scopes);
6412
- if (!validatorFunction || !validatorChecksPayloadProperties(validatorFunction, scopes)) return;
6413
- const keyValue = rawValueKeys.get(rawValueSymbolId);
6414
- if (keyValue === void 0 || safeReadSymbolIds.has(rawValueSymbolId)) return;
6415
- safeReadSymbolIds.add(rawValueSymbolId);
6416
- incrementCount(safeReadCountsByKey, keyValue);
6417
- });
6418
- });
6419
- const safeKeys = /* @__PURE__ */ new Set();
6420
- for (const [keyValue, readCount] of readCountsByKey) if (safeReadCountsByKey.get(keyValue) === readCount) safeKeys.add(keyValue);
6421
- return safeKeys;
6422
- };
6423
- //#endregion
6424
6273
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6425
6274
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6426
6275
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
@@ -6442,39 +6291,26 @@ const clientLocalstorageNoVersion = defineRule({
6442
6291
  severity: "warn",
6443
6292
  category: "Correctness",
6444
6293
  recommendation: "Put a version in the storage key (e.g. \"myKey:v1\"). If you change the data shape later, old saved data can be ignored instead of crashing the app.",
6445
- create: (context) => {
6446
- let safelyValidatedStorageKeys = /* @__PURE__ */ new Set();
6447
- return {
6448
- Program(node) {
6449
- safelyValidatedStorageKeys = collectSafelyValidatedLocalStorageKeys({
6450
- programNode: node,
6451
- scopes: context.scopes,
6452
- resolveKey: (keyNode) => resolveStringKey(keyNode, context)
6453
- });
6454
- },
6455
- CallExpression(node) {
6456
- if (!isNodeOfType(node.callee, "MemberExpression")) return;
6457
- const receiver = stripParenExpression(node.callee.object);
6458
- if (!isNodeOfType(receiver, "Identifier")) return;
6459
- if (receiver.name !== "localStorage") return;
6460
- if (!isNodeOfType(node.callee.property, "Identifier")) return;
6461
- if (node.callee.property.name !== "setItem") return;
6462
- const keyArg = node.arguments?.[0];
6463
- if (!keyArg) return;
6464
- const keyValue = resolveStringKey(keyArg, context);
6465
- if (keyValue === null) return;
6466
- if (isVersionedKey(keyValue)) return;
6467
- if (safelyValidatedStorageKeys.has(keyValue)) return;
6468
- const valueArg = node.arguments?.[1];
6469
- if (!valueArg) return;
6470
- if (!isJsonStringifyCall(valueArg)) return;
6471
- context.report({
6472
- node: keyArg,
6473
- message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
6474
- });
6475
- }
6476
- };
6477
- }
6294
+ create: (context) => ({ CallExpression(node) {
6295
+ if (!isNodeOfType(node.callee, "MemberExpression")) return;
6296
+ const receiver = stripParenExpression(node.callee.object);
6297
+ if (!isNodeOfType(receiver, "Identifier")) return;
6298
+ if (receiver.name !== "localStorage") return;
6299
+ if (!isNodeOfType(node.callee.property, "Identifier")) return;
6300
+ if (node.callee.property.name !== "setItem") return;
6301
+ const keyArg = node.arguments?.[0];
6302
+ if (!keyArg) return;
6303
+ const keyValue = resolveStringKey(keyArg, context);
6304
+ if (keyValue === null) return;
6305
+ if (isVersionedKey(keyValue)) return;
6306
+ const valueArg = node.arguments?.[1];
6307
+ if (!valueArg) return;
6308
+ if (!isJsonStringifyCall(valueArg)) return;
6309
+ context.report({
6310
+ node: keyArg,
6311
+ message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
6312
+ });
6313
+ } })
6478
6314
  });
6479
6315
  //#endregion
6480
6316
  //#region src/plugin/rules/client/client-passive-event-listeners.ts
@@ -7597,7 +7433,7 @@ const findJsxAttribute = (attributes, attributeName) => attributes?.find((attrib
7597
7433
  const getOpeningElementTagName = (openingElement) => {
7598
7434
  if (!openingElement) return null;
7599
7435
  if (!isNodeOfType(openingElement, "JSXOpeningElement")) return null;
7600
- if (isNodeOfType(openingElement.name, "JSXIdentifier")) return resolveJsxElementType(openingElement);
7436
+ if (isNodeOfType(openingElement.name, "JSXIdentifier")) return openingElement.name.name;
7601
7437
  if (isNodeOfType(openingElement.name, "JSXMemberExpression")) {
7602
7438
  let cursor = openingElement.name;
7603
7439
  while (isNodeOfType(cursor, "JSXMemberExpression")) cursor = cursor.property;
@@ -7849,7 +7685,7 @@ const dialogHasAccessibleName = defineRule({
7849
7685
  if (isTestlikeFilename(context.filename)) return {};
7850
7686
  return { JSXOpeningElement(node) {
7851
7687
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
7852
- const tagName = resolveJsxElementType(node);
7688
+ const tagName = node.name.name;
7853
7689
  if (tagName[0] !== tagName[0]?.toLowerCase()) return;
7854
7690
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
7855
7691
  const roleValue = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
@@ -12081,7 +11917,7 @@ const forbidDomProps = defineRule({
12081
11917
  return { JSXOpeningElement(node) {
12082
11918
  if (forbidMap.size === 0) return;
12083
11919
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12084
- const elementName = resolveJsxElementType(node);
11920
+ const elementName = node.name.name;
12085
11921
  if (isReactComponentName(elementName)) return;
12086
11922
  for (const attribute of node.attributes) {
12087
11923
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -12159,7 +11995,7 @@ const forbidElements = defineRule({
12159
11995
  return {
12160
11996
  JSXOpeningElement(node) {
12161
11997
  if (forbidMap.size === 0) return;
12162
- const fullName = resolveJsxElementType(node);
11998
+ const fullName = flattenJsxName$1(node.name);
12163
11999
  if (!fullName || !forbidMap.has(fullName)) return;
12164
12000
  context.report({
12165
12001
  node: node.name,
@@ -12521,7 +12357,8 @@ const buildMessage$22 = (childTagName) => `Your users get reshuffled HTML becaus
12521
12357
  const isParagraphElement = (candidate) => {
12522
12358
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12523
12359
  const opening = candidate.openingElement;
12524
- return resolveJsxElementType(opening) === "p";
12360
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12361
+ return opening.name.name === "p";
12525
12362
  };
12526
12363
  const findEnclosingParagraph = (openingElement) => {
12527
12364
  const owningElement = openingElement.parent;
@@ -12542,7 +12379,8 @@ const htmlNoInvalidParagraphChild = defineRule({
12542
12379
  severity: "warn",
12543
12380
  recommendation: "Swap the `<p>` for a `<div>`, or move the child outside the paragraph.",
12544
12381
  create: (context) => ({ JSXOpeningElement(node) {
12545
- const childTagName = resolveJsxElementType(node);
12382
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12383
+ const childTagName = node.name.name;
12546
12384
  if (!BLOCK_LEVEL_ELEMENTS.has(childTagName)) return;
12547
12385
  if (!findEnclosingParagraph(node)) return;
12548
12386
  context.report({
@@ -12573,7 +12411,7 @@ const getHostTagName = (jsxElement) => {
12573
12411
  if (!isNodeOfType(jsxElement, "JSXElement")) return null;
12574
12412
  const opening = jsxElement.openingElement;
12575
12413
  if (!isNodeOfType(opening.name, "JSXIdentifier")) return null;
12576
- const tagName = resolveJsxElementType(opening);
12414
+ const tagName = opening.name.name;
12577
12415
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return null;
12578
12416
  return tagName;
12579
12417
  };
@@ -12603,7 +12441,7 @@ const findClosestHostAncestor = (jsxElement) => {
12603
12441
  if (isNodeOfType(ancestor, "JSXElement")) {
12604
12442
  const opening = ancestor.openingElement;
12605
12443
  if (isNodeOfType(opening.name, "JSXIdentifier")) {
12606
- const ancestorTag = resolveJsxElementType(opening);
12444
+ const ancestorTag = opening.name.name;
12607
12445
  if (ancestorTag.length === 0) {
12608
12446
  previous = ancestor;
12609
12447
  ancestor = ancestor.parent ?? null;
@@ -12685,7 +12523,8 @@ const buildMessage$20 = (tagName) => `Your users get broken clicks, focus & scre
12685
12523
  const isJsxElementWithTagName = (candidate, tagName) => {
12686
12524
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12687
12525
  const opening = candidate.openingElement;
12688
- return resolveJsxElementType(opening) === tagName;
12526
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12527
+ return opening.name.name === tagName;
12689
12528
  };
12690
12529
  const findEnclosingSameTag = (openingElement, tagName) => {
12691
12530
  const owningElement = openingElement.parent;
@@ -12706,7 +12545,8 @@ const htmlNoNestedInteractive = defineRule({
12706
12545
  severity: "warn",
12707
12546
  recommendation: "Move the inner `<a>` or `<button>` so it's a sibling, or change the outer one to a plain `<div>` or `<span>`.",
12708
12547
  create: (context) => ({ JSXOpeningElement(node) {
12709
- const tagName = resolveJsxElementType(node);
12548
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12549
+ const tagName = node.name.name;
12710
12550
  if (tagName !== "a" && tagName !== "button") return;
12711
12551
  if (!findEnclosingSameTag(node, tagName)) return;
12712
12552
  context.report({
@@ -12821,7 +12661,7 @@ const iframeMissingSandbox = defineRule({
12821
12661
  matchByOccurrence: true,
12822
12662
  create: skipNonProductionFiles((context) => ({
12823
12663
  JSXOpeningElement(node) {
12824
- if (resolveJsxElementType(node) !== "iframe") return;
12664
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "iframe") return;
12825
12665
  const sandboxAttr = hasJsxPropIgnoreCase(node.attributes, "sandbox");
12826
12666
  if (!sandboxAttr) {
12827
12667
  const hasExplicitSrc = Boolean(hasJsxPropIgnoreCase(node.attributes, "src"));
@@ -14749,18 +14589,6 @@ const jsHoistRegexp = defineRule({
14749
14589
  }, { treatIteratorCallbacksAsLoops: true })
14750
14590
  });
14751
14591
  //#endregion
14752
- //#region src/plugin/utils/resolve-first-argument-binding.ts
14753
- const resolveFirstArgumentBinding = (firstParameter) => {
14754
- if (!firstParameter) return null;
14755
- if (!isNodeOfType(firstParameter, "RestElement")) return firstParameter;
14756
- if (!isNodeOfType(firstParameter.argument, "ArrayPattern")) return null;
14757
- const elements = firstParameter.argument.elements ?? [];
14758
- if (elements.length !== 1) return null;
14759
- const firstBinding = elements[0];
14760
- if (!firstBinding || isNodeOfType(firstBinding, "RestElement")) return null;
14761
- return firstBinding;
14762
- };
14763
- //#endregion
14764
14592
  //#region src/plugin/rules/js-performance/js-index-maps.ts
14765
14593
  const referencesParameter = (expression, parameterName) => {
14766
14594
  if (!expression) return false;
@@ -14771,7 +14599,7 @@ const referencesParameter = (expression, parameterName) => {
14771
14599
  const isSingleFieldEqualityPredicate = (node) => {
14772
14600
  const callback = node.arguments?.[0];
14773
14601
  if (!isInlineFunctionExpression(callback)) return false;
14774
- const firstParameter = resolveFirstArgumentBinding(callback.params?.[0]);
14602
+ const firstParameter = callback.params?.[0];
14775
14603
  if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
14776
14604
  let predicate = null;
14777
14605
  const body = callback.body;
@@ -16110,21 +15938,14 @@ const jsxFilenameExtension = defineRule({
16110
15938
  });
16111
15939
  //#endregion
16112
15940
  //#region src/plugin/utils/is-jsx-fragment-element.ts
16113
- const isJsxFragmentElement = (node, scopes) => {
15941
+ const isJsxFragmentElement = (node) => {
16114
15942
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
16115
15943
  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
- }
15944
+ if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
16122
15945
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
16123
15946
  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);
15947
+ if (elementName.object.name !== "React") return false;
15948
+ return elementName.property.name === "Fragment";
16128
15949
  }
16129
15950
  return false;
16130
15951
  };
@@ -16150,7 +15971,7 @@ const jsxFragments = defineRule({
16150
15971
  if (mode !== "syntax") return;
16151
15972
  if (!node.closingElement) return;
16152
15973
  const openingElement = node.openingElement;
16153
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
15974
+ if (!isJsxFragmentElement(openingElement)) return;
16154
15975
  if (openingElement.attributes.length > 0) return;
16155
15976
  context.report({
16156
15977
  node: openingElement,
@@ -18831,6 +18652,10 @@ const resolveSettings$29 = (settings) => {
18831
18652
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
18832
18653
  return reactDoctor.jsxNoScriptUrl ?? {};
18833
18654
  };
18655
+ const getElementName = (node) => {
18656
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
18657
+ return null;
18658
+ };
18834
18659
  const isLinkPropForElement = (elementName, attributeName, options) => {
18835
18660
  if (elementName === "a" && attributeName === "href") return true;
18836
18661
  const explicit = options.components?.[elementName];
@@ -18850,8 +18675,7 @@ const jsxNoScriptUrl = defineRule({
18850
18675
  create: (context) => {
18851
18676
  const options = resolveSettings$29(context.settings);
18852
18677
  return { JSXOpeningElement(node) {
18853
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18854
- const elementName = resolveJsxElementType(node);
18678
+ const elementName = getElementName(node);
18855
18679
  if (!elementName) return;
18856
18680
  for (const attribute of node.attributes) {
18857
18681
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -19038,6 +18862,11 @@ const checkTarget = (attributeValue) => {
19038
18862
  alternate: false
19039
18863
  };
19040
18864
  };
18865
+ const getOpeningElementName = (node) => {
18866
+ const name = node.name;
18867
+ if (isNodeOfType(name, "JSXIdentifier")) return name.name;
18868
+ return null;
18869
+ };
19041
18870
  const jsxNoTargetBlank = defineRule({
19042
18871
  id: "jsx-no-target-blank",
19043
18872
  title: "Unsafe target=_blank link",
@@ -19057,8 +18886,7 @@ const jsxNoTargetBlank = defineRule({
19057
18886
  return settings.formComponents.has(tagName);
19058
18887
  };
19059
18888
  return { JSXOpeningElement(node) {
19060
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
19061
- const tagName = resolveJsxElementType(node);
18889
+ const tagName = getOpeningElementName(node);
19062
18890
  if (!tagName) return;
19063
18891
  if (!isLink(tagName) && !isForm(tagName)) return;
19064
18892
  const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
@@ -19291,7 +19119,7 @@ const jsxNoUselessFragment = defineRule({
19291
19119
  return {
19292
19120
  JSXElement(node) {
19293
19121
  const openingElement = node.openingElement;
19294
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
19122
+ if (!isJsxFragmentElement(openingElement)) return;
19295
19123
  if (hasJsxKeyAttribute(openingElement)) return;
19296
19124
  if (checkChildren(node, openingElement, node.children)) return;
19297
19125
  if (isChildOfHtmlElement(node)) context.report({
@@ -20533,6 +20361,9 @@ const hasDirective = (programNode, directive) => {
20533
20361
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20534
20362
  };
20535
20363
  //#endregion
20364
+ //#region src/plugin/utils/is-uppercase-name.ts
20365
+ const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
20366
+ //#endregion
20536
20367
  //#region src/plugin/utils/is-component-assignment.ts
20537
20368
  const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
20538
20369
  //#endregion
@@ -20865,7 +20696,7 @@ const nextjsNoAElement = defineRule({
20865
20696
  severity: "warn",
20866
20697
  recommendation: "`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",
20867
20698
  create: (context) => ({ JSXOpeningElement(node) {
20868
- if (resolveJsxElementType(node) !== "a") return;
20699
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
20869
20700
  const attributes = node.attributes ?? [];
20870
20701
  const downloadAttribute = findJsxAttribute(attributes, "download");
20871
20702
  if (downloadAttribute) {
@@ -21061,7 +20892,7 @@ const nextjsNoCssLink = defineRule({
21061
20892
  severity: "warn",
21062
20893
  recommendation: "Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.",
21063
20894
  create: (context) => ({ JSXOpeningElement(node) {
21064
- if (resolveJsxElementType(node) !== "link") return;
20895
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21065
20896
  const attributes = node.attributes ?? [];
21066
20897
  const relAttribute = findJsxAttribute(attributes, "rel");
21067
20898
  if (!relAttribute?.value) return;
@@ -21169,7 +21000,7 @@ const nextjsNoFontLink = defineRule({
21169
21000
  severity: "warn",
21170
21001
  recommendation: "`import { Inter } from \"next/font/google\"` for self-hosting, zero layout shift, and no render-blocking requests",
21171
21002
  create: (context) => ({ JSXOpeningElement(node) {
21172
- if (resolveJsxElementType(node) !== "link") return;
21003
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21173
21004
  const attributes = node.attributes ?? [];
21174
21005
  const hrefAttribute = findJsxAttribute(attributes, "href");
21175
21006
  if (!hrefAttribute?.value) return;
@@ -21344,7 +21175,7 @@ const nextjsNoImgElement = defineRule({
21344
21175
  create: (context) => {
21345
21176
  if (isGeneratedImageRenderContext(context)) return {};
21346
21177
  return { JSXOpeningElement(node) {
21347
- if (resolveJsxElementType(node) !== "img") return;
21178
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
21348
21179
  if (isGeneratedImageRenderContext(context, node)) return;
21349
21180
  const programRoot = findProgramRoot(node);
21350
21181
  if (programRoot && hasEmailTemplateImport(programRoot)) return;
@@ -21391,8 +21222,8 @@ const nextjsNoPolyfillScript = defineRule({
21391
21222
  severity: "warn",
21392
21223
  recommendation: "Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically",
21393
21224
  create: (context) => ({ JSXOpeningElement(node) {
21394
- const elementName = resolveJsxElementType(node);
21395
- if (elementName !== "script" && elementName !== "Script") return;
21225
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
21226
+ if (node.name.name !== "script" && node.name.name !== "Script") return;
21396
21227
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21397
21228
  if (!srcAttribute?.value) return;
21398
21229
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
@@ -23200,15 +23031,10 @@ const isProp = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
23200
23031
  if (!declaringNode) return false;
23201
23032
  return isReactFunctionalComponent(declaringNode) && !isReactFunctionalHOC(analysis, declaringNode) || isCustomHook(declaringNode);
23202
23033
  }));
23203
- const isWholePropsParameterBinding = (bindingNode) => {
23204
- if (isFunctionLike$1(bindingNode.parent)) return true;
23205
- let declaringFunction = bindingNode.parent;
23206
- while (declaringFunction && !isFunctionLike$1(declaringFunction)) declaringFunction = declaringFunction.parent;
23207
- return Boolean(declaringFunction && resolveFirstArgumentBinding(declaringFunction.params?.[0]) === bindingNode);
23208
- };
23209
23034
  const isWholePropsObjectReference = (analysis, ref) => isProp(analysis, ref) && Boolean(ref.resolved?.defs.some((def) => {
23210
23035
  if (def.type !== "Parameter") return false;
23211
- return isWholePropsParameterBinding(def.name);
23036
+ const bindingParent = def.name.parent;
23037
+ return isFunctionLike$1(bindingParent);
23212
23038
  }));
23213
23039
  const isIdentifierOrMemberExpression = (node) => isNodeOfType(node, "Identifier") || isNodeOfType(node, "MemberExpression");
23214
23040
  const isPropAlias = (analysis, ref) => {
@@ -26205,56 +26031,6 @@ const noBarrelImport = defineRule({
26205
26031
  }
26206
26032
  });
26207
26033
  //#endregion
26208
- //#region src/plugin/utils/function-returns-matching-expression.ts
26209
- const collectReturnedExpressions = (functionNode) => {
26210
- if (!isFunctionLike$1(functionNode)) return [];
26211
- const body = functionNode.body;
26212
- if (!body) return [];
26213
- if (!isNodeOfType(body, "BlockStatement")) return [body];
26214
- const returnedExpressions = [];
26215
- walkAst(body, (node) => {
26216
- if (node !== body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
26217
- if (isNodeOfType(node, "ReturnStatement") && node.argument) returnedExpressions.push(node.argument);
26218
- });
26219
- return returnedExpressions;
26220
- };
26221
- const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpression) => {
26222
- const visitedExpressions = /* @__PURE__ */ new Set();
26223
- const visitedFunctions = /* @__PURE__ */ new Set();
26224
- const functionMatches = (candidateFunction) => {
26225
- if (visitedFunctions.has(candidateFunction)) return false;
26226
- visitedFunctions.add(candidateFunction);
26227
- return collectReturnedExpressions(candidateFunction).some(expressionMatches);
26228
- };
26229
- const expressionMatches = (expression) => {
26230
- const unwrappedExpression = stripParenExpression(expression);
26231
- if (visitedExpressions.has(unwrappedExpression)) return false;
26232
- visitedExpressions.add(unwrappedExpression);
26233
- if (matchesExpression(unwrappedExpression)) return true;
26234
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
26235
- const symbol = scopes.symbolFor(unwrappedExpression);
26236
- if (!symbol || symbol.kind !== "const" || !symbol.initializer) return false;
26237
- const initializer = stripParenExpression(symbol.initializer);
26238
- if (isFunctionLike$1(initializer)) return false;
26239
- return expressionMatches(initializer);
26240
- }
26241
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
26242
- if (unwrappedExpression.arguments.length !== 0) return false;
26243
- if (!isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
26244
- const symbol = scopes.symbolFor(unwrappedExpression.callee);
26245
- if (!symbol || symbol.kind !== "const" && symbol.kind !== "function") return false;
26246
- const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
26247
- const candidateFunction = isFunctionLike$1(initializer) ? initializer : isFunctionLike$1(symbol.declarationNode) ? symbol.declarationNode : null;
26248
- if (!candidateFunction || candidateFunction.async || candidateFunction.generator || candidateFunction.params.length !== 0) return false;
26249
- return functionMatches(candidateFunction);
26250
- }
26251
- if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return expressionMatches(unwrappedExpression.consequent) || expressionMatches(unwrappedExpression.alternate);
26252
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return expressionMatches(unwrappedExpression.left) || expressionMatches(unwrappedExpression.right);
26253
- return false;
26254
- };
26255
- return functionMatches(functionNode);
26256
- };
26257
- //#endregion
26258
26034
  //#region src/plugin/utils/function-contains-react-render-output.ts
26259
26035
  const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
26260
26036
  "FunctionDeclaration",
@@ -26274,13 +26050,12 @@ const isCallArgumentFunctionExpression = (node) => {
26274
26050
  return parent.arguments.some((argumentNode) => argumentNode === node);
26275
26051
  };
26276
26052
  const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
26277
- const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
26278
26053
  const containsRenderOutput$1 = (rootNode, scopes) => {
26279
26054
  let hasRenderOutput = false;
26280
26055
  walkAst(rootNode, (node) => {
26281
26056
  if (hasRenderOutput) return false;
26282
26057
  if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
26283
- if (isRenderOutputExpression(node, scopes)) {
26058
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
26284
26059
  hasRenderOutput = true;
26285
26060
  return false;
26286
26061
  }
@@ -26291,7 +26066,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
26291
26066
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
26292
26067
  const cachedEntry = renderOutputCache.get(functionNode);
26293
26068
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
26294
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
26069
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
26295
26070
  renderOutputCache.set(functionNode, {
26296
26071
  scopes,
26297
26072
  hasRenderOutput
@@ -28756,7 +28531,7 @@ const noDisabledZoom = defineRule({
28756
28531
  category: "Accessibility",
28757
28532
  recommendation: "Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If the layout breaks at 200% zoom, fix the layout instead.",
28758
28533
  create: (context) => ({ JSXOpeningElement(node) {
28759
- if (resolveJsxElementType(node) !== "meta") return;
28534
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "meta") return;
28760
28535
  const nameAttr = findJsxAttribute(node.attributes ?? [], "name");
28761
28536
  if (!nameAttr?.value) return;
28762
28537
  if ((isNodeOfType(nameAttr.value, "Literal") ? nameAttr.value.value : null) !== "viewport") return;
@@ -31928,7 +31703,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
31928
31703
  severity: "warn",
31929
31704
  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.",
31930
31705
  create: (context) => ({ JSXOpeningElement(node) {
31931
- if (resolveJsxElementType(node) !== "img") return;
31706
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
31932
31707
  const loadingAttribute = hasJsxPropIgnoreCase(node.attributes, "loading");
31933
31708
  if (!loadingAttribute || getJsxPropStringValue(loadingAttribute)?.toLowerCase() !== "lazy") return;
31934
31709
  const fetchPriorityAttribute = hasJsxPropIgnoreCase(node.attributes, "fetchPriority");
@@ -32169,7 +31944,7 @@ const noIndeterminateAttribute = defineRule({
32169
31944
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
32170
31945
  return {
32171
31946
  JSXOpeningElement(node) {
32172
- if (resolveJsxElementType(node) !== "input") return;
31947
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
32173
31948
  let typeAttribute = null;
32174
31949
  let typeAttributeIndex = null;
32175
31950
  let indeterminateAttribute = null;
@@ -33241,13 +33016,11 @@ const noManyBooleanProps = defineRule({
33241
33016
  };
33242
33017
  const checkComponent = (functionNode, param, body, componentName, reportNode) => {
33243
33018
  if (!param) return;
33244
- const propsBinding = resolveFirstArgumentBinding(param);
33245
- if (!propsBinding) return;
33246
33019
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
33247
- if (isNodeOfType(propsBinding, "ObjectPattern")) {
33020
+ if (isNodeOfType(param, "ObjectPattern")) {
33248
33021
  const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
33249
33022
  const booleanLikePropNames = [];
33250
- for (const property of propsBinding.properties ?? []) {
33023
+ for (const property of param.properties ?? []) {
33251
33024
  if (!isNodeOfType(property, "Property")) continue;
33252
33025
  const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
33253
33026
  if (!keyName) continue;
@@ -33258,7 +33031,7 @@ const noManyBooleanProps = defineRule({
33258
33031
  reportIfMany(booleanLikePropNames, componentName, reportNode);
33259
33032
  return;
33260
33033
  }
33261
- if (isNodeOfType(propsBinding, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, propsBinding.name)], componentName, reportNode);
33034
+ if (isNodeOfType(param, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, param.name)], componentName, reportNode);
33262
33035
  };
33263
33036
  return {
33264
33037
  FunctionDeclaration(node) {
@@ -36010,7 +35783,7 @@ const noPreventDefault = defineRule({
36010
35783
  const isServerActionsFramework = hasCapability(context.settings, "server-actions");
36011
35784
  const formMessage = isServerActionsFramework ? FORM_MESSAGE_SERVER_CAPABLE : FORM_MESSAGE_GENERIC;
36012
35785
  return { JSXOpeningElement(node) {
36013
- const elementName = resolveJsxElementType(node);
35786
+ const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
36014
35787
  if (!elementName) return;
36015
35788
  const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
36016
35789
  if (!targetEventProps) return;
@@ -38474,10 +38247,9 @@ const noStringFalseOnBooleanAttribute = defineRule({
38474
38247
  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.",
38475
38248
  create: (context) => ({ JSXOpeningElement(node) {
38476
38249
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
38477
- const elementName = resolveJsxElementType(node);
38478
- const firstCharacter = elementName.charCodeAt(0);
38250
+ const firstCharacter = node.name.name.charCodeAt(0);
38479
38251
  if (firstCharacter < 97 || firstCharacter > 122) return;
38480
- if (elementName.includes("-")) return;
38252
+ if (node.name.name.includes("-")) return;
38481
38253
  for (const attribute of node.attributes) {
38482
38254
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
38483
38255
  if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
@@ -38865,7 +38637,8 @@ const noUncontrolledInput = defineRule({
38865
38637
  const undefinedInitialStateNames = isNodeOfType(componentBody, "BlockStatement") ? collectUndefinedInitialStateNames(componentBody) : /* @__PURE__ */ new Set();
38866
38638
  walkAst(componentBody, (child) => {
38867
38639
  if (!isNodeOfType(child, "JSXOpeningElement")) return;
38868
- const tagName = resolveJsxElementType(child);
38640
+ if (!isNodeOfType(child.name, "JSXIdentifier")) return;
38641
+ const tagName = child.name.name;
38869
38642
  if (!UNCONTROLLED_INPUT_TAGS.has(tagName)) return;
38870
38643
  const attributes = child.attributes ?? [];
38871
38644
  if (hasJsxSpreadAttribute(attributes)) return;
@@ -38926,7 +38699,7 @@ const noUndeferredThirdParty = defineRule({
38926
38699
  severity: "warn",
38927
38700
  recommendation: "Use `next/script` with `strategy=\"lazyOnload\"`, or add the `defer` attribute.",
38928
38701
  create: (context) => ({ JSXOpeningElement(node) {
38929
- if (resolveJsxElementType(node) !== "script") return;
38702
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "script") return;
38930
38703
  const attributes = node.attributes ?? [];
38931
38704
  const srcAttribute = findJsxAttribute(attributes, "src");
38932
38705
  if (!srcAttribute) return;
@@ -40141,7 +39914,7 @@ const noUnknownProperty = defineRule({
40141
39914
  }
40142
39915
  if (fileIsNonReactJsx) return;
40143
39916
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
40144
- const elementType = resolveJsxElementType(node);
39917
+ const elementType = node.name.name;
40145
39918
  const firstCharacter = elementType.charCodeAt(0);
40146
39919
  if (!(firstCharacter >= 97 && firstCharacter <= 122) || elementType === "fbt" || elementType === "fbs") return;
40147
39920
  let isValidHtmlTag = isKnownDomTag(elementType);
@@ -40319,21 +40092,20 @@ const expressionContainsJsxOrCreateElement = (root) => {
40319
40092
  });
40320
40093
  return found;
40321
40094
  };
40322
- const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
40323
40095
  const isReactClassComponent = (classNode) => {
40324
40096
  if (isEs6Component(classNode)) return true;
40325
40097
  return expressionContainsJsxOrCreateElement(classNode);
40326
40098
  };
40327
- const findEnclosingComponent = (node, scopes) => {
40099
+ const findEnclosingComponent = (node) => {
40328
40100
  let walker = node.parent;
40329
40101
  while (walker) {
40330
40102
  if (isFunctionLike$1(walker)) {
40331
40103
  const componentName = inferFunctionLikeName(walker);
40332
- if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
40104
+ if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
40333
40105
  component: walker,
40334
40106
  name: componentName
40335
40107
  };
40336
- if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40108
+ if (!componentName && expressionContainsJsxOrCreateElement(walker) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40337
40109
  component: walker,
40338
40110
  name: null
40339
40111
  };
@@ -40572,7 +40344,7 @@ const noUnstableNestedComponents = defineRule({
40572
40344
  if (renderPropRegex.test(propInfo.propName)) return;
40573
40345
  if (settings.allowAsProps) return;
40574
40346
  }
40575
- const enclosing = findEnclosingComponent(candidateNode, context.scopes);
40347
+ const enclosing = findEnclosingComponent(candidateNode);
40576
40348
  if (!enclosing) return;
40577
40349
  const gatedName = propInfo ? null : requiredInstantiationName;
40578
40350
  queuedReports.push({
@@ -40583,7 +40355,7 @@ const noUnstableNestedComponents = defineRule({
40583
40355
  });
40584
40356
  };
40585
40357
  const checkFunctionLike = (node) => {
40586
- if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
40358
+ if (!expressionContainsJsxOrCreateElement(node)) return;
40587
40359
  const inferredName = inferFunctionLikeName(node);
40588
40360
  const propInfo = isComponentDeclaredInProp(node);
40589
40361
  const isObjectCallback = isObjectCallbackCandidate(node);
@@ -41843,7 +41615,7 @@ const preactPreferOndblclick = defineRule({
41843
41615
  recommendation: "Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",
41844
41616
  create: (context) => ({ JSXOpeningElement(node) {
41845
41617
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
41846
- const tagName = resolveJsxElementType(node);
41618
+ const tagName = node.name.name;
41847
41619
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
41848
41620
  const onDoubleClickAttribute = findJsxAttribute(node.attributes, "onDoubleClick");
41849
41621
  if (!onDoubleClickAttribute) return;
@@ -41863,7 +41635,7 @@ const COMPAT_EXEMPT_INPUT_TYPES = new Set([
41863
41635
  ]);
41864
41636
  const isTextLikeInput = (openingElement) => {
41865
41637
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
41866
- const tagName = resolveJsxElementType(openingElement);
41638
+ const tagName = openingElement.name.name;
41867
41639
  if (tagName === "textarea") return true;
41868
41640
  if (tagName !== "input") return false;
41869
41641
  const typeAttribute = findJsxAttribute(openingElement.attributes, "type");
@@ -42020,9 +41792,8 @@ const CROSS_CUTTING_STATE_BOOLEAN_NAMES = new Set([
42020
41792
  ]);
42021
41793
  const collectBooleanPropBindings = (param) => {
42022
41794
  const bindings = /* @__PURE__ */ new Set();
42023
- const propsBinding = resolveFirstArgumentBinding(param);
42024
- if (!isNodeOfType(propsBinding, "ObjectPattern")) return bindings;
42025
- for (const property of propsBinding.properties ?? []) {
41795
+ if (!param || !isNodeOfType(param, "ObjectPattern")) return bindings;
41796
+ for (const property of param.properties ?? []) {
42026
41797
  if (!isNodeOfType(property, "Property")) continue;
42027
41798
  if (property.computed) continue;
42028
41799
  if (!isNodeOfType(property.key, "Identifier")) continue;
@@ -42283,7 +42054,7 @@ const preferHtmlDialog = defineRule({
42283
42054
  },
42284
42055
  JSXOpeningElement(node) {
42285
42056
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
42286
- const tagName = resolveJsxElementType(node);
42057
+ const tagName = node.name.name;
42287
42058
  if (tagName === "dialog") return;
42288
42059
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
42289
42060
  if (!HTML_TAGS.has(tagName)) return;
@@ -43830,13 +43601,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
43830
43601
  }
43831
43602
  return null;
43832
43603
  };
43833
- const resolveHookNameFromInitializer = (initializer, scopes) => {
43604
+ const resolveHookNameFromInitializer = (initializer) => {
43834
43605
  if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
43835
43606
  if (!isNodeOfType(initializer, "Identifier")) return null;
43836
- const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
43837
- if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
43838
- if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
43839
- return resolveTanstackQueryHookName(resolvedSymbol.initializer);
43607
+ const binding = findVariableInitializer(initializer, initializer.name);
43608
+ if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
43609
+ if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
43610
+ return resolveTanstackQueryHookName(binding.initializer);
43840
43611
  };
43841
43612
  const queryNoRestDestructuring = defineRule({
43842
43613
  id: "query-no-rest-destructuring",
@@ -43849,7 +43620,7 @@ const queryNoRestDestructuring = defineRule({
43849
43620
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
43850
43621
  if (!node.init) return;
43851
43622
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
43852
- const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
43623
+ const hookName = resolveHookNameFromInitializer(node.init);
43853
43624
  if (!hookName) return;
43854
43625
  context.report({
43855
43626
  node: node.id,
@@ -44320,7 +44091,7 @@ const renderingAnimateSvgWrapper = defineRule({
44320
44091
  severity: "warn",
44321
44092
  recommendation: "Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.",
44322
44093
  create: (context) => ({ JSXOpeningElement(node) {
44323
- if (resolveJsxElementType(node) !== "svg") return;
44094
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "svg") return;
44324
44095
  if (node.attributes?.some((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && MOTION_ANIMATE_PROPS.has(attribute.name.name))) context.report({
44325
44096
  node,
44326
44097
  message: "This is slow to render because you animate <svg> directly, so wrap it in a <div> or <motion.div> & animate that instead"
@@ -45613,10 +45384,14 @@ const rerenderLazyStateInit = defineRule({
45613
45384
  //#endregion
45614
45385
  //#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
45615
45386
  const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
45616
- const callbackReturnsJsx = (callback, scopes) => {
45387
+ const callbackReturnsJsx = (callback) => {
45617
45388
  if (!callback) return false;
45618
45389
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
45619
- return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
45390
+ const body = callback.body;
45391
+ if (isJsxExpression(body)) return true;
45392
+ if (!isNodeOfType(body, "BlockStatement")) return false;
45393
+ for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
45394
+ return false;
45620
45395
  };
45621
45396
  const returnArgumentUsesAnyName = (returnStatement, names) => {
45622
45397
  if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
@@ -45694,7 +45469,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
45694
45469
  if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
45695
45470
  for (const declarator of stmt.declarations ?? []) {
45696
45471
  const init = declarator.init;
45697
- if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
45472
+ if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
45698
45473
  memoNode = declarator;
45699
45474
  callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
45700
45475
  if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
@@ -45760,11 +45535,8 @@ const collectFromObjectPattern = (pattern, bindings) => {
45760
45535
  const collectDefaultedEmptyBindings = (functionNode) => {
45761
45536
  const bindings = /* @__PURE__ */ new Map();
45762
45537
  const params = functionNode.params ?? [];
45763
- for (const [parameterIndex, param] of params.entries()) {
45764
- const parameterBinding = parameterIndex === 0 ? resolveFirstArgumentBinding(param) : param;
45765
- if (parameterBinding) collectFromObjectPattern(parameterBinding, bindings);
45766
- }
45767
- const propsParam = resolveFirstArgumentBinding(params[0]);
45538
+ for (const param of params) collectFromObjectPattern(param, bindings);
45539
+ const propsParam = params[0];
45768
45540
  const body = functionNode.body;
45769
45541
  if (!propsParam || !isNodeOfType(propsParam, "Identifier") || !body) return bindings;
45770
45542
  if (!isNodeOfType(body, "BlockStatement")) return bindings;
@@ -53878,6 +53650,10 @@ const isInvalidStyleExpression = (expression) => {
53878
53650
  }
53879
53651
  return false;
53880
53652
  };
53653
+ const getJsxOpeningElementName = (node) => {
53654
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
53655
+ return null;
53656
+ };
53881
53657
  const stylePropObject = defineRule({
53882
53658
  id: "style-prop-object",
53883
53659
  title: "Style prop is not an object",
@@ -53889,8 +53665,7 @@ const stylePropObject = defineRule({
53889
53665
  const allowSet = new Set(allow);
53890
53666
  return {
53891
53667
  JSXOpeningElement(node) {
53892
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
53893
- const elementName = resolveJsxElementType(node);
53668
+ const elementName = getJsxOpeningElementName(node);
53894
53669
  if (elementName && allowSet.has(elementName)) return;
53895
53670
  if (elementName) {
53896
53671
  const firstCharCode = elementName.charCodeAt(0);
@@ -54595,7 +54370,7 @@ const tanstackStartNoAnchorElement = defineRule({
54595
54370
  create: (context) => {
54596
54371
  if (!isInProjectDirectory(context, "routes")) return {};
54597
54372
  return { JSXOpeningElement(node) {
54598
- if (resolveJsxElementType(node) !== "a") return;
54373
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
54599
54374
  const attributes = node.attributes ?? [];
54600
54375
  const hrefAttribute = findJsxAttribute(attributes, "href");
54601
54376
  if (!hrefAttribute?.value) return;
@@ -55216,7 +54991,8 @@ const voidDomElementsNoChildren = defineRule({
55216
54991
  create: (context) => ({
55217
54992
  JSXElement(node) {
55218
54993
  const openingElement = node.openingElement;
55219
- const tagName = resolveJsxElementType(openingElement);
54994
+ if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return;
54995
+ const tagName = openingElement.name.name;
55220
54996
  if (!VOID_DOM_ELEMENTS.has(tagName)) return;
55221
54997
  const hasChildrenContent = node.children.some(isMeaningfulJsxChild);
55222
54998
  const hasChildrenLikeProp = findChildrenLikePropName(openingElement.attributes);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.6-dev.c6f996e",
3
+ "version": "0.7.6-dev.e026a23",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",