Skip to content

Commit 021a4b8

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

7 files changed

Lines changed: 184 additions & 6 deletions

File tree

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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
6+
7+
<!-- end auto-generated rule header -->
8+
9+
Enforces the usage of native driver when using `Animated` from `react-native` to improve performance.
10+
11+
## Rule details
12+
13+
Example of **incorrect** code for this rule:
14+
15+
```jsx
16+
Animated.timing(fadeAnim, {
17+
toValue: 1,
18+
duration: 500,
19+
useNativeDriver: false,
20+
}).start();
21+
```
22+
23+
Example of **correct** code for this rule:
24+
25+
```jsx
26+
Animated.timing(fadeAnim, {
27+
toValue: 1,
28+
duration: 500,
29+
useNativeDriver: true,
30+
}).start();
31+
```

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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
fixable: "code",
20+
},
21+
22+
create(context) {
23+
return {
24+
CallExpression(node) {
25+
// Check if the node is a call to `Animated` object
26+
if (
27+
node.callee.type === "MemberExpression" &&
28+
node.callee.object.type === "Identifier" &&
29+
node.callee.object.name === "Animated"
30+
) {
31+
// Handle the case: Animated.someMethod(..., { useNativeDriver: false })
32+
if (
33+
node.arguments.length > 0 &&
34+
node.arguments[1].type === "ObjectExpression"
35+
) {
36+
const useNativeDriverProperty = 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 (useNativeDriverProperty) {
46+
context.report({
47+
node: useNativeDriverProperty,
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 useNativeDriverProperty = node.arguments[1].properties.find(
61+
(prop) =>
62+
prop.type === "Property" &&
63+
prop.key.type === "Identifier" &&
64+
prop.key.name === "useNativeDriver" &&
65+
prop.value.type === "Literal" &&
66+
prop.value.value === false,
67+
);
68+
69+
if (useNativeDriverProperty) {
70+
context.report({
71+
node: useNativeDriverProperty,
72+
messageId: "noNativeDriverFalse",
73+
});
74+
}
75+
}
76+
}
77+
},
78+
};
79+
},
80+
};
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)