oxlint-plugin-react-doctor 0.2.17-dev.5614b32 → 0.2.18-dev.08e1d55

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.
package/dist/index.js CHANGED
@@ -2574,7 +2574,7 @@ const INTENTIONAL_SEQUENCING_CALLEE_NAMES = new Set([
2574
2574
  * (`FUNCTION_LIKE_TYPES.has(node.type)`) and as a type-guard. The
2575
2575
  * type-guard form covers both shapes without callers paying a cast.
2576
2576
  */
2577
- const isFunctionLike$2 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
2577
+ const isFunctionLike$3 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
2578
2578
  //#endregion
2579
2579
  //#region src/plugin/utils/is-inline-function-expression.ts
2580
2580
  /**
@@ -2597,7 +2597,7 @@ const findFirstAwaitOutsideNestedFunctions = (block) => {
2597
2597
  let firstAwait = null;
2598
2598
  walkAst(block, (child) => {
2599
2599
  if (firstAwait) return false;
2600
- if (child !== block && isFunctionLike$2(child)) return false;
2600
+ if (child !== block && isFunctionLike$3(child)) return false;
2601
2601
  if (isNodeOfType(child, "AwaitExpression")) firstAwait = child;
2602
2602
  });
2603
2603
  return firstAwait;
@@ -3054,13 +3054,13 @@ const asyncDeferAwait = defineRule({
3054
3054
  const inspectAllStatementBlocks = (functionBody) => {
3055
3055
  if (!functionBody) return;
3056
3056
  walkAst(functionBody, (descendant) => {
3057
- if (isFunctionLike$2(descendant)) return false;
3057
+ if (isFunctionLike$3(descendant)) return false;
3058
3058
  if (isNodeOfType(descendant, "BlockStatement")) inspectStatements(descendant.body ?? []);
3059
3059
  else if (isNodeOfType(descendant, "SwitchCase")) inspectStatements(descendant.consequent ?? []);
3060
3060
  });
3061
3061
  };
3062
3062
  const enterFunction = (node) => {
3063
- if (!isFunctionLike$2(node)) return;
3063
+ if (!isFunctionLike$3(node)) return;
3064
3064
  if (!node.async) return;
3065
3065
  if (!isNodeOfType(node.body, "BlockStatement")) return;
3066
3066
  inspectAllStatementBlocks(node.body);
@@ -4509,7 +4509,7 @@ const displayName = defineRule({
4509
4509
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
4510
4510
  const walkInsideStatementBlocks = (node, visitor) => {
4511
4511
  if (!node || typeof node !== "object") return;
4512
- if (isFunctionLike$2(node)) return;
4512
+ if (isFunctionLike$3(node)) return;
4513
4513
  visitor(node);
4514
4514
  const nodeRecord = node;
4515
4515
  for (const key of Object.keys(nodeRecord)) {
@@ -4597,7 +4597,7 @@ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubs
4597
4597
  let didFindRelease = false;
4598
4598
  walkAst(node, (child) => {
4599
4599
  if (didFindRelease) return false;
4600
- if (child !== node && isFunctionLike$2(child) && !isIteratorCallbackArgument(child)) return false;
4600
+ if (child !== node && isFunctionLike$3(child) && !isIteratorCallbackArgument(child)) return false;
4601
4601
  if (isReleaseLikeCall(child, knownCleanupFunctionNames, knownBoundSubscriptionNames)) {
4602
4602
  didFindRelease = true;
4603
4603
  return false;
@@ -4606,14 +4606,20 @@ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubs
4606
4606
  return didFindRelease;
4607
4607
  };
4608
4608
  const isCleanupFunctionLike = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
4609
- if (!isFunctionLike$2(node)) return false;
4609
+ if (!isFunctionLike$3(node)) return false;
4610
4610
  return containsReleaseLikeCall(node.body, knownCleanupFunctionNames, knownBoundSubscriptionNames);
4611
4611
  };
4612
- const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
4612
+ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames, options = {}) => {
4613
4613
  if (!returnedValue) return false;
4614
4614
  const unwrappedValue = unwrapChainExpression$2(returnedValue);
4615
- if (isNodeOfType(unwrappedValue, "Identifier")) return knownCleanupFunctionNames.has(unwrappedValue.name);
4615
+ if (isNodeOfType(unwrappedValue, "Literal") && unwrappedValue.value === null) return false;
4616
+ if (isNodeOfType(unwrappedValue, "Identifier")) {
4617
+ if (unwrappedValue.name === "undefined") return false;
4618
+ if (knownCleanupFunctionNames.has(unwrappedValue.name)) return true;
4619
+ return options.allowOpaqueReturn === true && !knownBoundSubscriptionNames.has(unwrappedValue.name);
4620
+ }
4616
4621
  if (isCleanupReturningSubscribeLikeCallExpression(unwrappedValue)) return true;
4622
+ if (options.allowOpaqueReturn === true && !isSubscribeLikeCallExpression(unwrappedValue)) return true;
4617
4623
  if (isCleanupFunctionLike(unwrappedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames)) return true;
4618
4624
  return false;
4619
4625
  };
@@ -4634,12 +4640,14 @@ const findSubscribeLikeUsages = (callback) => {
4634
4640
  if (isNodeOfType(child.callee, "Identifier") && TIMER_CALLEE_NAMES_REQUIRING_CLEANUP.has(child.callee.name)) {
4635
4641
  usages.push({
4636
4642
  kind: "timer",
4643
+ node: child,
4637
4644
  resourceName: child.callee.name
4638
4645
  });
4639
4646
  return;
4640
4647
  }
4641
4648
  if (isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") && SUBSCRIPTION_METHOD_NAMES.has(child.callee.property.name)) usages.push({
4642
4649
  kind: "subscribe",
4650
+ node: child,
4643
4651
  resourceName: child.callee.property.name
4644
4652
  });
4645
4653
  });
@@ -4686,7 +4694,20 @@ const collectCleanupBindings = (effectCallback) => {
4686
4694
  });
4687
4695
  return bindings;
4688
4696
  };
4689
- const effectHasCleanupRelease = (callback) => {
4697
+ const getRangeStart = (node) => {
4698
+ const rangeStart = node.range?.[0];
4699
+ return typeof rangeStart === "number" ? rangeStart : null;
4700
+ };
4701
+ const cleanupReturnRunsAfterUsage = (returnStatement, usages) => {
4702
+ if (returnStatement.argument && isCleanupReturningSubscribeLikeCallExpression(returnStatement.argument)) return true;
4703
+ const returnStart = getRangeStart(returnStatement);
4704
+ if (returnStart === null) return true;
4705
+ return usages.some((usage) => {
4706
+ const usageStart = getRangeStart(usage.node);
4707
+ return usageStart === null || usageStart < returnStart;
4708
+ });
4709
+ };
4710
+ const effectHasCleanupReturn = (callback, usages) => {
4690
4711
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
4691
4712
  if (!isNodeOfType(callback.body, "BlockStatement")) return isCleanupReturningSubscribeLikeCallExpression(callback.body);
4692
4713
  const cleanupBindings = collectCleanupBindings(callback);
@@ -4694,7 +4715,8 @@ const effectHasCleanupRelease = (callback) => {
4694
4715
  walkInsideStatementBlocks(callback.body, (child) => {
4695
4716
  if (didFindCleanupReturn) return;
4696
4717
  if (!isNodeOfType(child, "ReturnStatement")) return;
4697
- if (isCleanupReturn(child.argument, cleanupBindings.cleanupFunctionNames, cleanupBindings.subscriptionNames)) didFindCleanupReturn = true;
4718
+ if (!cleanupReturnRunsAfterUsage(child, usages)) return;
4719
+ if (isCleanupReturn(child.argument, cleanupBindings.cleanupFunctionNames, cleanupBindings.subscriptionNames, { allowOpaqueReturn: true })) didFindCleanupReturn = true;
4698
4720
  });
4699
4721
  return didFindCleanupReturn;
4700
4722
  };
@@ -4710,7 +4732,7 @@ const effectNeedsCleanup = defineRule({
4710
4732
  if (!callback) return;
4711
4733
  const usages = findSubscribeLikeUsages(callback);
4712
4734
  if (usages.length === 0) return;
4713
- if (effectHasCleanupRelease(callback)) return;
4735
+ if (effectHasCleanupReturn(callback, usages)) return;
4714
4736
  const firstUsage = usages[0];
4715
4737
  const verb = firstUsage.kind === "timer" ? "schedules" : "subscribes via";
4716
4738
  context.report({
@@ -4848,7 +4870,7 @@ const recordReference = (state, identifier, flag) => {
4848
4870
  };
4849
4871
  const isFunctionBodyBlock = (block) => {
4850
4872
  if (!block.parent) return false;
4851
- return isFunctionLike$2(block.parent);
4873
+ return isFunctionLike$3(block.parent);
4852
4874
  };
4853
4875
  const isCatchClauseBlock = (block) => block.parent !== null && block.parent !== void 0 && block.parent.type === "CatchClause";
4854
4876
  const handleVariableDeclaration = (declaration, state) => {
@@ -5003,7 +5025,7 @@ const walkParameterReferences = (pattern, state) => {
5003
5025
  if (isNodeOfType(pattern, "RestElement")) walkParameterReferences(pattern.argument, state);
5004
5026
  };
5005
5027
  const walk = (node, state) => {
5006
- if (isFunctionLike$2(node)) {
5028
+ if (isFunctionLike$3(node)) {
5007
5029
  if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
5008
5030
  setNodeScope(node, state);
5009
5031
  const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
@@ -5249,7 +5271,7 @@ const closureCaptures = (functionNode, scopes) => {
5249
5271
  const out = [];
5250
5272
  const seen = /* @__PURE__ */ new Set();
5251
5273
  const visit = (node) => {
5252
- if (node !== functionNode && isFunctionLike$2(node)) {
5274
+ if (node !== functionNode && isFunctionLike$3(node)) {
5253
5275
  const innerCaptures = closureCaptures(node, scopes);
5254
5276
  for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
5255
5277
  if (!seen.has(reference.id)) {
@@ -7346,6 +7368,13 @@ const isImportedFromModule = (contextNode, localIdentifierName, moduleSource) =>
7346
7368
  if (!info) return false;
7347
7369
  return info.source === moduleSource;
7348
7370
  };
7371
+ const isNamespaceImportFromModule = (contextNode, localIdentifierName, moduleSource) => {
7372
+ const lookup = getImportLookup(contextNode);
7373
+ if (!lookup) return false;
7374
+ const info = lookup.get(localIdentifierName);
7375
+ if (!info) return false;
7376
+ return info.isNamespace && info.source === moduleSource;
7377
+ };
7349
7378
  const getImportedNameFromModule = (contextNode, localIdentifierName, moduleSource) => {
7350
7379
  const lookup = getImportLookup(contextNode);
7351
7380
  if (!lookup) return null;
@@ -7362,7 +7391,7 @@ const isAtomFromJotai = (callExpression) => {
7362
7391
  if (!isImportedFromModule(callExpression, localName, "jotai")) return false;
7363
7392
  return getImportedNameFromModule(callExpression, localName, "jotai") === "atom";
7364
7393
  };
7365
- const isFunctionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")));
7394
+ const isFunctionLike$2 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")));
7366
7395
  const getFirstParameterName = (fn) => {
7367
7396
  const parameters = fn.params ?? [];
7368
7397
  if (parameters.length !== 1) return null;
@@ -7481,7 +7510,7 @@ const jotaiDerivedAtomReturnsFreshObject = defineRule({
7481
7510
  const args = node.arguments ?? [];
7482
7511
  if (args.length === 0) return;
7483
7512
  const reader = args[0];
7484
- if (!isFunctionLike$1(reader)) return;
7513
+ if (!isFunctionLike$2(reader)) return;
7485
7514
  const getParameterName = getFirstParameterName(reader);
7486
7515
  if (!getParameterName) return;
7487
7516
  const freshReturn = getFreshReturnForFunction(reader);
@@ -7737,7 +7766,7 @@ const isInsideLoopContext = (node) => {
7737
7766
  let current = node.parent;
7738
7767
  while (current) {
7739
7768
  if (isNodeOfType(current, "ForStatement") || isNodeOfType(current, "ForInStatement") || isNodeOfType(current, "ForOfStatement") || isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return true;
7740
- if (isFunctionLike$2(current)) {
7769
+ if (isFunctionLike$3(current)) {
7741
7770
  if (isIteratorCallback(current)) return true;
7742
7771
  return false;
7743
7772
  }
@@ -8098,7 +8127,7 @@ const jsHoistIntl = defineRule({
8098
8127
  let cursor = node.parent ?? null;
8099
8128
  let inFunctionBody = false;
8100
8129
  while (cursor) {
8101
- if (isFunctionLike$2(cursor)) {
8130
+ if (isFunctionLike$3(cursor)) {
8102
8131
  inFunctionBody = true;
8103
8132
  const fnParent = cursor.parent;
8104
8133
  if (fnParent && isNodeOfType(fnParent, "CallExpression") && fnParent.arguments?.[0] === cursor) {
@@ -9696,6 +9725,7 @@ const jsxNoConstructedContextValues = defineRule({
9696
9725
  title: "Unstable context provider value",
9697
9726
  tags: ["react-jsx-only"],
9698
9727
  severity: "warn",
9728
+ disabledBy: ["react-compiler"],
9699
9729
  recommendation: "Wrap the context value in `useMemo`, or move it outside the component.",
9700
9730
  category: "Performance",
9701
9731
  create: (context) => {
@@ -13052,7 +13082,7 @@ const collectChainedGetHandlerBodies = (initNode) => {
13052
13082
  };
13053
13083
  const resolveBodiesFromExpression = (expression, resolveBinding, remainingDepth) => {
13054
13084
  if (remainingDepth <= 0) return [];
13055
- if (isFunctionLike$2(expression)) return expression.body ? [expression.body] : [];
13085
+ if (isFunctionLike$3(expression)) return expression.body ? [expression.body] : [];
13056
13086
  if (isNodeOfType(expression, "CallExpression")) {
13057
13087
  for (const callArgument of expression.arguments ?? []) {
13058
13088
  if (isNodeOfType(callArgument, "ArrowFunctionExpression") || isNodeOfType(callArgument, "FunctionExpression")) {
@@ -13522,7 +13552,7 @@ const getEffectFn = (analysis, node) => {
13522
13552
  if (isNodeOfType(fn, "ArrowFunctionExpression") || isNodeOfType(fn, "FunctionExpression")) return fn;
13523
13553
  if (isNodeOfType(fn, "Identifier")) {
13524
13554
  const definitionNode = getRef(analysis, fn)?.resolved?.defs[0]?.node;
13525
- if (definitionNode && isFunctionLike$2(definitionNode)) return definitionNode;
13555
+ if (definitionNode && isFunctionLike$3(definitionNode)) return definitionNode;
13526
13556
  if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
13527
13557
  const initializer = definitionNode.init;
13528
13558
  if (isNodeOfType(initializer, "ArrowFunctionExpression") || isNodeOfType(initializer, "FunctionExpression")) return initializer;
@@ -13615,14 +13645,14 @@ const getUseStateDecl = (analysis, ref) => {
13615
13645
  return node ?? null;
13616
13646
  };
13617
13647
  const isCleanupReturnArgument = (analysis, node) => {
13618
- if (isFunctionLike$2(node)) return true;
13648
+ if (isFunctionLike$3(node)) return true;
13619
13649
  if (isNodeOfType(node, "MemberExpression")) return true;
13620
13650
  if (isNodeOfType(node, "Identifier")) {
13621
13651
  const definitionNode = getRef(analysis, node)?.resolved?.defs[0]?.node;
13622
- if (definitionNode && isFunctionLike$2(definitionNode)) return true;
13652
+ if (definitionNode && isFunctionLike$3(definitionNode)) return true;
13623
13653
  if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
13624
13654
  const initializer = definitionNode.init;
13625
- return isFunctionLike$2(initializer);
13655
+ return isFunctionLike$3(initializer);
13626
13656
  }
13627
13657
  }
13628
13658
  if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
@@ -13632,7 +13662,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
13632
13662
  if (visited.has(node)) return false;
13633
13663
  visited.add(node);
13634
13664
  if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
13635
- if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$2(node)) return false;
13665
+ if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$3(node)) return false;
13636
13666
  const record = node;
13637
13667
  for (const [key, value] of Object.entries(record)) {
13638
13668
  if (key === "parent") continue;
@@ -13644,7 +13674,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
13644
13674
  };
13645
13675
  const hasCleanup = (analysis, node) => {
13646
13676
  const fn = getEffectFn(analysis, node);
13647
- if (!isFunctionLike$2(fn)) return false;
13677
+ if (!isFunctionLike$3(fn)) return false;
13648
13678
  if (!isNodeOfType(fn.body, "BlockStatement")) return false;
13649
13679
  return hasCleanupReturn(analysis, fn.body);
13650
13680
  };
@@ -13994,7 +14024,7 @@ const isInsideStaticPlaceholderMap = (node) => {
13994
14024
  let current = node;
13995
14025
  while (current.parent) {
13996
14026
  const parent = current.parent;
13997
- if (isFunctionLike$2(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
14027
+ if (isFunctionLike$3(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
13998
14028
  const callee = parent.callee;
13999
14029
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach")) return isStaticPlaceholderReceiver(callee.object);
14000
14030
  if (isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current) return isArrayFromLengthObjectCall(parent);
@@ -14013,7 +14043,7 @@ const findIteratorItemName$1 = (node) => {
14013
14043
  let current = node;
14014
14044
  while (current.parent) {
14015
14045
  const parent = current.parent;
14016
- if (isFunctionLike$2(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
14046
+ if (isFunctionLike$3(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
14017
14047
  const callee = parent.callee;
14018
14048
  const isIteratorMethodCall = isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach");
14019
14049
  const isArrayFromCallback = isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current;
@@ -14958,7 +14988,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
14958
14988
  const nearestEnclosingFunction = (node) => {
14959
14989
  let cursor = node.parent;
14960
14990
  while (cursor) {
14961
- if (isFunctionLike$2(cursor)) return cursor;
14991
+ if (isFunctionLike$3(cursor)) return cursor;
14962
14992
  cursor = cursor.parent ?? null;
14963
14993
  }
14964
14994
  return null;
@@ -15641,7 +15671,7 @@ const extractDestructuredPropNames = (params) => {
15641
15671
  };
15642
15672
  const getInlineFunctionNode = (node) => {
15643
15673
  if (!node) return null;
15644
- if (isFunctionLike$2(node)) return node;
15674
+ if (isFunctionLike$3(node)) return node;
15645
15675
  if (!isNodeOfType(node, "CallExpression")) return null;
15646
15676
  for (const argument of node.arguments ?? []) {
15647
15677
  const inlineFunctionNode = getInlineFunctionNode(argument);
@@ -15652,7 +15682,7 @@ const getInlineFunctionNode = (node) => {
15652
15682
  const getNearestComponentFunction = (node) => {
15653
15683
  let cursor = node.parent ?? null;
15654
15684
  while (cursor) {
15655
- if (isFunctionLike$2(cursor)) return cursor;
15685
+ if (isFunctionLike$3(cursor)) return cursor;
15656
15686
  cursor = cursor.parent ?? null;
15657
15687
  }
15658
15688
  return null;
@@ -15982,7 +16012,7 @@ const collectFunctionLocalBindings = (functionNode) => {
15982
16012
  const walkComponentRespectingShadows = (node, shadowedStateNames, visit) => {
15983
16013
  if (!node || typeof node !== "object") return;
15984
16014
  let nextShadowedStateNames = shadowedStateNames;
15985
- if (isFunctionLike$2(node)) {
16015
+ if (isFunctionLike$3(node)) {
15986
16016
  const localBindings = collectFunctionLocalBindings(node);
15987
16017
  if (localBindings.size > 0) {
15988
16018
  const merged = new Set(shadowedStateNames);
@@ -18239,7 +18269,7 @@ const isInsideClassBody = (node) => {
18239
18269
  let current = node.parent;
18240
18270
  while (current) {
18241
18271
  if (isNodeOfType(current, "ClassBody")) return true;
18242
- if (isFunctionLike$2(current)) return false;
18272
+ if (isFunctionLike$3(current)) return false;
18243
18273
  current = current.parent;
18244
18274
  }
18245
18275
  return false;
@@ -18943,7 +18973,7 @@ const isLodashMutatorCall = (callExpression) => {
18943
18973
  };
18944
18974
  //#endregion
18945
18975
  //#region src/plugin/utils/find-exported-function-body.ts
18946
- const isFunctionLike = (node) => {
18976
+ const isFunctionLike$1 = (node) => {
18947
18977
  if (!node) return false;
18948
18978
  return isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression");
18949
18979
  };
@@ -18958,7 +18988,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
18958
18988
  if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
18959
18989
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
18960
18990
  const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
18961
- if (initializer && isFunctionLike(initializer)) localBindings.set(declarator.id.name, initializer);
18991
+ if (initializer && isFunctionLike$1(initializer)) localBindings.set(declarator.id.name, initializer);
18962
18992
  }
18963
18993
  };
18964
18994
  for (const statement of programRoot.body ?? []) {
@@ -19002,7 +19032,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
19002
19032
  defaultExport = declaration;
19003
19033
  continue;
19004
19034
  }
19005
- if (isFunctionLike(declaration)) {
19035
+ if (isFunctionLike$1(declaration)) {
19006
19036
  defaultExport = declaration;
19007
19037
  continue;
19008
19038
  }
@@ -19163,7 +19193,7 @@ const resolveCrossFileFunctionExport = (fromFilename, source, exportedName) => {
19163
19193
  const resolveReducerFunction = (node, currentFilename) => {
19164
19194
  if (!node) return null;
19165
19195
  const unwrappedNode = stripParenExpression(node);
19166
- if (isFunctionLike$2(unwrappedNode)) return {
19196
+ if (isFunctionLike$3(unwrappedNode)) return {
19167
19197
  functionNode: unwrappedNode,
19168
19198
  crossFileSourceDisplay: null
19169
19199
  };
@@ -19171,7 +19201,7 @@ const resolveReducerFunction = (node, currentFilename) => {
19171
19201
  const initializer = findVariableInitializer(unwrappedNode, unwrappedNode.name)?.initializer;
19172
19202
  if (!initializer) return null;
19173
19203
  const unwrappedInitializer = stripParenExpression(initializer);
19174
- if (isFunctionLike$2(unwrappedInitializer)) return {
19204
+ if (isFunctionLike$3(unwrappedInitializer)) return {
19175
19205
  functionNode: unwrappedInitializer,
19176
19206
  crossFileSourceDisplay: null
19177
19207
  };
@@ -19285,11 +19315,11 @@ const canExpressionReturnOriginalReducerStateReference = (node, state) => {
19285
19315
  return false;
19286
19316
  };
19287
19317
  const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
19288
- if (isFunctionLike$2(node)) return [];
19318
+ if (isFunctionLike$3(node)) return [];
19289
19319
  const mutations = [];
19290
19320
  walkAst(node, (child) => {
19291
19321
  const unwrappedChild = stripParenExpression(child);
19292
- if (child !== node && isFunctionLike$2(unwrappedChild)) return false;
19322
+ if (child !== node && isFunctionLike$3(unwrappedChild)) return false;
19293
19323
  if (isNodeOfType(unwrappedChild, "AssignmentExpression")) {
19294
19324
  if (isNodeOfType(stripParenExpression(unwrappedChild.left), "MemberExpression") && isExpressionRootedInMutableReducerStateSource(unwrappedChild.left, state)) mutations.push({ node: unwrappedChild });
19295
19325
  return;
@@ -19394,7 +19424,7 @@ const updateReducerStateIdentityForIdentifierAssignment = (assignment, state) =>
19394
19424
  if (isExpressionReachableFromOriginalReducerState(assignment.right, state)) state.mutableStateSourceNames.add(name);
19395
19425
  };
19396
19426
  const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, reportedNodes, options) => {
19397
- if (!isFunctionLike$2(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return;
19427
+ if (!isFunctionLike$3(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return;
19398
19428
  const firstParam = functionNode.params?.[0];
19399
19429
  const stateName = isNodeOfType(firstParam, "Identifier") ? firstParam.name : isNodeOfType(firstParam, "AssignmentPattern") && isNodeOfType(firstParam.left, "Identifier") ? firstParam.left.name : null;
19400
19430
  if (!stateName) return;
@@ -20172,6 +20202,12 @@ const getReactDoctorStringSetting = (settings, settingName) => {
20172
20202
  const settingValue = readOwnPropertyValue(bag, settingName);
20173
20203
  return typeof settingValue === "string" ? settingValue : void 0;
20174
20204
  };
20205
+ const getReactDoctorNumberSetting = (settings, settingName) => {
20206
+ const bag = readReactDoctorSettingsBag(settings);
20207
+ if (!bag) return void 0;
20208
+ const settingValue = readOwnPropertyValue(bag, settingName);
20209
+ return typeof settingValue === "number" && Number.isFinite(settingValue) ? settingValue : void 0;
20210
+ };
20175
20211
  const getReactDoctorStringArraySetting = (settings, settingName) => {
20176
20212
  const bag = readReactDoctorSettingsBag(settings);
20177
20213
  if (!bag) return [];
@@ -21237,7 +21273,7 @@ const isTanStackServerFnHandler = (node) => {
21237
21273
  const isInsideServerOnlyScope = (node) => {
21238
21274
  let currentNode = node.parent ?? null;
21239
21275
  while (currentNode) {
21240
- if (isFunctionLike$2(currentNode)) {
21276
+ if (isFunctionLike$3(currentNode)) {
21241
21277
  if (hasUseServerDirective(currentNode) || isTanStackServerFnHandler(currentNode)) return true;
21242
21278
  }
21243
21279
  currentNode = currentNode.parent ?? null;
@@ -23392,7 +23428,7 @@ const isReactClassComponent = (classNode) => {
23392
23428
  const findEnclosingComponent = (node) => {
23393
23429
  let walker = node.parent;
23394
23430
  while (walker) {
23395
- if (isFunctionLike$2(walker)) {
23431
+ if (isFunctionLike$3(walker)) {
23396
23432
  const componentName = inferFunctionLikeName(walker);
23397
23433
  if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
23398
23434
  component: walker,
@@ -23644,6 +23680,35 @@ const noUsememoSimpleExpression = defineRule({
23644
23680
  });
23645
23681
  //#endregion
23646
23682
  //#region src/plugin/rules/design/no-wide-letter-spacing.ts
23683
+ const getJsxAttributeStringValue = (attributeValue) => {
23684
+ if (!attributeValue) return null;
23685
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return attributeValue.value;
23686
+ if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
23687
+ const expression = attributeValue.expression;
23688
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
23689
+ }
23690
+ return null;
23691
+ };
23692
+ const isTruthyBooleanJsxAttribute = (attributeValue) => {
23693
+ if (attributeValue === null) return true;
23694
+ if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
23695
+ const expression = attributeValue.expression;
23696
+ return isNodeOfType(expression, "Literal") && expression.value === true;
23697
+ }
23698
+ return false;
23699
+ };
23700
+ const hasUppercaseSiblingProp = (styleAttribute) => {
23701
+ const openingElement = styleAttribute.parent;
23702
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
23703
+ for (const attribute of openingElement.attributes ?? []) {
23704
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
23705
+ if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
23706
+ const attributeName = attribute.name.name;
23707
+ if (attributeName === "uppercase" && isTruthyBooleanJsxAttribute(attribute.value)) return true;
23708
+ if (attributeName === "textTransform" && getJsxAttributeStringValue(attribute.value) === "uppercase") return true;
23709
+ }
23710
+ return false;
23711
+ };
23647
23712
  const noWideLetterSpacing = defineRule({
23648
23713
  id: "no-wide-letter-spacing",
23649
23714
  title: "Wide letter spacing on body text",
@@ -23653,6 +23718,7 @@ const noWideLetterSpacing = defineRule({
23653
23718
  create: (context) => ({ JSXAttribute(node) {
23654
23719
  const expression = getInlineStyleExpression(node);
23655
23720
  if (!expression) return;
23721
+ if (hasUppercaseSiblingProp(node)) return;
23656
23722
  let isUppercase = false;
23657
23723
  let letterSpacingProperty = null;
23658
23724
  let letterSpacingEm = null;
@@ -24815,6 +24881,7 @@ const preferModuleScopeStaticValue = defineRule({
24815
24881
  tags: ["test-noise"],
24816
24882
  severity: "warn",
24817
24883
  category: "Architecture",
24884
+ disabledBy: ["react-compiler"],
24818
24885
  recommendation: "Move the value above the component, at the top of the file. It doesn't use local state, so rebuilding it each update is wasted and makes it look new every time.",
24819
24886
  create: (context) => ({ VariableDeclarator(node) {
24820
24887
  if (!isNodeOfType(node.id, "Identifier")) return;
@@ -25627,7 +25694,7 @@ const reduxUseselectorInlineDerivation = defineRule({
25627
25694
  if (!body) return;
25628
25695
  const returnedExpressions = [];
25629
25696
  if (isNodeOfType(body, "BlockStatement")) walkAst(body, (node) => {
25630
- if (node !== body && isFunctionLike$2(node)) return false;
25697
+ if (node !== body && isFunctionLike$3(node)) return false;
25631
25698
  if (isNodeOfType(node, "ReturnStatement")) {
25632
25699
  if (node.argument) returnedExpressions.push(node.argument);
25633
25700
  return false;
@@ -25970,7 +26037,7 @@ const hasOwnAwait = (functionBody) => {
25970
26037
  let found = false;
25971
26038
  walkAst(functionBody, (child) => {
25972
26039
  if (found) return;
25973
- if (child !== functionBody && isFunctionLike$2(child)) return false;
26040
+ if (child !== functionBody && isFunctionLike$3(child)) return false;
25974
26041
  if (isNodeOfType(child, "AwaitExpression")) found = true;
25975
26042
  });
25976
26043
  return found;
@@ -25989,7 +26056,7 @@ const setterIsCalledInAsyncContext = (componentBody, setterName) => {
25989
26056
  let found = false;
25990
26057
  walkAst(componentBody, (child) => {
25991
26058
  if (found) return;
25992
- if (!isFunctionLike$2(child)) return;
26059
+ if (!isFunctionLike$3(child)) return;
25993
26060
  const functionBody = child.body;
25994
26061
  if (!(Boolean(child.async) || hasOwnAwait(functionBody))) return;
25995
26062
  if (callsIdentifier(functionBody, setterName)) found = true;
@@ -26772,7 +26839,8 @@ const rerenderTransitionsScroll = defineRule({
26772
26839
  });
26773
26840
  //#endregion
26774
26841
  //#region src/plugin/rules/react-native/rn-animate-layout-property.ts
26775
- const REANIMATED_LAYOUT_KEYS = new Set([
26842
+ const REANIMATED_MODULE = "react-native-reanimated";
26843
+ const REANIMATED_LAYOUT_STYLE_PROPERTIES = new Set([
26776
26844
  "width",
26777
26845
  "height",
26778
26846
  "top",
@@ -26810,12 +26878,91 @@ const REANIMATED_LAYOUT_KEYS = new Set([
26810
26878
  "lineHeight",
26811
26879
  "letterSpacing"
26812
26880
  ]);
26881
+ const REANIMATED_ANIMATION_HELPERS = new Set([
26882
+ "withTiming",
26883
+ "withSpring",
26884
+ "withDecay",
26885
+ "withDelay",
26886
+ "withRepeat",
26887
+ "withSequence",
26888
+ "withClamp"
26889
+ ]);
26890
+ const findImportDeclaration = (symbol) => {
26891
+ let currentNode = symbol.declarationNode.parent;
26892
+ while (currentNode && !isNodeOfType(currentNode, "ImportDeclaration")) currentNode = currentNode.parent ?? null;
26893
+ return currentNode && isNodeOfType(currentNode, "ImportDeclaration") ? currentNode : null;
26894
+ };
26895
+ const isReanimatedImport = (symbol) => {
26896
+ const importDeclaration = findImportDeclaration(symbol);
26897
+ if (!importDeclaration) return false;
26898
+ return importDeclaration.source.value === REANIMATED_MODULE;
26899
+ };
26900
+ const getReanimatedNamedImport = (node, context) => {
26901
+ if (!isNodeOfType(node, "Identifier")) return null;
26902
+ const symbol = context.scopes.symbolFor(node);
26903
+ if (!symbol || symbol.kind !== "import") return null;
26904
+ if (!isReanimatedImport(symbol)) return null;
26905
+ const declarationNode = symbol.declarationNode;
26906
+ if (!isNodeOfType(declarationNode, "ImportSpecifier")) return null;
26907
+ const importedName = declarationNode.imported;
26908
+ if (isNodeOfType(importedName, "Identifier")) return importedName.name;
26909
+ if (isNodeOfType(importedName, "Literal") && typeof importedName.value === "string") return importedName.value;
26910
+ return null;
26911
+ };
26912
+ const isReanimatedNamespace = (node, context) => {
26913
+ if (!isNodeOfType(node, "Identifier")) return false;
26914
+ const symbol = context.scopes.symbolFor(node);
26915
+ if (!symbol || symbol.kind !== "import") return false;
26916
+ if (!isReanimatedImport(symbol)) return false;
26917
+ return isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier");
26918
+ };
26919
+ const isUseAnimatedStyleCallee = (callee, context) => {
26920
+ if (isNodeOfType(callee, "Identifier")) return getReanimatedNamedImport(callee, context) === "useAnimatedStyle";
26921
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
26922
+ if (!isNodeOfType(callee.property, "Identifier")) return false;
26923
+ if (callee.property.name !== "useAnimatedStyle") return false;
26924
+ return isReanimatedNamespace(callee.object, context);
26925
+ };
26926
+ const isReanimatedAnimationHelperCallee = (callee, context) => {
26927
+ if (isNodeOfType(callee, "Identifier")) {
26928
+ const importedName = getReanimatedNamedImport(callee, context);
26929
+ return importedName !== null && REANIMATED_ANIMATION_HELPERS.has(importedName);
26930
+ }
26931
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
26932
+ if (!isNodeOfType(callee.property, "Identifier")) return false;
26933
+ if (!REANIMATED_ANIMATION_HELPERS.has(callee.property.name)) return false;
26934
+ return isReanimatedNamespace(callee.object, context);
26935
+ };
26936
+ const isFunctionLike = (node) => isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration");
26937
+ const containsReanimatedAnimationHelperCall = (expression, context) => {
26938
+ const rootExpression = stripParenExpression(expression);
26939
+ let didFindAnimationHelper = false;
26940
+ walkAst(rootExpression, (node) => {
26941
+ if (didFindAnimationHelper) return false;
26942
+ if (node !== rootExpression && isFunctionLike(node)) return false;
26943
+ if (isNodeOfType(node, "CallExpression") && isReanimatedAnimationHelperCallee(node.callee, context)) {
26944
+ didFindAnimationHelper = true;
26945
+ return false;
26946
+ }
26947
+ });
26948
+ return didFindAnimationHelper;
26949
+ };
26813
26950
  const findReturnedObject = (callback) => {
26814
26951
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return null;
26815
- const body = callback.body;
26952
+ const body = stripParenExpression(callback.body);
26816
26953
  if (isNodeOfType(body, "ObjectExpression")) return body;
26817
26954
  if (!isNodeOfType(body, "BlockStatement")) return null;
26818
- for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isNodeOfType(stmt.argument, "ObjectExpression")) return stmt.argument;
26955
+ for (const statement of body.body ?? []) {
26956
+ if (!isNodeOfType(statement, "ReturnStatement")) continue;
26957
+ if (!statement.argument) continue;
26958
+ const returnArgument = stripParenExpression(statement.argument);
26959
+ if (isNodeOfType(returnArgument, "ObjectExpression")) return returnArgument;
26960
+ }
26961
+ return null;
26962
+ };
26963
+ const getStaticPropertyName$1 = (property) => {
26964
+ if (!property.computed && isNodeOfType(property.key, "Identifier")) return property.key.name;
26965
+ if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
26819
26966
  return null;
26820
26967
  };
26821
26968
  const rnAnimateLayoutProperty = defineRule({
@@ -26823,21 +26970,25 @@ const rnAnimateLayoutProperty = defineRule({
26823
26970
  title: "Animating a layout property",
26824
26971
  tags: ["test-noise"],
26825
26972
  requires: ["react-native"],
26826
- severity: "error",
26827
- recommendation: "Animating size or position props runs on the JS thread and can stutter. Animate `transform: [{ translateX/Y }, { scale }]` or `opacity` instead, which run on the GPU.",
26973
+ severity: "warn",
26974
+ category: "Performance",
26975
+ recommendation: "Prefer transform or opacity when a Reanimated animation helper drives purely visual motion; use layout-affecting styles only when the layout itself must change.",
26828
26976
  create: (context) => ({ CallExpression(node) {
26829
- if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "useAnimatedStyle") return;
26977
+ if (!isUseAnimatedStyleCallee(node.callee, context)) return;
26830
26978
  const callback = node.arguments?.[0];
26831
26979
  if (!callback) return;
26832
26980
  const returnedObject = findReturnedObject(callback);
26833
26981
  if (!returnedObject) return;
26834
26982
  for (const property of returnedObject.properties ?? []) {
26835
26983
  if (!isNodeOfType(property, "Property")) continue;
26836
- if (!isNodeOfType(property.key, "Identifier")) continue;
26837
- if (!REANIMATED_LAYOUT_KEYS.has(property.key.name)) continue;
26984
+ const propertyName = getStaticPropertyName$1(property);
26985
+ if (propertyName === null) continue;
26986
+ if (!REANIMATED_LAYOUT_STYLE_PROPERTIES.has(propertyName)) continue;
26987
+ const propertyValue = property.value;
26988
+ if (!containsReanimatedAnimationHelperCall(propertyValue, context)) continue;
26838
26989
  context.report({
26839
26990
  node: property,
26840
- message: `Your users see stutter when useAnimatedStyle animates "${property.key.name}" on the layout thread.`
26991
+ message: `Reanimated can animate "${propertyName}", but this layout-affecting style recalculates layout while the animation runs; prefer transform or opacity when the motion is only visual.`
26841
26992
  });
26842
26993
  }
26843
26994
  } })
@@ -27045,9 +27196,13 @@ const LEGACY_EXPO_PACKAGE_REPLACEMENTS = new Map([
27045
27196
  ["expo-av", "expo-audio for audio and expo-video for video"],
27046
27197
  ["expo-permissions", "the permissions API in each module (e.g. Camera.requestPermissionsAsync())"],
27047
27198
  ["expo-app-loading", "expo-splash-screen"],
27048
- ["expo-linear-gradient", "the `backgroundImage` CSS gradient style prop (New Architecture) or expo-linear-gradient's successor"],
27049
27199
  ["react-native-fast-image", "expo-image (drop-in with caching, placeholders, and crossfades)"]
27050
27200
  ]);
27201
+ const EXPO_UI_MODULE_SOURCES = new Set([
27202
+ "@expo/ui",
27203
+ "@expo/ui/swift-ui",
27204
+ "@expo/ui/jetpack-compose"
27205
+ ]);
27051
27206
  const REACT_NATIVE_LIST_COMPONENTS = new Set([
27052
27207
  "FlatList",
27053
27208
  "SectionList",
@@ -27195,6 +27350,10 @@ const RECYCLABLE_LIST_PACKAGES = {
27195
27350
  LegendList: ["@legendapp/list"]
27196
27351
  };
27197
27352
  const SIZING_HINT_ATTRIBUTE_NAMES = new Set(["estimatedItemSize", "estimatedListSize"]);
27353
+ const isFlashListV2OrNewer = (context) => {
27354
+ const flashListMajorVersion = getReactDoctorNumberSetting(context.settings, "shopifyFlashListMajorVersion");
27355
+ return flashListMajorVersion !== void 0 && flashListMajorVersion >= 2;
27356
+ };
27198
27357
  const isEmptyArrayLiteral = (node) => {
27199
27358
  if (!isNodeOfType(node.value, "JSXExpressionContainer")) return false;
27200
27359
  const expression = node.value.expression;
@@ -27216,6 +27375,7 @@ const rnListMissingEstimatedItemSize = defineRule({
27216
27375
  break;
27217
27376
  }
27218
27377
  if (!canonicalRecyclerName) return;
27378
+ if (canonicalRecyclerName === "FlashList" && isFlashListV2OrNewer(context)) return;
27219
27379
  let hasSizingHint = false;
27220
27380
  let dataIsEmptyLiteral = false;
27221
27381
  let hasDataProp = false;
@@ -27859,6 +28019,24 @@ const collectTextWrapperComponents = (programNode, isTextHandlingRoot) => {
27859
28019
  return wrappers;
27860
28020
  };
27861
28021
  //#endregion
28022
+ //#region src/plugin/rules/react-native/utils/is-expo-ui-component-element.ts
28023
+ const isNamedImportOf = (contextNode, localName, componentName) => {
28024
+ for (const moduleSource of EXPO_UI_MODULE_SOURCES) if (getImportedNameFromModule(contextNode, localName, moduleSource) === componentName) return true;
28025
+ return false;
28026
+ };
28027
+ const isExpoUiNamespaceImport = (contextNode, localName) => {
28028
+ for (const moduleSource of EXPO_UI_MODULE_SOURCES) if (isNamespaceImportFromModule(contextNode, localName, moduleSource)) return true;
28029
+ return false;
28030
+ };
28031
+ const isExpoUiComponentElement = (openingElement, contextNode, componentName) => {
28032
+ if (!openingElement.name) return false;
28033
+ const dottedName = flattenJsxName(openingElement.name);
28034
+ if (!dottedName) return false;
28035
+ const [rootLocalName, secondName] = dottedName.split(".");
28036
+ if (isNamedImportOf(contextNode, rootLocalName, componentName)) return true;
28037
+ return secondName === componentName && isExpoUiNamespaceImport(contextNode, rootLocalName);
28038
+ };
28039
+ //#endregion
27862
28040
  //#region src/plugin/rules/react-native/rn-no-raw-text.ts
27863
28041
  const truncateText = (text) => text.length > 30 ? `${text.slice(0, 30)}...` : text;
27864
28042
  const isRawTextContent = (child) => {
@@ -27918,6 +28096,7 @@ const rnNoRawText = defineRule({
27918
28096
  if (isDomComponentFile) return;
27919
28097
  const elementName = resolveTextBoundaryName(node.openingElement);
27920
28098
  if (elementName && (isTextHandlingComponent(elementName) || autoDetectedWrappers.has(elementName))) return;
28099
+ if (isExpoUiComponentElement(node.openingElement, node, "ListItem")) return;
27921
28100
  if (isInsidePlatformOsWebBranch(node)) return;
27922
28101
  if (isTransparentTextWrapper(elementName) && isInsideTextHandlingComponent(node)) return;
27923
28102
  for (const child of node.children ?? []) {
@@ -28081,6 +28260,7 @@ const rnNoScrollviewMappedList = defineRule({
28081
28260
  create: (context) => ({ JSXElement(node) {
28082
28261
  const elementName = resolveJsxElementName(node.openingElement);
28083
28262
  if (!elementName || !NON_VIRTUALIZED_SCROLL_CONTAINERS.has(elementName)) return;
28263
+ if (isExpoUiComponentElement(node.openingElement, node, "ScrollView")) return;
28084
28264
  for (const child of node.children ?? []) {
28085
28265
  if (!isNodeOfType(child, "JSXExpressionContainer")) continue;
28086
28266
  const expression = child.expression;
@@ -28138,37 +28318,22 @@ const rnNoSingleElementStyleArray = defineRule({
28138
28318
  } })
28139
28319
  });
28140
28320
  //#endregion
28141
- //#region src/plugin/rules/react-native/utils/scrollview_names.ts
28142
- const SCROLLVIEW_NAMES = new Set([
28143
- "ScrollView",
28144
- "FlatList",
28145
- "SectionList",
28146
- "VirtualizedList",
28147
- "KeyboardAwareScrollView"
28148
- ]);
28321
+ //#region src/plugin/utils/define-retired-rule.ts
28322
+ const defineRetiredRule = (rule) => defineRule({
28323
+ ...rule,
28324
+ defaultEnabled: false,
28325
+ lifecycle: "retired",
28326
+ create: () => ({})
28327
+ });
28149
28328
  //#endregion
28150
28329
  //#region src/plugin/rules/react-native/rn-prefer-content-inset-adjustment.ts
28151
- const rnPreferContentInsetAdjustment = defineRule({
28330
+ const rnPreferContentInsetAdjustment = defineRetiredRule({
28152
28331
  id: "rn-prefer-content-inset-adjustment",
28153
- title: "SafeAreaView instead of contentInsetAdjustment",
28332
+ title: "Manual safe-area inset adjustment",
28154
28333
  tags: ["test-noise"],
28155
28334
  requires: ["react-native"],
28156
28335
  severity: "warn",
28157
- recommendation: "Drop the SafeAreaView wrapper and set `contentInsetAdjustmentBehavior=\"automatic\"` on the ScrollView so the OS handles the safe area.",
28158
- create: (context) => ({ JSXElement(node) {
28159
- if (resolveJsxElementName(node.openingElement) !== "SafeAreaView") return;
28160
- for (const child of node.children ?? []) {
28161
- if (!isNodeOfType(child, "JSXElement")) continue;
28162
- const childName = resolveJsxElementName(child.openingElement);
28163
- if (!childName || !SCROLLVIEW_NAMES.has(childName)) continue;
28164
- if (childName === "KeyboardAwareScrollView") continue;
28165
- context.report({
28166
- node,
28167
- message: `Your users render an extra wrapper view from <SafeAreaView> around <${childName}>.`
28168
- });
28169
- return;
28170
- }
28171
- } })
28336
+ recommendation: "Prefer native content inset adjustment only when it replaces manual inset plumbing; SafeAreaView wrappers are valid and intentionally ignored."
28172
28337
  });
28173
28338
  //#endregion
28174
28339
  //#region src/react-native-dependency-names.ts
@@ -28554,6 +28719,15 @@ const rnPressableSharedValueMutation = defineRule({
28554
28719
  }
28555
28720
  });
28556
28721
  //#endregion
28722
+ //#region src/plugin/rules/react-native/utils/scrollview_names.ts
28723
+ const SCROLLVIEW_NAMES = new Set([
28724
+ "ScrollView",
28725
+ "FlatList",
28726
+ "SectionList",
28727
+ "VirtualizedList",
28728
+ "KeyboardAwareScrollView"
28729
+ ]);
28730
+ //#endregion
28557
28731
  //#region src/plugin/rules/react-native/rn-scrollview-dynamic-padding.ts
28558
28732
  const rnScrollviewDynamicPadding = defineRule({
28559
28733
  id: "rn-scrollview-dynamic-padding",
@@ -32249,7 +32423,7 @@ const isUseEffectEventSymbol = (symbol) => {
32249
32423
  const findEnclosingComponentOrHookFunction = (node) => {
32250
32424
  let current = node.parent;
32251
32425
  while (current) {
32252
- if (isFunctionLike$2(current)) {
32426
+ if (isFunctionLike$3(current)) {
32253
32427
  const resolvedName = inferFunctionName(current);
32254
32428
  if (resolvedName !== null && isReactComponentOrHookName(resolvedName)) return current;
32255
32429
  }
@@ -32270,7 +32444,7 @@ const isCallbackArgumentForAllowedEffectEventHook = (functionNode, additionalEff
32270
32444
  const isInsideAllowedEffectEventCallback = (node, additionalEffectHooksRegex) => {
32271
32445
  let current = node.parent;
32272
32446
  while (current) {
32273
- if (isFunctionLike$2(current) && isCallbackArgumentForAllowedEffectEventHook(current, additionalEffectHooksRegex)) return true;
32447
+ if (isFunctionLike$3(current) && isCallbackArgumentForAllowedEffectEventHook(current, additionalEffectHooksRegex)) return true;
32274
32448
  current = current.parent ?? null;
32275
32449
  }
32276
32450
  return false;
@@ -32608,7 +32782,7 @@ const containsAuthCheck = (rootNodes, allowedFunctionNames, genericMethodNames)
32608
32782
  let foundAuthCall = false;
32609
32783
  for (const rootNode of rootNodes) walkAst(rootNode, (child) => {
32610
32784
  if (foundAuthCall) return;
32611
- if (isFunctionLike$2(child)) return false;
32785
+ if (isFunctionLike$3(child)) return false;
32612
32786
  if (!isNodeOfType(child, "CallExpression")) return;
32613
32787
  if (getAuthCallName(child, allowedFunctionNames, genericMethodNames)) foundAuthCall = true;
32614
32788
  });
@@ -37222,7 +37396,7 @@ const reactDoctorRules = [
37222
37396
  rule: {
37223
37397
  ...rnAnimateLayoutProperty,
37224
37398
  framework: "react-native",
37225
- category: "Bugs",
37399
+ category: "Performance",
37226
37400
  tags: [...new Set(["react-native", ...rnAnimateLayoutProperty.tags ?? []])]
37227
37401
  }
37228
37402
  },
@@ -38087,7 +38261,7 @@ const appendNode = (builder, block, node) => {
38087
38261
  };
38088
38262
  const mapDescendantsToBlock = (builder, node, block) => {
38089
38263
  builder.nodeBlock.set(node, block);
38090
- if (isFunctionLike$2(node)) return;
38264
+ if (isFunctionLike$3(node)) return;
38091
38265
  const record = node;
38092
38266
  for (const key of Object.keys(record)) {
38093
38267
  if (key === "parent") continue;
@@ -38425,7 +38599,7 @@ const analyzeControlFlow = (program) => {
38425
38599
  body: program.body
38426
38600
  });
38427
38601
  const visit = (node) => {
38428
- if (isFunctionLike$2(node)) {
38602
+ if (isFunctionLike$3(node)) {
38429
38603
  const body = node.body;
38430
38604
  if (body) buildFor(node, body);
38431
38605
  }
@@ -38442,7 +38616,7 @@ const analyzeControlFlow = (program) => {
38442
38616
  const enclosingFunction = (node) => {
38443
38617
  let current = node;
38444
38618
  while (current) {
38445
- if (isFunctionLike$2(current)) return current;
38619
+ if (isFunctionLike$3(current)) return current;
38446
38620
  if (isNodeOfType(current, "Program")) return current;
38447
38621
  current = current.parent ?? null;
38448
38622
  }