Skip to content

Commit 41c123d

Browse files
authored
feat(eslint-plugin)!: add no-different-displayname custom rule (#120)
* feat: add no-different-displayname custom rule * doc: add rule in breaking examples * feat!: add no-different-displayname in recommended config
1 parent b09e75d commit 41c123d

File tree

6 files changed

+128
-5
lines changed

6 files changed

+128
-5
lines changed

packages/eslint-plugin/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,16 @@ This plugin exports some custom rules that you can optionally use in your projec
103103
<!-- begin auto-generated rules list -->
104104

105105
💼 Configurations enabled in.\
106+
✅ Set in the `recommended` configuration.\
106107
🧪 Set in the `tests` configuration.\
107108
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
108109

109-
| Name | Description | 💼 | 🔧 |
110-
| :------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------- | :-- | :-- |
111-
| [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 | 🧪 | 🔧 |
112-
| [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. | | 🔧 |
113-
| [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+
| [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 | | |
114116

115117
<!-- end auto-generated rules list -->
116118

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Enforce component displayName to match with component name (`@bam.tech/no-different-displayname`)
2+
3+
💼 This rule is enabled in the ✅ `recommended` 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 component displayName to match with component name
10+
11+
## Rule Details
12+
13+
Examples of **incorrect** code for this rule :
14+
15+
```jsx
16+
const MyComponent = () => {
17+
/* ... */
18+
};
19+
20+
MyComponent.displayName = "NotMyComponent";
21+
```
22+
23+
Examples of **correct** code for this rule:
24+
25+
```jsx
26+
const MyComponent = () => {
27+
/* ... */
28+
};
29+
30+
MyComponent.displayName = "MyComponent";
31+
```

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const recommendedConfig = defineConfig({
4747
"react/prop-types": "off",
4848
"react/no-unused-prop-types": "error",
4949
"react/jsx-no-useless-fragment": "error",
50+
"@bam.tech/no-different-displayname": "error",
5051
// ☢️ Rules that require type information must be added to the `.ts` overrides section below
5152
},
5253
env: {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { awaitUserEventRule } from "./await-user-event";
2+
import { noDifferentDisplaynameRule } from "./no-different-displayname";
23
import { preferUserEventRule } from "./prefer-user-event";
34
import { requireNamedEffectRule } from "./require-named-effect";
45

56
export default {
67
"await-user-event": awaitUserEventRule,
78
"prefer-user-event": preferUserEventRule,
89
"require-named-effect": requireNamedEffectRule,
10+
"no-different-displayname": noDifferentDisplaynameRule,
911
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @fileoverview Enforces component displayName to match with component name
3+
* @author Remi Leroy
4+
*/
5+
6+
import type { Rule } from "eslint";
7+
import * as ESTree from "estree";
8+
9+
export const noDifferentDisplaynameRule: Rule.RuleModule = {
10+
meta: {
11+
type: "problem",
12+
docs: {
13+
description: "Enforce component displayName to match with component name",
14+
recommended: true,
15+
url: "https://github.com/bamlab/react-native-project-config/tree/main/packages/eslint-plugin/docs/rules/no-different-displayname.md",
16+
},
17+
messages: {
18+
displayNameMismatch: "DisplayName does not match the component name",
19+
},
20+
schema: [],
21+
fixable: "code",
22+
},
23+
24+
create(context) {
25+
return {
26+
'Program > ExpressionStatement > AssignmentExpression:has(Identifier[name="displayName"])'(
27+
node: ESTree.AssignmentExpression,
28+
) {
29+
if (!("object" in node.left)) return;
30+
if (!("name" in node.left.object)) return;
31+
if (!("value" in node.right)) return;
32+
33+
const componentName = node.left.object.name;
34+
const displayedName = node.right.value;
35+
36+
if (componentName !== displayedName) {
37+
context.report({
38+
node,
39+
message: "DisplayName does not match the component name",
40+
fix(fixer) {
41+
return fixer.replaceText(node.right, `"${componentName}"`);
42+
},
43+
});
44+
}
45+
},
46+
};
47+
},
48+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @fileoverview Enforce component displayName to match with component name
3+
* @author Remi Leroy
4+
*/
5+
6+
import { RuleTester } from "eslint";
7+
import { noDifferentDisplaynameRule } from "../../../lib/rules/no-different-displayname";
8+
9+
const ruleTester = new RuleTester({
10+
parser: require.resolve("@typescript-eslint/parser"),
11+
});
12+
13+
const valid = [
14+
{
15+
code: `
16+
const MyComponent = () => {};
17+
MyComponent.displayName = "MyComponent";
18+
`,
19+
},
20+
];
21+
22+
const invalid = [
23+
{
24+
code: `
25+
const MyComponent = () => {};
26+
MyComponent.displayName = "WrongName";
27+
`,
28+
errors: [{ message: "DisplayName does not match the component name" }],
29+
output: `
30+
const MyComponent = () => {};
31+
MyComponent.displayName = "MyComponent";
32+
`,
33+
},
34+
];
35+
36+
ruleTester.run("no-different-displayname", noDifferentDisplaynameRule, {
37+
valid,
38+
invalid,
39+
});

0 commit comments

Comments
 (0)