oxlint-plugin-react-doctor 0.2.18-dev.6851a78 → 0.2.18-dev.68a0bef

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.d.ts CHANGED
@@ -5047,7 +5047,7 @@ declare const REACT_DOCTOR_RULES: readonly [{
5047
5047
  readonly originallyExternal: false;
5048
5048
  readonly rule: {
5049
5049
  readonly framework: "react-native";
5050
- readonly category: "Performance";
5050
+ readonly category: "Bugs";
5051
5051
  readonly tags: readonly string[];
5052
5052
  readonly id: string;
5053
5053
  readonly title?: string;
@@ -11345,7 +11345,7 @@ declare const RULES: readonly [{
11345
11345
  readonly originallyExternal: false;
11346
11346
  readonly rule: {
11347
11347
  readonly framework: "react-native";
11348
- readonly category: "Performance";
11348
+ readonly category: "Bugs";
11349
11349
  readonly tags: readonly string[];
11350
11350
  readonly id: string;
11351
11351
  readonly title?: string;
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$3 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
2577
+ const isFunctionLike$2 = (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$3(child)) return false;
2600
+ if (child !== block && isFunctionLike$2(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$3(descendant)) return false;
3057
+ if (isFunctionLike$2(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$3(node)) return;
3063
+ if (!isFunctionLike$2(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$3(node)) return;
4512
+ if (isFunctionLike$2(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$3(child) && !isIteratorCallbackArgument(child)) return false;
4600
+ if (child !== node && isFunctionLike$2(child) && !isIteratorCallbackArgument(child)) return false;
4601
4601
  if (isReleaseLikeCall(child, knownCleanupFunctionNames, knownBoundSubscriptionNames)) {
4602
4602
  didFindRelease = true;
4603
4603
  return false;
@@ -4606,7 +4606,7 @@ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubs
4606
4606
  return didFindRelease;
4607
4607
  };
4608
4608
  const isCleanupFunctionLike = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
4609
- if (!isFunctionLike$3(node)) return false;
4609
+ if (!isFunctionLike$2(node)) return false;
4610
4610
  return containsReleaseLikeCall(node.body, knownCleanupFunctionNames, knownBoundSubscriptionNames);
4611
4611
  };
4612
4612
  const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames, options = {}) => {
@@ -4870,7 +4870,7 @@ const recordReference = (state, identifier, flag) => {
4870
4870
  };
4871
4871
  const isFunctionBodyBlock = (block) => {
4872
4872
  if (!block.parent) return false;
4873
- return isFunctionLike$3(block.parent);
4873
+ return isFunctionLike$2(block.parent);
4874
4874
  };
4875
4875
  const isCatchClauseBlock = (block) => block.parent !== null && block.parent !== void 0 && block.parent.type === "CatchClause";
4876
4876
  const handleVariableDeclaration = (declaration, state) => {
@@ -5025,7 +5025,7 @@ const walkParameterReferences = (pattern, state) => {
5025
5025
  if (isNodeOfType(pattern, "RestElement")) walkParameterReferences(pattern.argument, state);
5026
5026
  };
5027
5027
  const walk = (node, state) => {
5028
- if (isFunctionLike$3(node)) {
5028
+ if (isFunctionLike$2(node)) {
5029
5029
  if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
5030
5030
  setNodeScope(node, state);
5031
5031
  const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
@@ -5271,7 +5271,7 @@ const closureCaptures = (functionNode, scopes) => {
5271
5271
  const out = [];
5272
5272
  const seen = /* @__PURE__ */ new Set();
5273
5273
  const visit = (node) => {
5274
- if (node !== functionNode && isFunctionLike$3(node)) {
5274
+ if (node !== functionNode && isFunctionLike$2(node)) {
5275
5275
  const innerCaptures = closureCaptures(node, scopes);
5276
5276
  for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
5277
5277
  if (!seen.has(reference.id)) {
@@ -7391,7 +7391,7 @@ const isAtomFromJotai = (callExpression) => {
7391
7391
  if (!isImportedFromModule(callExpression, localName, "jotai")) return false;
7392
7392
  return getImportedNameFromModule(callExpression, localName, "jotai") === "atom";
7393
7393
  };
7394
- const isFunctionLike$2 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")));
7394
+ const isFunctionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")));
7395
7395
  const getFirstParameterName = (fn) => {
7396
7396
  const parameters = fn.params ?? [];
7397
7397
  if (parameters.length !== 1) return null;
@@ -7510,7 +7510,7 @@ const jotaiDerivedAtomReturnsFreshObject = defineRule({
7510
7510
  const args = node.arguments ?? [];
7511
7511
  if (args.length === 0) return;
7512
7512
  const reader = args[0];
7513
- if (!isFunctionLike$2(reader)) return;
7513
+ if (!isFunctionLike$1(reader)) return;
7514
7514
  const getParameterName = getFirstParameterName(reader);
7515
7515
  if (!getParameterName) return;
7516
7516
  const freshReturn = getFreshReturnForFunction(reader);
@@ -7766,7 +7766,7 @@ const isInsideLoopContext = (node) => {
7766
7766
  let current = node.parent;
7767
7767
  while (current) {
7768
7768
  if (isNodeOfType(current, "ForStatement") || isNodeOfType(current, "ForInStatement") || isNodeOfType(current, "ForOfStatement") || isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return true;
7769
- if (isFunctionLike$3(current)) {
7769
+ if (isFunctionLike$2(current)) {
7770
7770
  if (isIteratorCallback(current)) return true;
7771
7771
  return false;
7772
7772
  }
@@ -8127,7 +8127,7 @@ const jsHoistIntl = defineRule({
8127
8127
  let cursor = node.parent ?? null;
8128
8128
  let inFunctionBody = false;
8129
8129
  while (cursor) {
8130
- if (isFunctionLike$3(cursor)) {
8130
+ if (isFunctionLike$2(cursor)) {
8131
8131
  inFunctionBody = true;
8132
8132
  const fnParent = cursor.parent;
8133
8133
  if (fnParent && isNodeOfType(fnParent, "CallExpression") && fnParent.arguments?.[0] === cursor) {
@@ -13082,7 +13082,7 @@ const collectChainedGetHandlerBodies = (initNode) => {
13082
13082
  };
13083
13083
  const resolveBodiesFromExpression = (expression, resolveBinding, remainingDepth) => {
13084
13084
  if (remainingDepth <= 0) return [];
13085
- if (isFunctionLike$3(expression)) return expression.body ? [expression.body] : [];
13085
+ if (isFunctionLike$2(expression)) return expression.body ? [expression.body] : [];
13086
13086
  if (isNodeOfType(expression, "CallExpression")) {
13087
13087
  for (const callArgument of expression.arguments ?? []) {
13088
13088
  if (isNodeOfType(callArgument, "ArrowFunctionExpression") || isNodeOfType(callArgument, "FunctionExpression")) {
@@ -13552,7 +13552,7 @@ const getEffectFn = (analysis, node) => {
13552
13552
  if (isNodeOfType(fn, "ArrowFunctionExpression") || isNodeOfType(fn, "FunctionExpression")) return fn;
13553
13553
  if (isNodeOfType(fn, "Identifier")) {
13554
13554
  const definitionNode = getRef(analysis, fn)?.resolved?.defs[0]?.node;
13555
- if (definitionNode && isFunctionLike$3(definitionNode)) return definitionNode;
13555
+ if (definitionNode && isFunctionLike$2(definitionNode)) return definitionNode;
13556
13556
  if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
13557
13557
  const initializer = definitionNode.init;
13558
13558
  if (isNodeOfType(initializer, "ArrowFunctionExpression") || isNodeOfType(initializer, "FunctionExpression")) return initializer;
@@ -13645,14 +13645,14 @@ const getUseStateDecl = (analysis, ref) => {
13645
13645
  return node ?? null;
13646
13646
  };
13647
13647
  const isCleanupReturnArgument = (analysis, node) => {
13648
- if (isFunctionLike$3(node)) return true;
13648
+ if (isFunctionLike$2(node)) return true;
13649
13649
  if (isNodeOfType(node, "MemberExpression")) return true;
13650
13650
  if (isNodeOfType(node, "Identifier")) {
13651
13651
  const definitionNode = getRef(analysis, node)?.resolved?.defs[0]?.node;
13652
- if (definitionNode && isFunctionLike$3(definitionNode)) return true;
13652
+ if (definitionNode && isFunctionLike$2(definitionNode)) return true;
13653
13653
  if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
13654
13654
  const initializer = definitionNode.init;
13655
- return isFunctionLike$3(initializer);
13655
+ return isFunctionLike$2(initializer);
13656
13656
  }
13657
13657
  }
13658
13658
  if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
@@ -13662,7 +13662,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
13662
13662
  if (visited.has(node)) return false;
13663
13663
  visited.add(node);
13664
13664
  if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
13665
- if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$3(node)) return false;
13665
+ if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$2(node)) return false;
13666
13666
  const record = node;
13667
13667
  for (const [key, value] of Object.entries(record)) {
13668
13668
  if (key === "parent") continue;
@@ -13674,7 +13674,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
13674
13674
  };
13675
13675
  const hasCleanup = (analysis, node) => {
13676
13676
  const fn = getEffectFn(analysis, node);
13677
- if (!isFunctionLike$3(fn)) return false;
13677
+ if (!isFunctionLike$2(fn)) return false;
13678
13678
  if (!isNodeOfType(fn.body, "BlockStatement")) return false;
13679
13679
  return hasCleanupReturn(analysis, fn.body);
13680
13680
  };
@@ -14024,7 +14024,7 @@ const isInsideStaticPlaceholderMap = (node) => {
14024
14024
  let current = node;
14025
14025
  while (current.parent) {
14026
14026
  const parent = current.parent;
14027
- if (isFunctionLike$3(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
14027
+ if (isFunctionLike$2(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
14028
14028
  const callee = parent.callee;
14029
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);
14030
14030
  if (isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current) return isArrayFromLengthObjectCall(parent);
@@ -14043,7 +14043,7 @@ const findIteratorItemName$1 = (node) => {
14043
14043
  let current = node;
14044
14044
  while (current.parent) {
14045
14045
  const parent = current.parent;
14046
- if (isFunctionLike$3(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
14046
+ if (isFunctionLike$2(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
14047
14047
  const callee = parent.callee;
14048
14048
  const isIteratorMethodCall = isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach");
14049
14049
  const isArrayFromCallback = isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current;
@@ -14988,7 +14988,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
14988
14988
  const nearestEnclosingFunction = (node) => {
14989
14989
  let cursor = node.parent;
14990
14990
  while (cursor) {
14991
- if (isFunctionLike$3(cursor)) return cursor;
14991
+ if (isFunctionLike$2(cursor)) return cursor;
14992
14992
  cursor = cursor.parent ?? null;
14993
14993
  }
14994
14994
  return null;
@@ -15671,7 +15671,7 @@ const extractDestructuredPropNames = (params) => {
15671
15671
  };
15672
15672
  const getInlineFunctionNode = (node) => {
15673
15673
  if (!node) return null;
15674
- if (isFunctionLike$3(node)) return node;
15674
+ if (isFunctionLike$2(node)) return node;
15675
15675
  if (!isNodeOfType(node, "CallExpression")) return null;
15676
15676
  for (const argument of node.arguments ?? []) {
15677
15677
  const inlineFunctionNode = getInlineFunctionNode(argument);
@@ -15682,7 +15682,7 @@ const getInlineFunctionNode = (node) => {
15682
15682
  const getNearestComponentFunction = (node) => {
15683
15683
  let cursor = node.parent ?? null;
15684
15684
  while (cursor) {
15685
- if (isFunctionLike$3(cursor)) return cursor;
15685
+ if (isFunctionLike$2(cursor)) return cursor;
15686
15686
  cursor = cursor.parent ?? null;
15687
15687
  }
15688
15688
  return null;
@@ -16012,7 +16012,7 @@ const collectFunctionLocalBindings = (functionNode) => {
16012
16012
  const walkComponentRespectingShadows = (node, shadowedStateNames, visit) => {
16013
16013
  if (!node || typeof node !== "object") return;
16014
16014
  let nextShadowedStateNames = shadowedStateNames;
16015
- if (isFunctionLike$3(node)) {
16015
+ if (isFunctionLike$2(node)) {
16016
16016
  const localBindings = collectFunctionLocalBindings(node);
16017
16017
  if (localBindings.size > 0) {
16018
16018
  const merged = new Set(shadowedStateNames);
@@ -18269,7 +18269,7 @@ const isInsideClassBody = (node) => {
18269
18269
  let current = node.parent;
18270
18270
  while (current) {
18271
18271
  if (isNodeOfType(current, "ClassBody")) return true;
18272
- if (isFunctionLike$3(current)) return false;
18272
+ if (isFunctionLike$2(current)) return false;
18273
18273
  current = current.parent;
18274
18274
  }
18275
18275
  return false;
@@ -18973,7 +18973,7 @@ const isLodashMutatorCall = (callExpression) => {
18973
18973
  };
18974
18974
  //#endregion
18975
18975
  //#region src/plugin/utils/find-exported-function-body.ts
18976
- const isFunctionLike$1 = (node) => {
18976
+ const isFunctionLike = (node) => {
18977
18977
  if (!node) return false;
18978
18978
  return isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression");
18979
18979
  };
@@ -18988,7 +18988,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
18988
18988
  if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
18989
18989
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
18990
18990
  const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
18991
- if (initializer && isFunctionLike$1(initializer)) localBindings.set(declarator.id.name, initializer);
18991
+ if (initializer && isFunctionLike(initializer)) localBindings.set(declarator.id.name, initializer);
18992
18992
  }
18993
18993
  };
18994
18994
  for (const statement of programRoot.body ?? []) {
@@ -19032,7 +19032,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
19032
19032
  defaultExport = declaration;
19033
19033
  continue;
19034
19034
  }
19035
- if (isFunctionLike$1(declaration)) {
19035
+ if (isFunctionLike(declaration)) {
19036
19036
  defaultExport = declaration;
19037
19037
  continue;
19038
19038
  }
@@ -19193,7 +19193,7 @@ const resolveCrossFileFunctionExport = (fromFilename, source, exportedName) => {
19193
19193
  const resolveReducerFunction = (node, currentFilename) => {
19194
19194
  if (!node) return null;
19195
19195
  const unwrappedNode = stripParenExpression(node);
19196
- if (isFunctionLike$3(unwrappedNode)) return {
19196
+ if (isFunctionLike$2(unwrappedNode)) return {
19197
19197
  functionNode: unwrappedNode,
19198
19198
  crossFileSourceDisplay: null
19199
19199
  };
@@ -19201,7 +19201,7 @@ const resolveReducerFunction = (node, currentFilename) => {
19201
19201
  const initializer = findVariableInitializer(unwrappedNode, unwrappedNode.name)?.initializer;
19202
19202
  if (!initializer) return null;
19203
19203
  const unwrappedInitializer = stripParenExpression(initializer);
19204
- if (isFunctionLike$3(unwrappedInitializer)) return {
19204
+ if (isFunctionLike$2(unwrappedInitializer)) return {
19205
19205
  functionNode: unwrappedInitializer,
19206
19206
  crossFileSourceDisplay: null
19207
19207
  };
@@ -19315,11 +19315,11 @@ const canExpressionReturnOriginalReducerStateReference = (node, state) => {
19315
19315
  return false;
19316
19316
  };
19317
19317
  const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
19318
- if (isFunctionLike$3(node)) return [];
19318
+ if (isFunctionLike$2(node)) return [];
19319
19319
  const mutations = [];
19320
19320
  walkAst(node, (child) => {
19321
19321
  const unwrappedChild = stripParenExpression(child);
19322
- if (child !== node && isFunctionLike$3(unwrappedChild)) return false;
19322
+ if (child !== node && isFunctionLike$2(unwrappedChild)) return false;
19323
19323
  if (isNodeOfType(unwrappedChild, "AssignmentExpression")) {
19324
19324
  if (isNodeOfType(stripParenExpression(unwrappedChild.left), "MemberExpression") && isExpressionRootedInMutableReducerStateSource(unwrappedChild.left, state)) mutations.push({ node: unwrappedChild });
19325
19325
  return;
@@ -19424,7 +19424,7 @@ const updateReducerStateIdentityForIdentifierAssignment = (assignment, state) =>
19424
19424
  if (isExpressionReachableFromOriginalReducerState(assignment.right, state)) state.mutableStateSourceNames.add(name);
19425
19425
  };
19426
19426
  const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, reportedNodes, options) => {
19427
- if (!isFunctionLike$3(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return;
19427
+ if (!isFunctionLike$2(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return;
19428
19428
  const firstParam = functionNode.params?.[0];
19429
19429
  const stateName = isNodeOfType(firstParam, "Identifier") ? firstParam.name : isNodeOfType(firstParam, "AssignmentPattern") && isNodeOfType(firstParam.left, "Identifier") ? firstParam.left.name : null;
19430
19430
  if (!stateName) return;
@@ -21273,7 +21273,7 @@ const isTanStackServerFnHandler = (node) => {
21273
21273
  const isInsideServerOnlyScope = (node) => {
21274
21274
  let currentNode = node.parent ?? null;
21275
21275
  while (currentNode) {
21276
- if (isFunctionLike$3(currentNode)) {
21276
+ if (isFunctionLike$2(currentNode)) {
21277
21277
  if (hasUseServerDirective(currentNode) || isTanStackServerFnHandler(currentNode)) return true;
21278
21278
  }
21279
21279
  currentNode = currentNode.parent ?? null;
@@ -23428,7 +23428,7 @@ const isReactClassComponent = (classNode) => {
23428
23428
  const findEnclosingComponent = (node) => {
23429
23429
  let walker = node.parent;
23430
23430
  while (walker) {
23431
- if (isFunctionLike$3(walker)) {
23431
+ if (isFunctionLike$2(walker)) {
23432
23432
  const componentName = inferFunctionLikeName(walker);
23433
23433
  if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
23434
23434
  component: walker,
@@ -25694,7 +25694,7 @@ const reduxUseselectorInlineDerivation = defineRule({
25694
25694
  if (!body) return;
25695
25695
  const returnedExpressions = [];
25696
25696
  if (isNodeOfType(body, "BlockStatement")) walkAst(body, (node) => {
25697
- if (node !== body && isFunctionLike$3(node)) return false;
25697
+ if (node !== body && isFunctionLike$2(node)) return false;
25698
25698
  if (isNodeOfType(node, "ReturnStatement")) {
25699
25699
  if (node.argument) returnedExpressions.push(node.argument);
25700
25700
  return false;
@@ -26037,7 +26037,7 @@ const hasOwnAwait = (functionBody) => {
26037
26037
  let found = false;
26038
26038
  walkAst(functionBody, (child) => {
26039
26039
  if (found) return;
26040
- if (child !== functionBody && isFunctionLike$3(child)) return false;
26040
+ if (child !== functionBody && isFunctionLike$2(child)) return false;
26041
26041
  if (isNodeOfType(child, "AwaitExpression")) found = true;
26042
26042
  });
26043
26043
  return found;
@@ -26056,7 +26056,7 @@ const setterIsCalledInAsyncContext = (componentBody, setterName) => {
26056
26056
  let found = false;
26057
26057
  walkAst(componentBody, (child) => {
26058
26058
  if (found) return;
26059
- if (!isFunctionLike$3(child)) return;
26059
+ if (!isFunctionLike$2(child)) return;
26060
26060
  const functionBody = child.body;
26061
26061
  if (!(Boolean(child.async) || hasOwnAwait(functionBody))) return;
26062
26062
  if (callsIdentifier(functionBody, setterName)) found = true;
@@ -26839,8 +26839,7 @@ const rerenderTransitionsScroll = defineRule({
26839
26839
  });
26840
26840
  //#endregion
26841
26841
  //#region src/plugin/rules/react-native/rn-animate-layout-property.ts
26842
- const REANIMATED_MODULE = "react-native-reanimated";
26843
- const REANIMATED_LAYOUT_STYLE_PROPERTIES = new Set([
26842
+ const REANIMATED_LAYOUT_KEYS = new Set([
26844
26843
  "width",
26845
26844
  "height",
26846
26845
  "top",
@@ -26878,91 +26877,12 @@ const REANIMATED_LAYOUT_STYLE_PROPERTIES = new Set([
26878
26877
  "lineHeight",
26879
26878
  "letterSpacing"
26880
26879
  ]);
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
- };
26950
26880
  const findReturnedObject = (callback) => {
26951
26881
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return null;
26952
- const body = stripParenExpression(callback.body);
26882
+ const body = callback.body;
26953
26883
  if (isNodeOfType(body, "ObjectExpression")) return body;
26954
26884
  if (!isNodeOfType(body, "BlockStatement")) return null;
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;
26885
+ for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isNodeOfType(stmt.argument, "ObjectExpression")) return stmt.argument;
26966
26886
  return null;
26967
26887
  };
26968
26888
  const rnAnimateLayoutProperty = defineRule({
@@ -26970,25 +26890,21 @@ const rnAnimateLayoutProperty = defineRule({
26970
26890
  title: "Animating a layout property",
26971
26891
  tags: ["test-noise"],
26972
26892
  requires: ["react-native"],
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.",
26893
+ severity: "error",
26894
+ 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.",
26976
26895
  create: (context) => ({ CallExpression(node) {
26977
- if (!isUseAnimatedStyleCallee(node.callee, context)) return;
26896
+ if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "useAnimatedStyle") return;
26978
26897
  const callback = node.arguments?.[0];
26979
26898
  if (!callback) return;
26980
26899
  const returnedObject = findReturnedObject(callback);
26981
26900
  if (!returnedObject) return;
26982
26901
  for (const property of returnedObject.properties ?? []) {
26983
26902
  if (!isNodeOfType(property, "Property")) 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;
26903
+ if (!isNodeOfType(property.key, "Identifier")) continue;
26904
+ if (!REANIMATED_LAYOUT_KEYS.has(property.key.name)) continue;
26989
26905
  context.report({
26990
26906
  node: property,
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.`
26907
+ message: `Your users see stutter when useAnimatedStyle animates "${property.key.name}" on the layout thread.`
26992
26908
  });
26993
26909
  }
26994
26910
  } })
@@ -32423,7 +32339,7 @@ const isUseEffectEventSymbol = (symbol) => {
32423
32339
  const findEnclosingComponentOrHookFunction = (node) => {
32424
32340
  let current = node.parent;
32425
32341
  while (current) {
32426
- if (isFunctionLike$3(current)) {
32342
+ if (isFunctionLike$2(current)) {
32427
32343
  const resolvedName = inferFunctionName(current);
32428
32344
  if (resolvedName !== null && isReactComponentOrHookName(resolvedName)) return current;
32429
32345
  }
@@ -32444,7 +32360,7 @@ const isCallbackArgumentForAllowedEffectEventHook = (functionNode, additionalEff
32444
32360
  const isInsideAllowedEffectEventCallback = (node, additionalEffectHooksRegex) => {
32445
32361
  let current = node.parent;
32446
32362
  while (current) {
32447
- if (isFunctionLike$3(current) && isCallbackArgumentForAllowedEffectEventHook(current, additionalEffectHooksRegex)) return true;
32363
+ if (isFunctionLike$2(current) && isCallbackArgumentForAllowedEffectEventHook(current, additionalEffectHooksRegex)) return true;
32448
32364
  current = current.parent ?? null;
32449
32365
  }
32450
32366
  return false;
@@ -32782,7 +32698,7 @@ const containsAuthCheck = (rootNodes, allowedFunctionNames, genericMethodNames)
32782
32698
  let foundAuthCall = false;
32783
32699
  for (const rootNode of rootNodes) walkAst(rootNode, (child) => {
32784
32700
  if (foundAuthCall) return;
32785
- if (isFunctionLike$3(child)) return false;
32701
+ if (isFunctionLike$2(child)) return false;
32786
32702
  if (!isNodeOfType(child, "CallExpression")) return;
32787
32703
  if (getAuthCallName(child, allowedFunctionNames, genericMethodNames)) foundAuthCall = true;
32788
32704
  });
@@ -37396,7 +37312,7 @@ const reactDoctorRules = [
37396
37312
  rule: {
37397
37313
  ...rnAnimateLayoutProperty,
37398
37314
  framework: "react-native",
37399
- category: "Performance",
37315
+ category: "Bugs",
37400
37316
  tags: [...new Set(["react-native", ...rnAnimateLayoutProperty.tags ?? []])]
37401
37317
  }
37402
37318
  },
@@ -38261,7 +38177,7 @@ const appendNode = (builder, block, node) => {
38261
38177
  };
38262
38178
  const mapDescendantsToBlock = (builder, node, block) => {
38263
38179
  builder.nodeBlock.set(node, block);
38264
- if (isFunctionLike$3(node)) return;
38180
+ if (isFunctionLike$2(node)) return;
38265
38181
  const record = node;
38266
38182
  for (const key of Object.keys(record)) {
38267
38183
  if (key === "parent") continue;
@@ -38599,7 +38515,7 @@ const analyzeControlFlow = (program) => {
38599
38515
  body: program.body
38600
38516
  });
38601
38517
  const visit = (node) => {
38602
- if (isFunctionLike$3(node)) {
38518
+ if (isFunctionLike$2(node)) {
38603
38519
  const body = node.body;
38604
38520
  if (body) buildFor(node, body);
38605
38521
  }
@@ -38616,7 +38532,7 @@ const analyzeControlFlow = (program) => {
38616
38532
  const enclosingFunction = (node) => {
38617
38533
  let current = node;
38618
38534
  while (current) {
38619
- if (isFunctionLike$3(current)) return current;
38535
+ if (isFunctionLike$2(current)) return current;
38620
38536
  if (isNodeOfType(current, "Program")) return current;
38621
38537
  current = current.parent ?? null;
38622
38538
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.2.18-dev.6851a78",
3
+ "version": "0.2.18-dev.68a0bef",
4
4
  "description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",