Skip to content

Commit

Permalink
BREAKING CHANGE: refactor -> create custom rule for import of useIsFo…
Browse files Browse the repository at this point in the history
…cused to prevent from override by import eslint rule
  • Loading branch information
fannytavart committed Sep 3, 2024
1 parent dd0cd55 commit dc465fd
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Save without formatting: [⌘ + K] > [S]

// This should trigger one error breaking eslint-plugin-react-native:
// no-restricted-imports
// This should trigger one error breaking custom performance rule:
// @bam.tech/no-use-is-focused

import { useIsFocused } from "@react-navigation/native";
import { Text } from "react-native";
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ This plugin exports some custom rules that you can optionally use in your projec
| [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 || | 🔧 |
| [no-flatlist](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-flatlist.md) | Disallow importing `FlatList` from `react-native` due to potential performance concerns or the preference for alternative components. | ![badge-performance][] | | 🔧 |
| [no-react-navigation-stack](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-react-navigation-stack.md) | Disallow importing from `@react-navigation/stack` and suggest using `@react-navigation/native-stack` instead. | ![badge-performance][] | | 🔧 |
| [no-use-is-focused](https://github.com/bamlab/react-native-project-config/blob/main/packages/eslint-plugin/docs/rules/no-use-is-focused.md) | Disallow importing `useIsFocused` from `@react-navigation/native` to encourage using `useFocusEffect` instead. | ![badge-performance][] | | 🔧 |
| [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. | | | 🔧 |
| [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 | | | |

Expand Down
17 changes: 17 additions & 0 deletions packages/eslint-plugin/docs/rules/no-use-is-focused.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Disallow importing `useIsFocused` from `@react-navigation/native` to encourage using `useFocusEffect` instead (`@bam.tech/no-use-is-focused`)

💼 This rule is enabled in the `performance` config.

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->

Prevents from using "useIsFocused" to avoid performance issues. "useFocusEffect" should be used instead.

## Rule details

Examples of **incorrect** code for this rule:

```jsx
import { useIsFocused } from "@react-navigation/native";
```
14 changes: 1 addition & 13 deletions packages/eslint-plugin/lib/configs/performance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,12 @@ import { defineConfig } from "eslint-define-config";

export const performanceConfig = defineConfig({
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "@react-navigation/native",
importNames: ["useIsFocused"],
message:
"Please use useFocusEffect instead of useIsFocused to avoid excessive rerenders.",
},
],
},
],
"@bam.tech/no-animated-without-native-driver": "error",
"@bam.tech/avoid-intl-number-format": "error",
"@bam.tech/avoid-react-native-svg": "warn",
"@bam.tech/no-flatlist": "error",
"@bam.tech/no-react-navigation-stack": "error",
"@bam.tech/no-use-is-focused": "error",
},
overrides: [
{
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/lib/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { preferUserEventRule } from "./prefer-user-event";
import { requireNamedEffectRule } from "./require-named-effect";
import { noFlatListImportRule } from "./no-flatlist";
import { noReactNavigationStackImportRule } from "./no-react-navigation-stack";
import { noUseIsFocusedImportRule } from "./no-use-is-focused";

export default {
"await-user-event": awaitUserEventRule,
Expand All @@ -18,4 +19,5 @@ export default {
"avoid-react-native-svg": avoidReactNativeSvgImportRule,
"no-flatlist": noFlatListImportRule,
"no-react-navigation-stack": noReactNavigationStackImportRule,
"no-use-is-focused": noUseIsFocusedImportRule,
};
86 changes: 86 additions & 0 deletions packages/eslint-plugin/lib/rules/no-use-is-focused.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { Rule } from "eslint";
import type { ImportDeclaration, CallExpression, Property } from "estree";

// Custom Rule: No Import of useIsFocused from @react-navigation/native
export const noUseIsFocusedImportRule: Rule.RuleModule = {
meta: {
type: "problem",
docs: {
description:
"Disallow importing `useIsFocused` from `@react-navigation/native` to encourage using `useFocusEffect` instead.",
category: "Best Practices",
recommended: true,
url: "https://github.com/bamlab/react-native-project-config/tree/main/packages/eslint-plugin/docs/rules/no-use-is-focused.md",
},
messages: {
noUseIsFocusedImport:
"Please use 'useFocusEffect' instead of 'useIsFocused' to avoid excessive rerenders.",
},
schema: [],
fixable: "code",
},

create(context) {
return {
ImportDeclaration(node: ImportDeclaration) {
if (node.source.value === "@react-navigation/native") {
node.specifiers.forEach((specifier) => {
if (
specifier.type === "ImportSpecifier" &&
specifier.imported.name === "useIsFocused"
) {
context.report({
node: specifier,
messageId: "noUseIsFocusedImport",
fix(fixer) {
return fixer.replaceText(
specifier.imported,
"useFocusEffect",
);
},
});
}
});
}
},
CallExpression(node: CallExpression) {
if (
node.callee.type === "Identifier" &&
node.callee.name === "require" &&
node.arguments.length > 0 &&
node.arguments[0].type === "Literal" &&
node.arguments[0].value === "@react-navigation/native"
) {
const ancestors = context.getAncestors();
const parent = ancestors[ancestors.length - 1]; // Get the direct parent of the node

if (
parent.type === "VariableDeclarator" &&
parent.id.type === "ObjectPattern"
) {
const properties = parent.id.properties as Property[];
const useIsFocusedProperty = properties.find(
(prop) =>
prop.type === "Property" &&
prop.key.type === "Identifier" &&
prop.key.name === "useIsFocused",
);

if (useIsFocusedProperty) {
context.report({
node: useIsFocusedProperty,
messageId: "noUseIsFocusedImport",
fix(fixer) {
return fixer.replaceText(
useIsFocusedProperty.key,
"useFocusEffect",
);
},
});
}
}
}
},
};
},
};
28 changes: 28 additions & 0 deletions packages/eslint-plugin/tests/lib/rules/no-use-is-focused.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Save without formatting: [⌘ + K] > [S]

// This should trigger an error breaking eslint-plugin-bam-custom-rules:
// bam-custom-rules/no-use-is-focused

import { noUseIsFocusedImportRule } from "../../../lib/rules/no-use-is-focused";
import { RuleTester } from "eslint";

const ruleTester = new RuleTester({
parser: require.resolve("@typescript-eslint/parser"),
});

const valid = [``];

const invalid = [`import { useIsFocused } from "@react-navigation/native";`];

const output = `import { useFocusEffect } from "@react-navigation/native";`;

ruleTester.run("no-use-is-focused", noUseIsFocusedImportRule, {
valid,
invalid: invalid.map((code) => ({
code,
errors: [
`Please use 'useFocusEffect' instead of 'useIsFocused' to avoid excessive rerenders.`,
],
output,
})),
});

0 comments on commit dc465fd

Please sign in to comment.