oxlint-plugin-react-doctor 0.2.18-dev.8e7fb33 → 0.2.18-dev.abbb042

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 +41 -73
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -23680,6 +23680,35 @@ const noUsememoSimpleExpression = defineRule({
23680
23680
  });
23681
23681
  //#endregion
23682
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
+ };
23683
23712
  const noWideLetterSpacing = defineRule({
23684
23713
  id: "no-wide-letter-spacing",
23685
23714
  title: "Wide letter spacing on body text",
@@ -23689,6 +23718,7 @@ const noWideLetterSpacing = defineRule({
23689
23718
  create: (context) => ({ JSXAttribute(node) {
23690
23719
  const expression = getInlineStyleExpression(node);
23691
23720
  if (!expression) return;
23721
+ if (hasUppercaseSiblingProp(node)) return;
23692
23722
  let isUppercase = false;
23693
23723
  let letterSpacingProperty = null;
23694
23724
  let letterSpacingEm = null;
@@ -26808,76 +26838,22 @@ const rerenderTransitionsScroll = defineRule({
26808
26838
  } })
26809
26839
  });
26810
26840
  //#endregion
26841
+ //#region src/plugin/utils/define-retired-rule.ts
26842
+ const defineRetiredRule = (rule) => defineRule({
26843
+ ...rule,
26844
+ defaultEnabled: false,
26845
+ lifecycle: "retired",
26846
+ create: () => ({})
26847
+ });
26848
+ //#endregion
26811
26849
  //#region src/plugin/rules/react-native/rn-animate-layout-property.ts
26812
- const REANIMATED_LAYOUT_KEYS = new Set([
26813
- "width",
26814
- "height",
26815
- "top",
26816
- "left",
26817
- "right",
26818
- "bottom",
26819
- "minWidth",
26820
- "minHeight",
26821
- "maxWidth",
26822
- "maxHeight",
26823
- "margin",
26824
- "marginTop",
26825
- "marginBottom",
26826
- "marginLeft",
26827
- "marginRight",
26828
- "marginHorizontal",
26829
- "marginVertical",
26830
- "padding",
26831
- "paddingTop",
26832
- "paddingBottom",
26833
- "paddingLeft",
26834
- "paddingRight",
26835
- "paddingHorizontal",
26836
- "paddingVertical",
26837
- "flex",
26838
- "flexBasis",
26839
- "flexGrow",
26840
- "flexShrink",
26841
- "borderWidth",
26842
- "borderTopWidth",
26843
- "borderBottomWidth",
26844
- "borderLeftWidth",
26845
- "borderRightWidth",
26846
- "fontSize",
26847
- "lineHeight",
26848
- "letterSpacing"
26849
- ]);
26850
- const findReturnedObject = (callback) => {
26851
- if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return null;
26852
- const body = callback.body;
26853
- if (isNodeOfType(body, "ObjectExpression")) return body;
26854
- if (!isNodeOfType(body, "BlockStatement")) return null;
26855
- for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isNodeOfType(stmt.argument, "ObjectExpression")) return stmt.argument;
26856
- return null;
26857
- };
26858
- const rnAnimateLayoutProperty = defineRule({
26850
+ const rnAnimateLayoutProperty = defineRetiredRule({
26859
26851
  id: "rn-animate-layout-property",
26860
26852
  title: "Animating a layout property",
26861
26853
  tags: ["test-noise"],
26862
26854
  requires: ["react-native"],
26863
- severity: "error",
26864
- 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.",
26865
- create: (context) => ({ CallExpression(node) {
26866
- if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "useAnimatedStyle") return;
26867
- const callback = node.arguments?.[0];
26868
- if (!callback) return;
26869
- const returnedObject = findReturnedObject(callback);
26870
- if (!returnedObject) return;
26871
- for (const property of returnedObject.properties ?? []) {
26872
- if (!isNodeOfType(property, "Property")) continue;
26873
- if (!isNodeOfType(property.key, "Identifier")) continue;
26874
- if (!REANIMATED_LAYOUT_KEYS.has(property.key.name)) continue;
26875
- context.report({
26876
- node: property,
26877
- message: `Your users see stutter when useAnimatedStyle animates "${property.key.name}" on the layout thread.`
26878
- });
26879
- }
26880
- } })
26855
+ severity: "warn",
26856
+ recommendation: "Reanimated useAnimatedStyle runs on the UI thread; layout-affecting properties driven by animation helpers, interpolate, or shared values are valid."
26881
26857
  });
26882
26858
  //#endregion
26883
26859
  //#region src/plugin/rules/react-native/rn-animation-reaction-as-derived.ts
@@ -28204,14 +28180,6 @@ const rnNoSingleElementStyleArray = defineRule({
28204
28180
  } })
28205
28181
  });
28206
28182
  //#endregion
28207
- //#region src/plugin/utils/define-retired-rule.ts
28208
- const defineRetiredRule = (rule) => defineRule({
28209
- ...rule,
28210
- defaultEnabled: false,
28211
- lifecycle: "retired",
28212
- create: () => ({})
28213
- });
28214
- //#endregion
28215
28183
  //#region src/plugin/rules/react-native/rn-prefer-content-inset-adjustment.ts
28216
28184
  const rnPreferContentInsetAdjustment = defineRetiredRule({
28217
28185
  id: "rn-prefer-content-inset-adjustment",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.2.18-dev.8e7fb33",
3
+ "version": "0.2.18-dev.abbb042",
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",