Skip to content

Commit

Permalink
feat(eslint-plugin)!: add no-different-displayname custom rule (#120)
Browse files Browse the repository at this point in the history
* feat: add no-different-displayname custom rule

* doc: add rule in breaking examples

* feat!: add no-different-displayname in recommended config
  • Loading branch information
remilry authored Aug 29, 2024
1 parent b09e75d commit 41c123d
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 5 deletions.
12 changes: 7 additions & 5 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,16 @@ This plugin exports some custom rules that you can optionally use in your projec
<!-- begin auto-generated rules list -->

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

| Name | Description | 💼 | 🔧 |
| :------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------- | :-- | :-- |
| [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 | 🧪 | 🔧 |
| [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 | | |
| Name | Description | 💼 | 🔧 |
| :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | :-- | :-- |
| [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 | 🧪 | 🔧 |
| [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 || 🔧 |
| [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 | | |

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

Expand Down
31 changes: 31 additions & 0 deletions packages/eslint-plugin/docs/rules/no-different-displayname.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Enforce component displayName to match with component name (`@bam.tech/no-different-displayname`)

💼 This rule is enabled in the ✅ `recommended` 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 -->

Enforces component displayName to match with component name

## Rule Details

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

```jsx
const MyComponent = () => {
/* ... */
};

MyComponent.displayName = "NotMyComponent";
```

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

```jsx
const MyComponent = () => {
/* ... */
};

MyComponent.displayName = "MyComponent";
```
1 change: 1 addition & 0 deletions packages/eslint-plugin/lib/configs/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const recommendedConfig = defineConfig({
"react/prop-types": "off",
"react/no-unused-prop-types": "error",
"react/jsx-no-useless-fragment": "error",
"@bam.tech/no-different-displayname": "error",
// ☢️ Rules that require type information must be added to the `.ts` overrides section below
},
env: {
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
@@ -1,9 +1,11 @@
import { awaitUserEventRule } from "./await-user-event";
import { noDifferentDisplaynameRule } from "./no-different-displayname";
import { preferUserEventRule } from "./prefer-user-event";
import { requireNamedEffectRule } from "./require-named-effect";

export default {
"await-user-event": awaitUserEventRule,
"prefer-user-event": preferUserEventRule,
"require-named-effect": requireNamedEffectRule,
"no-different-displayname": noDifferentDisplaynameRule,
};
48 changes: 48 additions & 0 deletions packages/eslint-plugin/lib/rules/no-different-displayname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* @fileoverview Enforces component displayName to match with component name
* @author Remi Leroy
*/

import type { Rule } from "eslint";
import * as ESTree from "estree";

export const noDifferentDisplaynameRule: Rule.RuleModule = {
meta: {
type: "problem",
docs: {
description: "Enforce component displayName to match with component name",
recommended: true,
url: "https://github.com/bamlab/react-native-project-config/tree/main/packages/eslint-plugin/docs/rules/no-different-displayname.md",
},
messages: {
displayNameMismatch: "DisplayName does not match the component name",
},
schema: [],
fixable: "code",
},

create(context) {
return {
'Program > ExpressionStatement > AssignmentExpression:has(Identifier[name="displayName"])'(
node: ESTree.AssignmentExpression,
) {
if (!("object" in node.left)) return;
if (!("name" in node.left.object)) return;
if (!("value" in node.right)) return;

const componentName = node.left.object.name;
const displayedName = node.right.value;

if (componentName !== displayedName) {
context.report({
node,
message: "DisplayName does not match the component name",
fix(fixer) {
return fixer.replaceText(node.right, `"${componentName}"`);
},
});
}
},
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @fileoverview Enforce component displayName to match with component name
* @author Remi Leroy
*/

import { RuleTester } from "eslint";
import { noDifferentDisplaynameRule } from "../../../lib/rules/no-different-displayname";

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

const valid = [
{
code: `
const MyComponent = () => {};
MyComponent.displayName = "MyComponent";
`,
},
];

const invalid = [
{
code: `
const MyComponent = () => {};
MyComponent.displayName = "WrongName";
`,
errors: [{ message: "DisplayName does not match the component name" }],
output: `
const MyComponent = () => {};
MyComponent.displayName = "MyComponent";
`,
},
];

ruleTester.run("no-different-displayname", noDifferentDisplaynameRule, {
valid,
invalid,
});

0 comments on commit 41c123d

Please sign in to comment.