Skip to content

Commit ca73c38

Browse files
committed
feat(filename): enhance default excepts patterns and improve validation
- Add default excepts patterns for common framework files: - Files starting with underscore (_app.tsx, _layout.tsx) - Numeric files (404.tsx) - Square bracket files ([slug].tsx) - Improve validation logic to filter non-word/dash characters - Update documentation with excepts option details - Add comprehensive test cases for new exception patterns This enhances the filename rule to be more framework-agnostic and handles edge cases with special characters in filenames.
1 parent cd5befe commit ca73c38

File tree

3 files changed

+57
-15
lines changed

3 files changed

+57
-15
lines changed

packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ src/components/example-component.tsx
6969
2. `camelCase`: camelCase
7070
3. `kebab-case`: kebab-case
7171
4. `snake_case`: snake_case
72+
- `excepts`: An array of regular expressions to ignore specific file names. Default is:
73+
- `"/^index$/"`: Ignore `index` files.
74+
- `"/^_/"`: Ignore files starting with an underscore (e.g., `_app.tsx`, `_layout.tsx`).
75+
- `"/^[0-9]+$/"`: Ignore files with only numbers (e.g., `404.tsx`).
76+
- `"/^\[[^\]]+\]$/"`: Ignore files with square brackets (e.g., `[slug].tsx`).
7277

7378
## Rule Options Examples
7479

@@ -103,23 +108,36 @@ export default [
103108
export default [
104109
// ...
105110
{
106-
files: ["src/**/*.{ts,tsx}"],
107-
ignore: ["**/index.{ts,tsx}"],
111+
files: ["src/components/**/*.{ts,tsx}"],
108112
rules: {
109113
"@eslint-react/naming-convention/filename": ["warn", "PascalCase"],
110114
},
111115
},
112116
{
113-
files: ["src/pages/**/*.{ts,tsx}"],
114-
ignore: ["**/index.{ts,tsx}"],
117+
files: ["src/hooks/**/use*.{ts,tsx}"],
118+
rules: {
119+
"@eslint-react/naming-convention/filename": ["warn", "camelCase"],
120+
},
121+
},
122+
];
123+
```
124+
125+
### Opting out of the rule for framework-specific files
126+
127+
```js title="eslint.config.js"
128+
// ...
129+
export default [
130+
// ...
131+
{
132+
files: ["**/*.{ts,tsx}"],
115133
rules: {
116134
"@eslint-react/naming-convention/filename": ["warn", "kebab-case"],
117135
},
118136
},
119137
{
120-
files: ["src/hooks/**/use*.{ts,tsx}"],
138+
files: ["app/**/*.{ts,tsx}"], // Opting out of the rule for framework-specific files
121139
rules: {
122-
"@eslint-react/naming-convention/filename": ["warn", "camelCase"],
140+
"@eslint-react/naming-convention/filename": "off",
123141
},
124142
},
125143
];

packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,5 +151,25 @@ ruleTester.run(RULE_NAME, rule, {
151151
filename: "snake_case.tsx",
152152
options: [{ rule: "snake_case" }],
153153
},
154+
{
155+
code,
156+
filename: "404.tsx",
157+
options: [{ rule: "PascalCase" }],
158+
},
159+
{
160+
code,
161+
filename: "$.tsx",
162+
options: [{ rule: "PascalCase" }],
163+
},
164+
{
165+
code,
166+
filename: "_app.tsx",
167+
options: [{ rule: "PascalCase" }],
168+
},
169+
{
170+
code,
171+
filename: "[slug].tsx",
172+
options: [{ rule: "PascalCase" }],
173+
},
154174
],
155175
});

packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ type Options = readonly [
3232

3333
const defaultOptions = [
3434
{
35-
excepts: ["^index$"],
35+
excepts: [
36+
"/^_/",
37+
"/^index$/",
38+
"/^[0-9]+$/",
39+
"/^\\[[^\\]]+\\]$/",
40+
],
3641
rule: "PascalCase",
3742
},
3843
] as const satisfies Options;
@@ -95,15 +100,14 @@ export function create(context: RuleContext<MessageID, Options>): RuleListener {
95100
: (options.excepts ?? []).map((s) => RE.toRegExp(s));
96101

97102
function validate(name: string, casing: Case = rule, ignores = excepts) {
98-
const shouldIgnore = ignores
99-
.some((pattern) => pattern.test(name));
100-
if (shouldIgnore) return true;
101-
103+
if (ignores.some((pattern) => pattern.test(name))) return true;
104+
const filteredName = name.match(/[\w-]/gu)?.join("") ?? "";
105+
if (filteredName.length === 0) return true;
102106
return match(casing)
103-
.with("PascalCase", () => RE.PASCAL_CASE.test(name))
104-
.with("camelCase", () => RE.CAMEL_CASE.test(name))
105-
.with("kebab-case", () => RE.KEBAB_CASE.test(name))
106-
.with("snake_case", () => RE.SNAKE_CASE.test(name))
107+
.with("PascalCase", () => RE.PASCAL_CASE.test(filteredName))
108+
.with("camelCase", () => RE.CAMEL_CASE.test(filteredName))
109+
.with("kebab-case", () => RE.KEBAB_CASE.test(filteredName))
110+
.with("snake_case", () => RE.SNAKE_CASE.test(filteredName))
107111
.exhaustive();
108112
}
109113

0 commit comments

Comments
 (0)