From 4a2cb190ecb305bdf88f512a5db3a108e456a455 Mon Sep 17 00:00:00 2001 From: Rel1cx Date: Thu, 4 Sep 2025 04:42:20 +0800 Subject: [PATCH 1/3] feat: update naming convention rules default excepts --- apps/website/content/docs/rules/meta.json | 1 - .../src/rules/prefer-namespace-import.mdx | 45 ------- ...-direct-set-state-in-use-layout-effect.mdx | 14 +- .../src/rules/component-name.mdx | 2 +- .../src/rules/component-name.ts | 20 --- .../src/rules/filename-extension.mdx | 2 +- .../src/rules/filename.mdx | 124 +++++++++--------- .../src/rules/filename.spec.ts | 45 +++++++ .../src/rules/filename.ts | 37 +++--- 9 files changed, 132 insertions(+), 158 deletions(-) delete mode 100644 packages/plugins/eslint-plugin-react-dom/src/rules/prefer-namespace-import.mdx diff --git a/apps/website/content/docs/rules/meta.json b/apps/website/content/docs/rules/meta.json index 9968fc71c2..0c6e071fa2 100644 --- a/apps/website/content/docs/rules/meta.json +++ b/apps/website/content/docs/rules/meta.json @@ -78,7 +78,6 @@ "dom-no-unsafe-target-blank", "dom-no-use-form-state", "dom-no-void-elements-with-children", - "dom-prefer-namespace-import", "---Web API Rules---", "web-api-no-leaked-event-listener", "web-api-no-leaked-interval", diff --git a/packages/plugins/eslint-plugin-react-dom/src/rules/prefer-namespace-import.mdx b/packages/plugins/eslint-plugin-react-dom/src/rules/prefer-namespace-import.mdx deleted file mode 100644 index 75b0c13cb0..0000000000 --- a/packages/plugins/eslint-plugin-react-dom/src/rules/prefer-namespace-import.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: prefer-namespace-import ---- - -**Full Name in `eslint-plugin-react-dom`** - -```sh copy -react-dom/prefer-namespace-import -``` - -**Full Name in `@eslint-react/eslint-plugin`** - -```sh copy -@eslint-react/dom/prefer-namespace-import -``` - -**Features** - -`๐Ÿ”ง` - -## Description - -Enforces React DOM is imported via a namespace import. - -## Examples - -### Failing - -```tsx -import ReactDOM from "react-dom/client"; - -import type ReactDOM from "react-dom/client"; -``` - -### Passing - -```tsx -import * as ReactDOM from "react-dom/client"; -import type * as ReactDOM from "react-dom/client"; -``` - -## Implementation - -- [Rule Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom/src/rules/prefer-namespace-import.ts) -- [Test Source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom/src/rules/prefer-namespace-import.spec.ts) diff --git a/packages/plugins/eslint-plugin-react-hooks-extra/src/rules/no-direct-set-state-in-use-layout-effect.mdx b/packages/plugins/eslint-plugin-react-hooks-extra/src/rules/no-direct-set-state-in-use-layout-effect.mdx index d55bd1239f..088c497e31 100644 --- a/packages/plugins/eslint-plugin-react-hooks-extra/src/rules/no-direct-set-state-in-use-layout-effect.mdx +++ b/packages/plugins/eslint-plugin-react-hooks-extra/src/rules/no-direct-set-state-in-use-layout-effect.mdx @@ -2,8 +2,6 @@ title: no-direct-set-state-in-use-layout-effect --- -This rule is experimental and may change in the future or be removed. It is not recommended to use it in production code at this time. - **Full Name in `eslint-plugin-react-hooks-extra`** ```sh copy @@ -20,12 +18,6 @@ react-hooks-extra/no-direct-set-state-in-use-layout-effect `๐Ÿงช` -**Presets** - -- `recommended` -- `recommended-typescript` -- `recommended-type-checked` - ## Description Disallow **direct** calls to the [`set` function](https://react.dev/reference/react/useState#setstate) of `useState` in `useLayoutEffect`. @@ -149,7 +141,11 @@ export default function RemoteContent() { } ``` -The following examples are derived from the [React documentation](https://react.dev/learn/you-might-not-need-an-effect): + + If you need to fetch remote data within the component, consider using libraries like [TanStack Query](https://tanstack.com/query/v3/) or [SWR](https://swr.vercel.app/). They handle caching, re-fetching, and state management for you, making your code cleaner and more efficient. + + +The following examples are derived from the [React Docs: You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect): ### Failing diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.mdx b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.mdx index 7adb72fe8f..6c9addc946 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.mdx +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.mdx @@ -54,7 +54,7 @@ function MyComponent() { - `excepts`: (optional) An array of component names that are allowed to not follow the rule. - `allowAllCaps`: (optional) If `true`, allows all caps component names. Default is `false`. -## Rule Options Examples +## Configuration Examples ```json { diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts index 15333d4124..c35345ff77 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts @@ -15,16 +15,6 @@ type Options = readonly [ | Case | { allowAllCaps?: boolean; - /** - * @todo Remove in the next major version - * @deprecated Component names now need to start with an uppercase letter instead of a non-lowercase letter. This means `_Button` or `_component` are no longer valid. (@kassens) in https://github.com/facebook/react/pull/25162 - */ - allowLeadingUnderscore?: boolean; - /** - * @todo Remove in the next major version - * @deprecated This option has no actual effect on the rule - */ - allowNamespace?: boolean; excepts?: readonly string[]; rule?: Case; }, @@ -50,16 +40,6 @@ const schema = [ additionalProperties: false, properties: { allowAllCaps: { type: "boolean" }, - /** - * @todo Remove in the next major version - * @deprecated - */ - allowLeadingUnderscore: { type: "boolean" }, - /** - * @todo Remove in the next major version - * @deprecated - */ - allowNamespace: { type: "boolean" }, excepts: { type: "array", items: { type: "string", format: "regex" }, diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.mdx b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.mdx index ccac381b29..b9045a3c2f 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.mdx +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.mdx @@ -34,7 +34,7 @@ This rule enforces consistent file extensions for JSX files. - `extensions`: List of file extensions that should be treated as JSX files. Default is `[".jsx", ".tsx"]`. - `ignoreFilesWithoutCode`: When set to `true`, this rule will ignore files that do not contain JSX syntax. Default is `true`. -## Rule Options Examples +## Configuration Examples ```js title="eslint.config.js" // ... diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.mdx b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.mdx index 91ccc27c50..9d42c03e95 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.mdx +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.mdx @@ -20,106 +20,117 @@ react-naming-convention/filename ## Description -Enforces consistent file naming conventions. +This rule enforces consistent file naming conventions for React components, hooks, and other project files. + +By default, this rule enforces `PascalCase`, but it can be configured to support `camelCase`, `kebab-case`, or `snake_case` to match your project's standards. ## Examples ### Failing -```bash title="Terminal" {3} -npx eslint --rule '@eslint-react/naming-convention/filename: ["warn", { "rule": "PascalCase" }]' . - -src/components/component.tsx - 1:1 error "File name `component.tsx` does not match `PascalCase`. Should rename to `Component.tsx` @eslint-react/naming-convention/filename +If the rule is configured for `PascalCase`, the following filename will trigger a warning: -โœ– 1 problems (0 errors, 1 warnings) +```js title="eslint.config.js" +export default [ + { + files: ["**/*.tsx"], + rules: { + "@eslint-react/naming-convention/filename": ["warn", { rule: "PascalCase" }], + }, + }, +]; ``` -```bash {3} -npx eslint --rule '@eslint-react/naming-convention/filename: ["warn", { "rule": "kebab-case" }]' . +```bash title="src/components/component.tsx" +# File name "component.tsx" does not match PascalCase. +``` -src/components/example_component.tsx - 1:1 error "File name `example_component.tsx` does not match `kebab-case`. Should rename to `example-component.tsx` @eslint-react/naming-convention/filename +```text title="ESLint Output" +src/components/component.tsx + 1:1 error File name `component.tsx` does not match `PascalCase`. Should rename to `Component.tsx` @eslint-react/naming-convention/filename -โœ– 1 problems (0 errors, 1 warnings) +โœ– 1 problem (1 error, 0 warnings) ``` ### Passing -```bash title="Terminal" -npx eslint --rule '@eslint-react/naming-convention/filename: ["warn", { "rule": "PascalCase" }]' . +With the same `PascalCase` configuration, this file name is valid: -src/components/Component.tsx - -โœจ Done in 0.61s. +```bash title="src/components/Component.tsx" +# Correctly named file. ``` -```bash title="Terminal" -npx eslint --rule '@eslint-react/naming-convention/filename: ["warn", { "rule": "kebab-case" }]' . +This file will pass without any warnings. -src/components/example-component.tsx +## Rule Options -โœจ Done in 0.61s. -``` +The rule accepts an object with the following properties: -## Rule Options +- `rule`: The naming convention to enforce. Default: `"PascalCase"`. + - `"PascalCase"`: `ExampleComponent.tsx` + - `"camelCase"`: `exampleComponent.tsx` + - `"kebab-case"`: `example-component.tsx` + - `"snake_case"`: `example_component.tsx` +- `excepts`: An array of strings or regex patterns to exclude certain file names from this rule. The default exceptions are designed to accommodate common patterns in modern web frameworks: + - `"index"`: Ignores `index` files (e.g., `index.ts`, `index.tsx`). + - `"/^_/"`: Ignores files starting with an underscore (e.g., `_app.tsx`, `_layout.tsx`). + - `"/^\\$/"`: Ignores files starting with a dollar sign (e.g., `$.tsx`). + - `"/^[0-9]+$/"`: Ignores files that are purely numeric (e.g., `404.tsx`). + - `"/^\[[^\]]+\]$/"`: Ignores files with dynamic route segments in brackets (e.g., `[slug].tsx`). -- `rule`: The rule to apply to the file name. Default is `"PascalCase"`. Possible values: - 1. `PascalCase`: PascalCase - 2. `camelCase`: camelCase - 3. `kebab-case`: kebab-case - 4. `snake_case`: snake\_case +## Configuration Examples -## Rule Options Examples +### Enforcing `kebab-case` ```js title="eslint.config.js" -// ... export default [ - // ... { files: ["**/*.tsx"], rules: { - "@eslint-react/naming-convention/filename": ["warn", "PascalCase"] - } + "@eslint-react/naming-convention/filename": ["warn", { rule: "kebab-case" }], + }, + }, ]; ``` +### Different Rules for Different Files + +You can apply different conventions to different parts of your project. For example, use `PascalCase` for components and `camelCase` for hooks. + ```js title="eslint.config.js" -// ... export default [ - // ... { - files: ["**/*.tsx"], + files: ["src/components/**/*.{ts,tsx}"], + rules: { + "@eslint-react/naming-convention/filename": ["warn", { rule: "PascalCase" }], + }, + }, + { + files: ["src/hooks/**/use*.{ts,tsx}"], rules: { - "@eslint-react/naming-convention/filename": ["warn", { "rule": "kebab-case" }] - } + "@eslint-react/naming-convention/filename": ["warn", { rule: "camelCase" }], + }, + }, ]; ``` -### Applying different rules to different files +### Disabling the Rule for Specific Directories + +Framework-specific directories like Next.js's `app` router often have their own naming conventions. You can disable the rule for these directories. ```js title="eslint.config.js" -// ... export default [ - // ... { - files: ["src/**/*.{ts,tsx}"], - ignore: ["**/index.{ts,tsx}"], + files: ["**/*.{ts,tsx}"], rules: { - "@eslint-react/naming-convention/filename": ["warn", "PascalCase"], + "@eslint-react/naming-convention/filename": ["warn", { rule: "kebab-case" }], }, }, { - files: ["src/pages/**/*.{ts,tsx}"], - ignore: ["**/index.{ts,tsx}"], + // Opt-out for files in the 'app' directory + files: ["app/**/*.{ts,tsx}"], rules: { - "@eslint-react/naming-convention/filename": ["warn", "kebab-case"], - }, - }, - { - files: ["src/hooks/**/use*.{ts,tsx}"], - rules: { - "@eslint-react/naming-convention/filename": ["warn", "camelCase"], + "@eslint-react/naming-convention/filename": "off", }, }, ]; @@ -129,10 +140,3 @@ export default [ - [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts) - [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts) - ---- - -## See Also - -- [`filename-extension`](./filename-extension): - Enforces consistent use of the JSX file extension. diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts index 62d6000f68..40754c06f2 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts @@ -110,6 +110,21 @@ ruleTester.run(RULE_NAME, rule, { filename: "snake_case.tsx", options: [{ rule: "camelCase" }], }, + { + code, + errors: [ + { + messageId: "filenameInvalid", + data: { + name: "snake_case.test.tsx", + rule: "camelCase", + suggestion: "snakeCase.test.tsx", + }, + }, + ], + filename: "snake_case.test.tsx", + options: [{ rule: "camelCase" }], + }, ], valid: [ { @@ -151,5 +166,35 @@ ruleTester.run(RULE_NAME, rule, { filename: "snake_case.tsx", options: [{ rule: "snake_case" }], }, + { + code, + filename: "snake_case.test.tsx", + options: [{ rule: "snake_case" }], + }, + { + code, + filename: "404.tsx", + options: [{ rule: "PascalCase" }], + }, + { + code, + filename: "$.tsx", + options: [{ rule: "PascalCase" }], + }, + { + code, + filename: "$id.tsx", + options: [{ rule: "PascalCase" }], + }, + { + code, + filename: "_app.tsx", + options: [{ rule: "PascalCase" }], + }, + { + code, + filename: "[slug].tsx", + options: [{ rule: "PascalCase" }], + }, ], }); diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts index 9181ed23c9..1cbf69eda2 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts @@ -25,22 +25,20 @@ type Options = readonly [ | unit | Case | { - /** - * @deprecated Use ESLint's [files](https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-and-ignores) feature instead - */ excepts?: readonly string[]; - /** - * @deprecated Use ESLint's [files](https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-and-ignores) feature instead - */ - extensions?: readonly string[]; rule?: Case; }, ]; const defaultOptions = [ { - excepts: ["^index$"], - extensions: [".js", ".jsx", ".ts", ".tsx"], + excepts: [ + "index", + "/^_/", + "/^\\$/", + "/^[0-9]+$/", + "/^\\[[^\\]]+\\]$/", + ], rule: "PascalCase", }, ] as const satisfies Options; @@ -100,20 +98,17 @@ export function create(context: RuleContext): RuleListener { : options.rule ?? "PascalCase"; const excepts = typeof options === "string" ? [] - // eslint-disable-next-line @typescript-eslint/no-deprecated - : options.excepts ?? []; - - function validate(name: string, casing: Case = rule, ignores: readonly string[] = excepts) { - const shouldIgnore = ignores - .map((s) => RE.toRegExp(s)) - .some((pattern) => pattern.test(name)); - if (shouldIgnore) return true; + : (options.excepts ?? []).map((s) => RE.toRegExp(s)); + function validate(name: string, casing: Case = rule, ignores = excepts) { + if (ignores.some((pattern) => pattern.test(name))) return true; + const filteredName = name.match(/[\w-]/gu)?.join("") ?? ""; + if (filteredName.length === 0) return true; return match(casing) - .with("PascalCase", () => RE.PASCAL_CASE.test(name)) - .with("camelCase", () => RE.CAMEL_CASE.test(name)) - .with("kebab-case", () => RE.KEBAB_CASE.test(name)) - .with("snake_case", () => RE.SNAKE_CASE.test(name)) + .with("PascalCase", () => RE.PASCAL_CASE.test(filteredName)) + .with("camelCase", () => RE.CAMEL_CASE.test(filteredName)) + .with("kebab-case", () => RE.KEBAB_CASE.test(filteredName)) + .with("snake_case", () => RE.SNAKE_CASE.test(filteredName)) .exhaustive(); } From 170c4df58b300dc505808459e1aedec6b5682621 Mon Sep 17 00:00:00 2001 From: REL1CX Date: Thu, 4 Sep 2025 04:47:00 +0800 Subject: [PATCH 2/3] Update packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: REL1CX --- .../src/rules/filename.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts index 1cbf69eda2..8e6c118ee2 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts @@ -34,10 +34,10 @@ const defaultOptions = [ { excepts: [ "index", - "/^_/", - "/^\\$/", - "/^[0-9]+$/", - "/^\\[[^\\]]+\\]$/", + String.raw`/^_/`, + String.raw`/^\$/`, + String.raw`/^[0-9]+$/`, + String.raw`/^\[[^\]]+\]$/`, ], rule: "PascalCase", }, From 92426bfdb20886c4ddc5b56be356620c419d391f Mon Sep 17 00:00:00 2001 From: REL1CX Date: Thu, 4 Sep 2025 04:50:12 +0800 Subject: [PATCH 3/3] Update packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: REL1CX --- .../eslint-plugin-react-naming-convention/src/rules/filename.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts index 8e6c118ee2..33e60c829e 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts @@ -102,7 +102,7 @@ export function create(context: RuleContext): RuleListener { function validate(name: string, casing: Case = rule, ignores = excepts) { if (ignores.some((pattern) => pattern.test(name))) return true; - const filteredName = name.match(/[\w-]/gu)?.join("") ?? ""; + const filteredName = name.match(/[\w.-]/gu)?.join("") ?? ""; if (filteredName.length === 0) return true; return match(casing) .with("PascalCase", () => RE.PASCAL_CASE.test(filteredName))