Skip to content

Commit b99a113

Browse files
committed
BREAKING CHANGE: add custom rule for native driver use with Animated to performance rules
1 parent 72ae38d commit b99a113

File tree

7 files changed

+183
-6
lines changed

7 files changed

+183
-6
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Animated, ScrollView, Text } from "react-native";
2+
3+
const fadeAnim = new Animated.Value(0);
4+
5+
Animated.timing(fadeAnim, {
6+
toValue: 1,
7+
duration: 500,
8+
useNativeDriver: false, // This line breaks the custom rule
9+
}).start();
10+
11+
export const CustomScrollView = () => {
12+
return (
13+
<ScrollView
14+
onScroll={Animated.event([], {
15+
useNativeDriver: false,
16+
})}
17+
>
18+
<Text>{"Something to scroll"}</Text>
19+
</ScrollView>
20+
);
21+
};

packages/eslint-plugin/README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,14 @@ This plugin exports some custom rules that you can optionally use in your projec
107107
🧪 Set in the `tests` configuration.\
108108
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
109109

110-
| Name | Description | 💼 | 🔧 |
111-
| :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | :-- | :-- |
112-
| [await-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/await-user-event.md) | Enforces awaiting userEvent calls | 🧪 | 🔧 |
113-
| [no-different-displayname](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-different-displayname.md) | Enforce component displayName to match with component name || 🔧 |
114-
| [prefer-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/prefer-user-event.md) | Enforces usage of userEvent over fireEvent in tests. | | 🔧 |
115-
| [require-named-effect](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/require-named-effect.md) | Enforces the use of named functions inside a useEffect | | |
110+
| Name | Description | 💼 | 🔧 |
111+
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :--------------------- | :-- |
112+
| [await-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/await-user-event.md) | Enforces awaiting userEvent calls | 🧪 | 🔧 |
113+
| [no-different-displayname](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-different-displayname.md) | Enforce component displayName to match with component name || 🔧 |
114+
| [no-animated-without-native-driver](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-animated-without-native-driver.md) | Disallow the use of `Animated` with `useNativeDriver: false` | ![badge-performance][] | |
115+
| [prefer-user-event](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/prefer-user-event.md) | Enforces usage of userEvent over fireEvent in tests. | | 🔧 |
116+
| [require-named-effect](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/require-named-effect.md) | Enforces the use of named functions inside a useEffect | | |
117+
| Name                              |
116118

117119
<!-- end auto-generated rules list -->
118120

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Disallow the use of `Animated` with `useNativeDriver: false` (`@bam.tech/no-animated-without-native-driver`)
2+
3+
💼 This rule is enabled in the `performance` config.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
Enforces the usage of native driver when using `Animated` from `react-native` to improve performance.
8+
9+
## Rule details
10+
11+
Example of **incorrect** code for this rule:
12+
13+
```jsx
14+
Animated.timing(fadeAnim, {
15+
toValue: 1,
16+
duration: 500,
17+
useNativeDriver: false,
18+
}).start();
19+
```
20+
21+
Example of **correct** code for this rule:
22+
23+
```jsx
24+
Animated.timing(fadeAnim, {
25+
toValue: 1,
26+
duration: 500,
27+
useNativeDriver: true,
28+
}).start();
29+
```

packages/eslint-plugin/lib/configs/performance.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const performanceConfig = defineConfig({
3131
],
3232
},
3333
],
34+
"@bam.tech/no-animated-without-native-driver": "error",
3435
},
3536
overrides: [
3637
{

packages/eslint-plugin/lib/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { awaitUserEventRule } from "./await-user-event";
22
import { noDifferentDisplaynameRule } from "./no-different-displayname";
3+
import { noAnimatedWithoutNativeDriverRule } from "./no-animated-without-native-driver";
34
import { preferUserEventRule } from "./prefer-user-event";
45
import { requireNamedEffectRule } from "./require-named-effect";
56

@@ -8,4 +9,5 @@ export default {
89
"prefer-user-event": preferUserEventRule,
910
"require-named-effect": requireNamedEffectRule,
1011
"no-different-displayname": noDifferentDisplaynameRule,
12+
"no-animated-without-native-driver": noAnimatedWithoutNativeDriverRule,
1113
};
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import type { Rule } from "eslint";
2+
3+
// Custom Rule: No Animated with useNativeDriver: false
4+
export const noAnimatedWithoutNativeDriverRule: Rule.RuleModule = {
5+
meta: {
6+
type: "problem",
7+
docs: {
8+
description:
9+
"Disallow the use of `Animated` with `useNativeDriver: false`",
10+
category: "Possible Errors",
11+
recommended: true,
12+
url: "https://github.com/bamlab/react-native-project-config/tree/main/packages/eslint-plugin/docs/rules/no-animated-without-native-driver.md",
13+
},
14+
messages: {
15+
noNativeDriverFalse:
16+
"Do not use Animated with useNativeDriver: false. Always set useNativeDriver: true for better performance.",
17+
},
18+
schema: [],
19+
},
20+
21+
create(context) {
22+
return {
23+
CallExpression(node) {
24+
// Check if the node is a call to `Animated` object
25+
if (
26+
node.callee.type === "MemberExpression" &&
27+
node.callee.object.type === "Identifier" &&
28+
node.callee.object.name === "Animated"
29+
) {
30+
// Handle the case: Animated.someMethod(..., { useNativeDriver: false })
31+
if (
32+
node.arguments.length > 0 &&
33+
node.arguments[1].type === "ObjectExpression"
34+
) {
35+
const useNativeDriverPropertyIsFalse =
36+
node.arguments[1].properties.find(
37+
(prop) =>
38+
prop.type === "Property" &&
39+
prop.key.type === "Identifier" &&
40+
prop.key.name === "useNativeDriver" &&
41+
prop.value.type === "Literal" &&
42+
prop.value.value === false,
43+
);
44+
45+
if (useNativeDriverPropertyIsFalse) {
46+
context.report({
47+
node: useNativeDriverPropertyIsFalse,
48+
messageId: "noNativeDriverFalse",
49+
});
50+
}
51+
}
52+
53+
// Handle the case: Animated.event([...], { useNativeDriver: false })
54+
if (
55+
node.callee.property.type === "Identifier" &&
56+
node.callee.property.name === "event" &&
57+
node.arguments.length > 1 &&
58+
node.arguments[1].type === "ObjectExpression"
59+
) {
60+
const useNativeDriverPropertyIsFalse =
61+
node.arguments[1].properties.find(
62+
(prop) =>
63+
prop.type === "Property" &&
64+
prop.key.type === "Identifier" &&
65+
prop.key.name === "useNativeDriver" &&
66+
prop.value.type === "Literal" &&
67+
prop.value.value === false,
68+
);
69+
70+
if (useNativeDriverPropertyIsFalse) {
71+
context.report({
72+
node: useNativeDriverPropertyIsFalse,
73+
messageId: "noNativeDriverFalse",
74+
});
75+
}
76+
}
77+
}
78+
},
79+
};
80+
},
81+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Save without formatting: [⌘ + K] > [S]
2+
3+
// This should trigger an error breaking eslint-plugin-bam-custom-rules:
4+
// bam-custom-rules/no-animated-without-native-driver
5+
6+
import { noAnimatedWithoutNativeDriverRule } from "../../../lib/rules/no-animated-without-native-driver";
7+
import { RuleTester } from "eslint";
8+
9+
const ruleTester = new RuleTester({
10+
parser: require.resolve("@typescript-eslint/parser"),
11+
});
12+
13+
const valid = [
14+
`Animated.timing(fadeAnim, {
15+
toValue: 1,
16+
duration: 500,
17+
useNativeDriver: true,
18+
}).start();`,
19+
];
20+
21+
const invalid = [
22+
`Animated.timing(fadeAnim, {
23+
toValue: 1,
24+
duration: 500,
25+
useNativeDriver: false,
26+
}).start();`,
27+
];
28+
29+
ruleTester.run(
30+
"no-animated-without-native-driver",
31+
noAnimatedWithoutNativeDriverRule,
32+
{
33+
valid,
34+
invalid: invalid.map((code) => ({
35+
code,
36+
errors: [
37+
"Do not use Animated with useNativeDriver: false. Always set useNativeDriver: true for better performance.",
38+
],
39+
})),
40+
},
41+
);

0 commit comments

Comments
 (0)