oxlint-plugin-react-doctor 0.2.18-dev.4dc48d7 → 0.2.18-dev.67be206
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 +104 -78
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3954,6 +3954,7 @@ const noEmDashInJsxText = defineRule({
|
|
|
3954
3954
|
title: "Em dash in JSX text",
|
|
3955
3955
|
tags: ["design", "test-noise"],
|
|
3956
3956
|
severity: "warn",
|
|
3957
|
+
defaultEnabled: false,
|
|
3957
3958
|
category: "Architecture",
|
|
3958
3959
|
recommendation: "Replace em dashes in UI text with commas, colons, semicolons, or parentheses so the copy reads less like AI output.",
|
|
3959
3960
|
create: (context) => ({ JSXText(jsxTextNode) {
|
|
@@ -4000,6 +4001,7 @@ const noRedundantPaddingAxes = defineRule({
|
|
|
4000
4001
|
title: "Redundant padding axes",
|
|
4001
4002
|
tags: ["design", "test-noise"],
|
|
4002
4003
|
severity: "warn",
|
|
4004
|
+
defaultEnabled: false,
|
|
4003
4005
|
category: "Architecture",
|
|
4004
4006
|
recommendation: "Collapse `px-N py-N` to `p-N` when both sides match. Keep them split only when one side changes at a breakpoint (`py-2 md:py-3`).",
|
|
4005
4007
|
create: (context) => ({ JSXAttribute(jsxAttribute) {
|
|
@@ -4023,6 +4025,7 @@ const noRedundantSizeAxes = defineRule({
|
|
|
4023
4025
|
requires: ["tailwind:3.4"],
|
|
4024
4026
|
tags: ["design", "test-noise"],
|
|
4025
4027
|
severity: "warn",
|
|
4028
|
+
defaultEnabled: false,
|
|
4026
4029
|
category: "Architecture",
|
|
4027
4030
|
recommendation: "Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both sides match.",
|
|
4028
4031
|
create: (context) => ({ JSXAttribute(jsxAttribute) {
|
|
@@ -4046,6 +4049,7 @@ const noSpaceOnFlexChildren = defineRule({
|
|
|
4046
4049
|
title: "space-* utility on flex children",
|
|
4047
4050
|
tags: ["design", "test-noise"],
|
|
4048
4051
|
severity: "warn",
|
|
4052
|
+
defaultEnabled: false,
|
|
4049
4053
|
category: "Architecture",
|
|
4050
4054
|
recommendation: "Use `gap-*` on the flex or grid parent. `space-x-*` and `space-y-*` leave gaps when a child is hidden, miss spacing on wrapped lines, and don't flip in right-to-left layouts.",
|
|
4051
4055
|
create: (context) => ({ JSXAttribute(jsxAttribute) {
|
|
@@ -4079,6 +4083,7 @@ const noThreePeriodEllipsis = defineRule({
|
|
|
4079
4083
|
title: "Three dots instead of ellipsis",
|
|
4080
4084
|
tags: ["design", "test-noise"],
|
|
4081
4085
|
severity: "warn",
|
|
4086
|
+
defaultEnabled: false,
|
|
4082
4087
|
category: "Architecture",
|
|
4083
4088
|
recommendation: "Use the real ellipsis \"…\" (or `…`) instead of three dots. Good for labels like \"Rename…\" and \"Loading…\".",
|
|
4084
4089
|
create: (context) => ({ JSXText(jsxTextNode) {
|
|
@@ -4138,6 +4143,7 @@ const noVagueButtonLabel = defineRule({
|
|
|
4138
4143
|
title: "Vague button label",
|
|
4139
4144
|
tags: ["design", "test-noise"],
|
|
4140
4145
|
severity: "warn",
|
|
4146
|
+
defaultEnabled: false,
|
|
4141
4147
|
recommendation: "Name the action: \"Save changes\" instead of \"Continue\", \"Send invite\" instead of \"Submit\". The label is the button's accessible name.",
|
|
4142
4148
|
create: (context) => ({ JSXElement(jsxElementNode) {
|
|
4143
4149
|
const tagName = getOpeningElementTagName(jsxElementNode.openingElement);
|
|
@@ -11808,6 +11814,7 @@ const jsxPascalCase = defineRule({
|
|
|
11808
11814
|
id: "jsx-pascal-case",
|
|
11809
11815
|
title: "Component name not PascalCase",
|
|
11810
11816
|
severity: "warn",
|
|
11817
|
+
defaultEnabled: false,
|
|
11811
11818
|
tags: ["test-noise"],
|
|
11812
11819
|
recommendation: "Rename custom JSX components to PascalCase.",
|
|
11813
11820
|
category: "Architecture",
|
|
@@ -14266,6 +14273,7 @@ const noArrayIndexKey = defineRule({
|
|
|
14266
14273
|
id: "no-array-index-key",
|
|
14267
14274
|
title: "Array index used as a key",
|
|
14268
14275
|
severity: "warn",
|
|
14276
|
+
defaultEnabled: false,
|
|
14269
14277
|
recommendation: "Use a stable `key` from your data instead of the array index.",
|
|
14270
14278
|
category: "Performance",
|
|
14271
14279
|
create: (context) => ({
|
|
@@ -14778,6 +14786,37 @@ const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
|
|
|
14778
14786
|
//#region src/plugin/utils/is-setter-call.ts
|
|
14779
14787
|
const isSetterCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && isSetterIdentifier(node.callee.name);
|
|
14780
14788
|
//#endregion
|
|
14789
|
+
//#region src/plugin/utils/is-hook-binding-in-scope.ts
|
|
14790
|
+
const isHookBindingInScope = (node, query) => {
|
|
14791
|
+
const { bindingName, hookName, destructureIndex } = query;
|
|
14792
|
+
let cursor = node;
|
|
14793
|
+
while (cursor) {
|
|
14794
|
+
if (isNodeOfType(cursor, "BlockStatement") || isNodeOfType(cursor, "Program")) for (const statement of cursor.body ?? []) {
|
|
14795
|
+
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
14796
|
+
for (const declarator of statement.declarations ?? []) {
|
|
14797
|
+
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
14798
|
+
if (!isHookCall$1(declarator.init, hookName)) continue;
|
|
14799
|
+
if (destructureIndex !== void 0) {
|
|
14800
|
+
if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
|
|
14801
|
+
const elements = declarator.id.elements ?? [];
|
|
14802
|
+
if (elements.length <= destructureIndex) continue;
|
|
14803
|
+
const element = elements[destructureIndex];
|
|
14804
|
+
if (isNodeOfType(element, "Identifier") && element.name === bindingName) return true;
|
|
14805
|
+
} else if (isNodeOfType(declarator.id, "Identifier") && declarator.id.name === bindingName) return true;
|
|
14806
|
+
}
|
|
14807
|
+
}
|
|
14808
|
+
cursor = cursor.parent ?? null;
|
|
14809
|
+
}
|
|
14810
|
+
return false;
|
|
14811
|
+
};
|
|
14812
|
+
//#endregion
|
|
14813
|
+
//#region src/plugin/utils/is-use-state-setter-in-scope.ts
|
|
14814
|
+
const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node, {
|
|
14815
|
+
bindingName: setterName,
|
|
14816
|
+
hookName: "useState",
|
|
14817
|
+
destructureIndex: 1
|
|
14818
|
+
});
|
|
14819
|
+
//#endregion
|
|
14781
14820
|
//#region src/plugin/rules/state-and-effects/no-cascading-set-state.ts
|
|
14782
14821
|
const isAsyncFunctionLike = (node) => {
|
|
14783
14822
|
if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) return Boolean(node.async);
|
|
@@ -14819,7 +14858,7 @@ const countMaxPathSetStateCalls = (node) => {
|
|
|
14819
14858
|
const finallyCount = node.finalizer ? countMaxPathSetStateCalls(node.finalizer) : 0;
|
|
14820
14859
|
return Math.max(tryCount, catchCount) + finallyCount;
|
|
14821
14860
|
}
|
|
14822
|
-
if (isSetterCall(node)) {
|
|
14861
|
+
if (isNodeOfType(node, "CallExpression") && isSetterCall(node) && isNodeOfType(node.callee, "Identifier") && isUseStateSetterInScope(node, node.callee.name)) {
|
|
14823
14862
|
let nestedSettersInArgs = 0;
|
|
14824
14863
|
for (const argument of node.arguments ?? []) nestedSettersInArgs += countMaxPathSetStateCalls(argument);
|
|
14825
14864
|
return 1 + nestedSettersInArgs;
|
|
@@ -15405,6 +15444,7 @@ const noDarkModeGlow = defineRule({
|
|
|
15405
15444
|
title: "Colored glow on dark background",
|
|
15406
15445
|
tags: ["design", "test-noise"],
|
|
15407
15446
|
severity: "warn",
|
|
15447
|
+
defaultEnabled: false,
|
|
15408
15448
|
recommendation: "Use a subtle `box-shadow` in neutral colors for depth, or a faint `border`. Colored glows on dark backgrounds look overdone.",
|
|
15409
15449
|
create: (context) => ({ JSXAttribute(node) {
|
|
15410
15450
|
const expression = getInlineStyleExpression(node);
|
|
@@ -15439,6 +15479,7 @@ const noDefaultProps = defineRule({
|
|
|
15439
15479
|
requires: ["react:19"],
|
|
15440
15480
|
tags: ["test-noise"],
|
|
15441
15481
|
severity: "warn",
|
|
15482
|
+
defaultEnabled: false,
|
|
15442
15483
|
recommendation: "React 19 drops `Component.defaultProps` for function components. Set the defaults in the destructured props instead: `function Foo({ size = \"md\", variant = \"primary\" })` instead of `Foo.defaultProps = { size: \"md\", variant: \"primary\" }`.",
|
|
15443
15484
|
create: (context) => ({ AssignmentExpression(node) {
|
|
15444
15485
|
if (node.operator !== "=") return;
|
|
@@ -15618,7 +15659,11 @@ const noDerivedStateEffect = defineRule({
|
|
|
15618
15659
|
if (statements.length === 0) return;
|
|
15619
15660
|
if (!statements.every((statement) => {
|
|
15620
15661
|
if (!isNodeOfType(statement, "ExpressionStatement")) return false;
|
|
15621
|
-
|
|
15662
|
+
const expression = statement.expression;
|
|
15663
|
+
if (!isSetterCall(expression)) return false;
|
|
15664
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
15665
|
+
if (!isNodeOfType(expression.callee, "Identifier")) return false;
|
|
15666
|
+
return isUseStateSetterInScope(expression, expression.callee.name);
|
|
15622
15667
|
})) return;
|
|
15623
15668
|
let allArgumentsDeriveFromDeps = true;
|
|
15624
15669
|
let hasAnyDependencyReference = false;
|
|
@@ -17611,6 +17656,7 @@ const noGenericHandlerNames = defineRule({
|
|
|
17611
17656
|
id: "no-generic-handler-names",
|
|
17612
17657
|
title: "Vague event handler name",
|
|
17613
17658
|
severity: "warn",
|
|
17659
|
+
defaultEnabled: false,
|
|
17614
17660
|
tags: ["test-noise"],
|
|
17615
17661
|
recommendation: "Rename it to say what it does. For example `handleSubmit` could be `saveUserProfile`, and `handleClick` could be `toggleSidebar`.",
|
|
17616
17662
|
create: (context) => ({ JSXAttribute(node) {
|
|
@@ -17812,6 +17858,7 @@ const noGradientText = defineRule({
|
|
|
17812
17858
|
title: "Gradient text is hard to read",
|
|
17813
17859
|
tags: ["design", "test-noise"],
|
|
17814
17860
|
severity: "warn",
|
|
17861
|
+
defaultEnabled: false,
|
|
17815
17862
|
recommendation: "Use a solid text color so it stays readable. For emphasis, change the weight, size, or color instead of using a gradient.",
|
|
17816
17863
|
create: (context) => ({
|
|
17817
17864
|
JSXAttribute(node) {
|
|
@@ -18098,6 +18145,7 @@ const noJustifiedText = defineRule({
|
|
|
18098
18145
|
title: "Justified text without hyphens",
|
|
18099
18146
|
tags: ["test-noise"],
|
|
18100
18147
|
severity: "warn",
|
|
18148
|
+
defaultEnabled: false,
|
|
18101
18149
|
category: "Accessibility",
|
|
18102
18150
|
recommendation: "Use `text-align: left` for body text. If you must justify, add `hyphens: auto` and `overflow-wrap: break-word`.",
|
|
18103
18151
|
create: (context) => ({ JSXAttribute(node) {
|
|
@@ -20345,6 +20393,7 @@ const noPropTypes = defineRule({
|
|
|
20345
20393
|
requires: ["react:19"],
|
|
20346
20394
|
tags: ["test-noise"],
|
|
20347
20395
|
severity: "warn",
|
|
20396
|
+
defaultEnabled: false,
|
|
20348
20397
|
recommendation: "React 19 ignores `Component.propTypes`, so invalid props pass silently. Describe props with TypeScript types and add real runtime checks (or schema parsing) only where data can actually be wrong. Only runs on React 19+ projects.",
|
|
20349
20398
|
create: (context) => ({
|
|
20350
20399
|
AssignmentExpression(node) {
|
|
@@ -20373,6 +20422,7 @@ const noPureBlackBackground = defineRule({
|
|
|
20373
20422
|
title: "Pure black background",
|
|
20374
20423
|
tags: ["design", "test-noise"],
|
|
20375
20424
|
severity: "warn",
|
|
20425
|
+
defaultEnabled: false,
|
|
20376
20426
|
recommendation: "Nudge the background slightly toward your brand color, like `#0a0a0f` or Tailwind's `bg-gray-950`. Pure black looks harsh on modern screens.",
|
|
20377
20427
|
create: (context) => ({
|
|
20378
20428
|
JSXAttribute(node) {
|
|
@@ -21802,6 +21852,7 @@ const noSideTabBorder = defineRule({
|
|
|
21802
21852
|
title: "Thick one-sided border",
|
|
21803
21853
|
tags: ["design", "test-noise"],
|
|
21804
21854
|
severity: "warn",
|
|
21855
|
+
defaultEnabled: false,
|
|
21805
21856
|
recommendation: "Use a softer accent like an inset box-shadow, a background, or a thin border-bottom instead of a thick one-sided border.",
|
|
21806
21857
|
create: (context) => ({
|
|
21807
21858
|
JSXAttribute(node) {
|
|
@@ -23680,15 +23731,46 @@ const noUsememoSimpleExpression = defineRule({
|
|
|
23680
23731
|
});
|
|
23681
23732
|
//#endregion
|
|
23682
23733
|
//#region src/plugin/rules/design/no-wide-letter-spacing.ts
|
|
23734
|
+
const getJsxAttributeStringValue = (attributeValue) => {
|
|
23735
|
+
if (!attributeValue) return null;
|
|
23736
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return attributeValue.value;
|
|
23737
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
23738
|
+
const expression = attributeValue.expression;
|
|
23739
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
|
|
23740
|
+
}
|
|
23741
|
+
return null;
|
|
23742
|
+
};
|
|
23743
|
+
const isTruthyBooleanJsxAttribute = (attributeValue) => {
|
|
23744
|
+
if (attributeValue === null) return true;
|
|
23745
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
23746
|
+
const expression = attributeValue.expression;
|
|
23747
|
+
return isNodeOfType(expression, "Literal") && expression.value === true;
|
|
23748
|
+
}
|
|
23749
|
+
return false;
|
|
23750
|
+
};
|
|
23751
|
+
const hasUppercaseSiblingProp = (styleAttribute) => {
|
|
23752
|
+
const openingElement = styleAttribute.parent;
|
|
23753
|
+
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
23754
|
+
for (const attribute of openingElement.attributes ?? []) {
|
|
23755
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
23756
|
+
if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
|
|
23757
|
+
const attributeName = attribute.name.name;
|
|
23758
|
+
if (attributeName === "uppercase" && isTruthyBooleanJsxAttribute(attribute.value)) return true;
|
|
23759
|
+
if (attributeName === "textTransform" && getJsxAttributeStringValue(attribute.value) === "uppercase") return true;
|
|
23760
|
+
}
|
|
23761
|
+
return false;
|
|
23762
|
+
};
|
|
23683
23763
|
const noWideLetterSpacing = defineRule({
|
|
23684
23764
|
id: "no-wide-letter-spacing",
|
|
23685
23765
|
title: "Wide letter spacing on body text",
|
|
23686
23766
|
severity: "warn",
|
|
23767
|
+
defaultEnabled: false,
|
|
23687
23768
|
tags: ["test-noise"],
|
|
23688
23769
|
recommendation: "Save wide letter-spacing (over 0.05em) for short uppercase labels, nav items, and buttons, not body text.",
|
|
23689
23770
|
create: (context) => ({ JSXAttribute(node) {
|
|
23690
23771
|
const expression = getInlineStyleExpression(node);
|
|
23691
23772
|
if (!expression) return;
|
|
23773
|
+
if (hasUppercaseSiblingProp(node)) return;
|
|
23692
23774
|
let isUppercase = false;
|
|
23693
23775
|
let letterSpacingProperty = null;
|
|
23694
23776
|
let letterSpacingEm = null;
|
|
@@ -23766,6 +23848,7 @@ const noZIndex9999 = defineRule({
|
|
|
23766
23848
|
title: "Excessively high z-index",
|
|
23767
23849
|
tags: ["test-noise"],
|
|
23768
23850
|
severity: "warn",
|
|
23851
|
+
defaultEnabled: false,
|
|
23769
23852
|
recommendation: "Pick a small z-index scale, like dropdown 10, modal 20, toast 30. To layer something on top, use `isolation: isolate` instead of bigger numbers.",
|
|
23770
23853
|
create: (context) => ({
|
|
23771
23854
|
JSXAttribute(node) {
|
|
@@ -24541,6 +24624,7 @@ const preferEs6Class = defineRule({
|
|
|
24541
24624
|
id: "prefer-es6-class",
|
|
24542
24625
|
title: "createClass instead of ES6 class",
|
|
24543
24626
|
severity: "warn",
|
|
24627
|
+
defaultEnabled: false,
|
|
24544
24628
|
recommendation: "Pick one component style for the whole codebase: `class extends React.Component` (default) or `createReactClass` (legacy).",
|
|
24545
24629
|
category: "Architecture",
|
|
24546
24630
|
create: (context) => {
|
|
@@ -24851,6 +24935,7 @@ const preferModuleScopeStaticValue = defineRule({
|
|
|
24851
24935
|
tags: ["test-noise"],
|
|
24852
24936
|
severity: "warn",
|
|
24853
24937
|
category: "Architecture",
|
|
24938
|
+
disabledBy: ["react-compiler"],
|
|
24854
24939
|
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.",
|
|
24855
24940
|
create: (context) => ({ VariableDeclarator(node) {
|
|
24856
24941
|
if (!isNodeOfType(node.id, "Identifier")) return;
|
|
@@ -25918,7 +26003,9 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
25918
26003
|
const bodyStatements = isNodeOfType(callback.body, "BlockStatement") ? callback.body.body : [callback.body];
|
|
25919
26004
|
if (!bodyStatements || bodyStatements.length !== 1) return;
|
|
25920
26005
|
const soleStatement = bodyStatements[0];
|
|
25921
|
-
if (isNodeOfType(soleStatement, "ExpressionStatement")
|
|
26006
|
+
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
26007
|
+
const expression = soleStatement.expression;
|
|
26008
|
+
if (isSetterCall(expression) && isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && isUseStateSetterInScope(expression, expression.callee.name)) context.report({
|
|
25922
26009
|
node,
|
|
25923
26010
|
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
25924
26011
|
});
|
|
@@ -26441,6 +26528,7 @@ const rerenderFunctionalSetstate = defineRule({
|
|
|
26441
26528
|
if (!isSetterCall(node)) return;
|
|
26442
26529
|
if (!node.arguments?.length) return;
|
|
26443
26530
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
26531
|
+
if (!isUseStateSetterInScope(node, node.callee.name)) return;
|
|
26444
26532
|
const calleeName = node.callee.name;
|
|
26445
26533
|
const argument = node.arguments[0];
|
|
26446
26534
|
const expectedStateName = deriveStateVariableName(calleeName);
|
|
@@ -26775,7 +26863,7 @@ const handlerCallsSetState = (handler) => {
|
|
|
26775
26863
|
let setStateCall = null;
|
|
26776
26864
|
walkAst(handler.body, (child) => {
|
|
26777
26865
|
if (setStateCall) return;
|
|
26778
|
-
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && /^set[A-Z]/.test(child.callee.name)) setStateCall = child;
|
|
26866
|
+
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && /^set[A-Z]/.test(child.callee.name) && isUseStateSetterInScope(child, child.callee.name)) setStateCall = child;
|
|
26779
26867
|
});
|
|
26780
26868
|
return setStateCall;
|
|
26781
26869
|
};
|
|
@@ -26807,76 +26895,22 @@ const rerenderTransitionsScroll = defineRule({
|
|
|
26807
26895
|
} })
|
|
26808
26896
|
});
|
|
26809
26897
|
//#endregion
|
|
26898
|
+
//#region src/plugin/utils/define-retired-rule.ts
|
|
26899
|
+
const defineRetiredRule = (rule) => defineRule({
|
|
26900
|
+
...rule,
|
|
26901
|
+
defaultEnabled: false,
|
|
26902
|
+
lifecycle: "retired",
|
|
26903
|
+
create: () => ({})
|
|
26904
|
+
});
|
|
26905
|
+
//#endregion
|
|
26810
26906
|
//#region src/plugin/rules/react-native/rn-animate-layout-property.ts
|
|
26811
|
-
const
|
|
26812
|
-
"width",
|
|
26813
|
-
"height",
|
|
26814
|
-
"top",
|
|
26815
|
-
"left",
|
|
26816
|
-
"right",
|
|
26817
|
-
"bottom",
|
|
26818
|
-
"minWidth",
|
|
26819
|
-
"minHeight",
|
|
26820
|
-
"maxWidth",
|
|
26821
|
-
"maxHeight",
|
|
26822
|
-
"margin",
|
|
26823
|
-
"marginTop",
|
|
26824
|
-
"marginBottom",
|
|
26825
|
-
"marginLeft",
|
|
26826
|
-
"marginRight",
|
|
26827
|
-
"marginHorizontal",
|
|
26828
|
-
"marginVertical",
|
|
26829
|
-
"padding",
|
|
26830
|
-
"paddingTop",
|
|
26831
|
-
"paddingBottom",
|
|
26832
|
-
"paddingLeft",
|
|
26833
|
-
"paddingRight",
|
|
26834
|
-
"paddingHorizontal",
|
|
26835
|
-
"paddingVertical",
|
|
26836
|
-
"flex",
|
|
26837
|
-
"flexBasis",
|
|
26838
|
-
"flexGrow",
|
|
26839
|
-
"flexShrink",
|
|
26840
|
-
"borderWidth",
|
|
26841
|
-
"borderTopWidth",
|
|
26842
|
-
"borderBottomWidth",
|
|
26843
|
-
"borderLeftWidth",
|
|
26844
|
-
"borderRightWidth",
|
|
26845
|
-
"fontSize",
|
|
26846
|
-
"lineHeight",
|
|
26847
|
-
"letterSpacing"
|
|
26848
|
-
]);
|
|
26849
|
-
const findReturnedObject = (callback) => {
|
|
26850
|
-
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return null;
|
|
26851
|
-
const body = callback.body;
|
|
26852
|
-
if (isNodeOfType(body, "ObjectExpression")) return body;
|
|
26853
|
-
if (!isNodeOfType(body, "BlockStatement")) return null;
|
|
26854
|
-
for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isNodeOfType(stmt.argument, "ObjectExpression")) return stmt.argument;
|
|
26855
|
-
return null;
|
|
26856
|
-
};
|
|
26857
|
-
const rnAnimateLayoutProperty = defineRule({
|
|
26907
|
+
const rnAnimateLayoutProperty = defineRetiredRule({
|
|
26858
26908
|
id: "rn-animate-layout-property",
|
|
26859
26909
|
title: "Animating a layout property",
|
|
26860
26910
|
tags: ["test-noise"],
|
|
26861
26911
|
requires: ["react-native"],
|
|
26862
|
-
severity: "
|
|
26863
|
-
recommendation: "
|
|
26864
|
-
create: (context) => ({ CallExpression(node) {
|
|
26865
|
-
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "useAnimatedStyle") return;
|
|
26866
|
-
const callback = node.arguments?.[0];
|
|
26867
|
-
if (!callback) return;
|
|
26868
|
-
const returnedObject = findReturnedObject(callback);
|
|
26869
|
-
if (!returnedObject) return;
|
|
26870
|
-
for (const property of returnedObject.properties ?? []) {
|
|
26871
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
26872
|
-
if (!isNodeOfType(property.key, "Identifier")) continue;
|
|
26873
|
-
if (!REANIMATED_LAYOUT_KEYS.has(property.key.name)) continue;
|
|
26874
|
-
context.report({
|
|
26875
|
-
node: property,
|
|
26876
|
-
message: `Your users see stutter when useAnimatedStyle animates "${property.key.name}" on the layout thread.`
|
|
26877
|
-
});
|
|
26878
|
-
}
|
|
26879
|
-
} })
|
|
26912
|
+
severity: "warn",
|
|
26913
|
+
recommendation: "Reanimated useAnimatedStyle runs on the UI thread; layout-affecting properties driven by animation helpers, interpolate, or shared values are valid."
|
|
26880
26914
|
});
|
|
26881
26915
|
//#endregion
|
|
26882
26916
|
//#region src/plugin/rules/react-native/rn-animation-reaction-as-derived.ts
|
|
@@ -28077,7 +28111,7 @@ const findSetStateInBody = (body) => {
|
|
|
28077
28111
|
let setStateCallNode = null;
|
|
28078
28112
|
walkAst(body, (child) => {
|
|
28079
28113
|
if (setStateCallNode) return;
|
|
28080
|
-
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && SET_STATE_PATTERN.test(child.callee.name)) setStateCallNode = child;
|
|
28114
|
+
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && SET_STATE_PATTERN.test(child.callee.name) && isUseStateSetterInScope(child, child.callee.name)) setStateCallNode = child;
|
|
28081
28115
|
});
|
|
28082
28116
|
return setStateCallNode;
|
|
28083
28117
|
};
|
|
@@ -28203,14 +28237,6 @@ const rnNoSingleElementStyleArray = defineRule({
|
|
|
28203
28237
|
} })
|
|
28204
28238
|
});
|
|
28205
28239
|
//#endregion
|
|
28206
|
-
//#region src/plugin/utils/define-retired-rule.ts
|
|
28207
|
-
const defineRetiredRule = (rule) => defineRule({
|
|
28208
|
-
...rule,
|
|
28209
|
-
defaultEnabled: false,
|
|
28210
|
-
lifecycle: "retired",
|
|
28211
|
-
create: () => ({})
|
|
28212
|
-
});
|
|
28213
|
-
//#endregion
|
|
28214
28240
|
//#region src/plugin/rules/react-native/rn-prefer-content-inset-adjustment.ts
|
|
28215
28241
|
const rnPreferContentInsetAdjustment = defineRetiredRule({
|
|
28216
28242
|
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.
|
|
3
|
+
"version": "0.2.18-dev.67be206",
|
|
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",
|