diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.md b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.md index 09bab4e35..ea825b8fe 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.md +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.md @@ -69,6 +69,12 @@ src/components/example-component.tsx 2. `camelCase`: camelCase 3. `kebab-case`: kebab-case 4. `snake_case`: snake_case +- `excepts`: An array of string or regex string to ignore specific file names. Default is: + - `"index"`: Ignore `index` files (e.g., `index.tsx`). + - `"/^_/"`: Ignore files starting with an underscore (e.g., `_app.tsx`, `_layout.tsx`). + - `"/^\\$/"`: Ignore files starting with a dollar sign (e.g., `$.tsx`, `$id.tsx`). + - `"/^[0-9]+$/"`: Ignore files with only numbers (e.g., `404.tsx`). + - `"/^\[[^\]]+\]$/"`: Ignore files with square brackets (e.g., `[slug].tsx`). ## Rule Options Examples @@ -103,23 +109,36 @@ export default [ export default [ // ... { - files: ["src/**/*.{ts,tsx}"], - ignore: ["**/index.{ts,tsx}"], + files: ["src/components/**/*.{ts,tsx}"], rules: { "@eslint-react/naming-convention/filename": ["warn", "PascalCase"], }, }, { - files: ["src/pages/**/*.{ts,tsx}"], - ignore: ["**/index.{ts,tsx}"], + files: ["src/hooks/**/use*.{ts,tsx}"], + rules: { + "@eslint-react/naming-convention/filename": ["warn", "camelCase"], + }, + }, +]; +``` + +### Opting out of the rule for framework-specific files + +```js title="eslint.config.js" +// ... +export default [ + // ... + { + files: ["**/*.{ts,tsx}"], rules: { "@eslint-react/naming-convention/filename": ["warn", "kebab-case"], }, }, { - files: ["src/hooks/**/use*.{ts,tsx}"], + files: ["app/**/*.{ts,tsx}"], // Opting out of the rule for framework-specific files rules: { - "@eslint-react/naming-convention/filename": ["warn", "camelCase"], + "@eslint-react/naming-convention/filename": "off", }, }, ]; 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 62d6000f6..40754c06f 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 006dea64f..1cbf69eda 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 @@ -32,7 +32,13 @@ type Options = readonly [ const defaultOptions = [ { - excepts: ["^index$"], + excepts: [ + "index", + "/^_/", + "/^\\$/", + "/^[0-9]+$/", + "/^\\[[^\\]]+\\]$/", + ], rule: "PascalCase", }, ] as const satisfies Options; @@ -95,15 +101,14 @@ export function create(context: RuleContext): RuleListener { : (options.excepts ?? []).map((s) => RE.toRegExp(s)); function validate(name: string, casing: Case = rule, ignores = excepts) { - const shouldIgnore = ignores - .some((pattern) => pattern.test(name)); - if (shouldIgnore) return true; - + 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(); }