oxlint-plugin-react-doctor 0.7.6-dev.dfd47a7 → 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 +65 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13653,6 +13653,28 @@ const jsAsyncReduceWithoutAwaitedAcc = defineRule({
13653
13653
  } })
13654
13654
  });
13655
13655
  //#endregion
13656
+ //#region src/plugin/utils/unwrap-discarded-expression.ts
13657
+ const unwrapDiscardedExpression = (node) => {
13658
+ let expression = isNodeOfType(node, "ExpressionStatement") ? node.expression : node;
13659
+ for (;;) {
13660
+ expression = stripParenExpression(expression);
13661
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") {
13662
+ expression = expression.argument;
13663
+ continue;
13664
+ }
13665
+ if (isNodeOfType(expression, "SequenceExpression")) {
13666
+ const expressions = expression.expressions ?? [];
13667
+ const finalExpression = expressions.at(-1);
13668
+ const prefixExpressions = expressions.slice(0, -1);
13669
+ if (finalExpression && prefixExpressions.length > 0 && prefixExpressions.every((prefixExpression) => isNodeOfType(stripParenExpression(prefixExpression), "Literal"))) {
13670
+ expression = finalExpression;
13671
+ continue;
13672
+ }
13673
+ }
13674
+ return expression;
13675
+ }
13676
+ };
13677
+ //#endregion
13656
13678
  //#region src/plugin/rules/js-performance/js-batch-dom-css.ts
13657
13679
  const ITERATOR_METHOD_NAMES$2 = new Set([
13658
13680
  "forEach",
@@ -13837,9 +13859,19 @@ const hasAttachmentBefore = (scopeOwner, elementName, beforeStart) => {
13837
13859
  });
13838
13860
  return foundAttachment;
13839
13861
  };
13862
+ const getStyleAssignment = (node) => {
13863
+ if (!isNodeOfType(node, "ExpressionStatement")) return null;
13864
+ const expression = unwrapDiscardedExpression(node);
13865
+ if (!isNodeOfType(expression, "AssignmentExpression")) return null;
13866
+ if (!isNodeOfType(expression.left, "MemberExpression")) return null;
13867
+ if (!isNodeOfType(expression.left.object, "MemberExpression")) return null;
13868
+ if (!isNodeOfType(expression.left.object.property, "Identifier")) return null;
13869
+ return expression.left.object.property.name === "style" ? expression : null;
13870
+ };
13840
13871
  const isProvablyDetachedAtWrite = (styleWriteStatement) => {
13841
- if (!isNodeOfType(styleWriteStatement, "ExpressionStatement") || !isNodeOfType(styleWriteStatement.expression, "AssignmentExpression") || !isNodeOfType(styleWriteStatement.expression.left, "MemberExpression") || !isNodeOfType(styleWriteStatement.expression.left.object, "MemberExpression")) return false;
13842
- const elementExpression = styleWriteStatement.expression.left.object.object;
13872
+ const assignment = getStyleAssignment(styleWriteStatement);
13873
+ if (!assignment || !isNodeOfType(assignment.left, "MemberExpression") || !isNodeOfType(assignment.left.object, "MemberExpression")) return false;
13874
+ const elementExpression = assignment.left.object.object;
13843
13875
  const creationRoot = resolveDetachedCreationRoot(elementExpression, 0);
13844
13876
  if (!creationRoot) return false;
13845
13877
  return !hasAttachmentBefore(creationRoot.scopeOwner, creationRoot.rootName, getNodeStart$1(styleWriteStatement));
@@ -13851,15 +13883,17 @@ const jsBatchDomCss = defineRule({
13851
13883
  severity: "warn",
13852
13884
  recommendation: "Do all your reads first, then all your writes. Mixing them inside a loop makes the browser recalculate the layout again and again, which is slow",
13853
13885
  create: (context) => {
13854
- const isStyleAssignment = (node) => isNodeOfType(node, "ExpressionStatement") && isNodeOfType(node.expression, "AssignmentExpression") && isNodeOfType(node.expression.left, "MemberExpression") && isNodeOfType(node.expression.left.object, "MemberExpression") && isNodeOfType(node.expression.left.object.property, "Identifier") && node.expression.left.object.property.name === "style";
13855
- const writesLayoutAffectingProperty = (node) => isNodeOfType(node, "ExpressionStatement") && isNodeOfType(node.expression, "AssignmentExpression") && isNodeOfType(node.expression.left, "MemberExpression") && isNodeOfType(node.expression.left.property, "Identifier") && !LAYOUT_NEUTRAL_STYLE_PROPERTY_NAMES.has(node.expression.left.property.name);
13886
+ const writesLayoutAffectingProperty = (node) => {
13887
+ const assignment = getStyleAssignment(node);
13888
+ return assignment !== null && isNodeOfType(assignment.left, "MemberExpression") && isNodeOfType(assignment.left.property, "Identifier") && !LAYOUT_NEUTRAL_STYLE_PROPERTY_NAMES.has(assignment.left.property.name);
13889
+ };
13856
13890
  return { BlockStatement(node) {
13857
13891
  const perIterationBody = findEnclosingPerIterationBody(node);
13858
13892
  if (!perIterationBody) return;
13859
13893
  const statements = node.body ?? [];
13860
13894
  let layoutReads = null;
13861
13895
  for (let statementIndex = 1; statementIndex < statements.length; statementIndex++) {
13862
- if (!isStyleAssignment(statements[statementIndex]) || !isStyleAssignment(statements[statementIndex - 1])) continue;
13896
+ if (getStyleAssignment(statements[statementIndex]) === null || getStyleAssignment(statements[statementIndex - 1]) === null) continue;
13863
13897
  if (!writesLayoutAffectingProperty(statements[statementIndex]) && !writesLayoutAffectingProperty(statements[statementIndex - 1])) continue;
13864
13898
  layoutReads ??= scanPerIterationLayoutReads(perIterationBody);
13865
13899
  if (!layoutReads.hasUsedLayoutRead || layoutReads.hasDeliberateForcedReflow) return;
@@ -15257,9 +15291,21 @@ const isSmallFixedListMember = (receiver) => {
15257
15291
  };
15258
15292
  const getResolvedInitializer = (receiver) => {
15259
15293
  if (!isNodeOfType(receiver, "Identifier")) return null;
15260
- const initializer = findVariableInitializer(receiver, receiver.name)?.initializer ?? null;
15261
- if (initializer && isNodeOfType(initializer, "Identifier")) return findVariableInitializer(initializer, initializer.name)?.initializer ?? initializer;
15262
- return initializer;
15294
+ const binding = findVariableInitializer(receiver, receiver.name);
15295
+ const initializer = binding?.initializer ?? null;
15296
+ if (!binding || !initializer) return null;
15297
+ const isDefault = isNodeOfType(binding.bindingIdentifier.parent, "AssignmentPattern");
15298
+ if (isNodeOfType(initializer, "Identifier")) {
15299
+ const aliased = findVariableInitializer(initializer, initializer.name);
15300
+ if (aliased?.initializer) return {
15301
+ initializer: aliased.initializer,
15302
+ isDefault: isDefault || isNodeOfType(aliased.bindingIdentifier.parent, "AssignmentPattern")
15303
+ };
15304
+ }
15305
+ return {
15306
+ initializer,
15307
+ isDefault
15308
+ };
15263
15309
  };
15264
15310
  const isSplitCall = (expression) => {
15265
15311
  if (!expression) return false;
@@ -15425,8 +15471,8 @@ const isBoundedConstantCollection = (collection) => {
15425
15471
  if (isScreamingSnakeCaseConstantReceiver(stripped)) return true;
15426
15472
  if (isSmallInlineLiteralArray(stripped)) return true;
15427
15473
  if (isNodeOfType(stripped, "Identifier")) {
15428
- const initializer = getResolvedInitializer(stripped);
15429
- if (initializer && isSmallInlineLiteralArray(initializer)) return true;
15474
+ const resolved = getResolvedInitializer(stripped);
15475
+ if (resolved && !resolved.isDefault && isSmallInlineLiteralArray(resolved.initializer)) return true;
15430
15476
  }
15431
15477
  return false;
15432
15478
  };
@@ -15478,8 +15524,8 @@ const jsSetMapLookups = defineRule({
15478
15524
  if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
15479
15525
  const resolvedInitializer = getResolvedInitializer(receiver);
15480
15526
  if (resolvedInitializer) {
15481
- if (isLikelyStringReceiver(resolvedInitializer)) return;
15482
- if (isSmallInlineLiteralArray(resolvedInitializer)) return;
15527
+ if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
15528
+ if (!resolvedInitializer.isDefault && isSmallInlineLiteralArray(resolvedInitializer.initializer)) return;
15483
15529
  }
15484
15530
  if (isStringElementOfSplitIteration(receiver)) return;
15485
15531
  if (isReceiverDeclaredInNearestLoop(receiver, node)) return;
@@ -28875,7 +28921,7 @@ const findTopLevelEffectCalls = (componentBody) => {
28875
28921
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
28876
28922
  for (const statement of componentBody.body ?? []) {
28877
28923
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
28878
- const expression = statement.expression;
28924
+ const expression = unwrapDiscardedExpression(statement);
28879
28925
  if (!isNodeOfType(expression, "CallExpression")) continue;
28880
28926
  if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
28881
28927
  effectCalls.push(expression);
@@ -33107,7 +33153,7 @@ const noMirrorPropEffect = defineRule({
33107
33153
  if (mirrorBindings.length === 0) return;
33108
33154
  for (const statement of componentBody.body ?? []) {
33109
33155
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
33110
- const effectCall = statement.expression;
33156
+ const effectCall = unwrapDiscardedExpression(statement);
33111
33157
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
33112
33158
  if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
33113
33159
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
@@ -33121,7 +33167,7 @@ const noMirrorPropEffect = defineRule({
33121
33167
  const bodyStatements = getCallbackStatements(callback);
33122
33168
  if (bodyStatements.length !== 1) continue;
33123
33169
  const onlyStatement = bodyStatements[0];
33124
- const expression = isNodeOfType(onlyStatement, "ExpressionStatement") ? onlyStatement.expression : onlyStatement;
33170
+ const expression = unwrapDiscardedExpression(onlyStatement);
33125
33171
  if (!isNodeOfType(expression, "CallExpression")) continue;
33126
33172
  if (!isNodeOfType(expression.callee, "Identifier")) continue;
33127
33173
  if (!isSetterIdentifier(expression.callee.name)) continue;
@@ -37222,7 +37268,7 @@ const isNonSettlingSetterArgument = (setterCall, stateName) => {
37222
37268
  return expressionReadsStateValue(argument, stateName);
37223
37269
  };
37224
37270
  const getUnconditionalSetterCall = (statement, setterNames) => {
37225
- const expression = stripParenExpression(isNodeOfType(statement, "ExpressionStatement") ? statement.expression : statement);
37271
+ const expression = unwrapDiscardedExpression(statement);
37226
37272
  if (!isNodeOfType(expression, "CallExpression")) return null;
37227
37273
  if (!isNodeOfType(expression.callee, "Identifier")) return null;
37228
37274
  if (!setterNames.has(expression.callee.name)) return null;
@@ -37571,7 +37617,7 @@ const noSelfUpdatingEffect = defineRule({
37571
37617
  const setterNames = new Set(setterNameToStateName.keys());
37572
37618
  for (const statement of functionBody.body ?? []) {
37573
37619
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
37574
- const effectCall = statement.expression;
37620
+ const effectCall = unwrapDiscardedExpression(statement);
37575
37621
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
37576
37622
  if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
37577
37623
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
@@ -45996,8 +46042,8 @@ const rnAnimationReactionAsDerived = defineRule({
45996
46042
  if (statements.length !== 1) return;
45997
46043
  const onlyStatement = statements[0];
45998
46044
  if (!isNodeOfType(onlyStatement, "ExpressionStatement")) return;
45999
- singleAssignment = onlyStatement.expression;
46000
- } else if (body) singleAssignment = body;
46045
+ singleAssignment = unwrapDiscardedExpression(onlyStatement);
46046
+ } else if (body) singleAssignment = unwrapDiscardedExpression(body);
46001
46047
  if (!singleAssignment) return;
46002
46048
  const isValueAssignment = isNodeOfType(singleAssignment, "AssignmentExpression") && isNodeOfType(singleAssignment.left, "MemberExpression") && isNodeOfType(singleAssignment.left.object, "Identifier") && isNodeOfType(singleAssignment.left.property, "Identifier") && singleAssignment.left.property.name === "value";
46003
46049
  const isSetCall = isNodeOfType(singleAssignment, "CallExpression") && isNodeOfType(singleAssignment.callee, "MemberExpression") && isNodeOfType(singleAssignment.callee.property, "Identifier") && singleAssignment.callee.property.name === "set" && (singleAssignment.arguments?.length ?? 0) === 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.6-dev.dfd47a7",
3
+ "version": "0.7.6-dev.e026a23",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",