Skip to content

Commit e352f62

Browse files
Rel1cxCopilot
andauthored
feat(naming-convention/filename): enhance default excepts patterns and improve validation (#1185)
Signed-off-by: REL1CX <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent cd5befe commit e352f62

File tree

3 files changed

+84
-15
lines changed

3 files changed

+84
-15
lines changed

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

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ 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 string or regex string to ignore specific file names. Default is:
73+
- `"index"`: Ignore `index` files (e.g., `index.tsx`).
74+
- `"/^_/"`: Ignore files starting with an underscore (e.g., `_app.tsx`, `_layout.tsx`).
75+
- `"/^\\$/"`: Ignore files starting with a dollar sign (e.g., `$.tsx`, `$id.tsx`).
76+
- `"/^[0-9]+$/"`: Ignore files with only numbers (e.g., `404.tsx`).
77+
- `"/^\[[^\]]+\]$/"`: Ignore files with square brackets (e.g., `[slug].tsx`).
7278

7379
## Rule Options Examples
7480

@@ -103,23 +109,36 @@ export default [
103109
export default [
104110
// ...
105111
{
106-
files: ["src/**/*.{ts,tsx}"],
107-
ignore: ["**/index.{ts,tsx}"],
112+
files: ["src/components/**/*.{ts,tsx}"],
108113
rules: {
109114
"@eslint-react/naming-convention/filename": ["warn", "PascalCase"],
110115
},
111116
},
112117
{
113-
files: ["src/pages/**/*.{ts,tsx}"],
114-
ignore: ["**/index.{ts,tsx}"],
118+
files: ["src/hooks/**/use*.{ts,tsx}"],
119+
rules: {
120+
"@eslint-react/naming-convention/filename": ["warn", "camelCase"],
121+
},
122+
},
123+
];
124+
```
125+
126+
### Opting out of the rule for framework-specific files
127+
128+
```js title="eslint.config.js"
129+
// ...
130+
export default [
131+
// ...
132+
{
133+
files: ["**/*.{ts,tsx}"],
115134
rules: {
116135
"@eslint-react/naming-convention/filename": ["warn", "kebab-case"],
117136
},
118137
},
119138
{
120-
files: ["src/hooks/**/use*.{ts,tsx}"],
139+
files: ["app/**/*.{ts,tsx}"], // Opting out of the rule for framework-specific files
121140
rules: {
122-
"@eslint-react/naming-convention/filename": ["warn", "camelCase"],
141+
"@eslint-react/naming-convention/filename": "off",
123142
},
124143
},
125144
];

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,21 @@ ruleTester.run(RULE_NAME, rule, {
110110
filename: "snake_case.tsx",
111111
options: [{ rule: "camelCase" }],
112112
},
113+
{
114+
code,
115+
errors: [
116+
{
117+
messageId: "filenameInvalid",
118+
data: {
119+
name: "snake_case.test.tsx",
120+
rule: "camelCase",
121+
suggestion: "snakeCase.test.tsx",
122+
},
123+
},
124+
],
125+
filename: "snake_case.test.tsx",
126+
options: [{ rule: "camelCase" }],
127+
},
113128
],
114129
valid: [
115130
{
@@ -151,5 +166,35 @@ ruleTester.run(RULE_NAME, rule, {
151166
filename: "snake_case.tsx",
152167
options: [{ rule: "snake_case" }],
153168
},
169+
{
170+
code,
171+
filename: "snake_case.test.tsx",
172+
options: [{ rule: "snake_case" }],
173+
},
174+
{
175+
code,
176+
filename: "404.tsx",
177+
options: [{ rule: "PascalCase" }],
178+
},
179+
{
180+
code,
181+
filename: "$.tsx",
182+
options: [{ rule: "PascalCase" }],
183+
},
184+
{
185+
code,
186+
filename: "$id.tsx",
187+
options: [{ rule: "PascalCase" }],
188+
},
189+
{
190+
code,
191+
filename: "_app.tsx",
192+
options: [{ rule: "PascalCase" }],
193+
},
194+
{
195+
code,
196+
filename: "[slug].tsx",
197+
options: [{ rule: "PascalCase" }],
198+
},
154199
],
155200
});

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

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

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

97103
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-
104+
if (ignores.some((pattern) => pattern.test(name))) return true;
105+
const filteredName = name.match(/[\w-]/gu)?.join("") ?? "";
106+
if (filteredName.length === 0) return true;
102107
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))
108+
.with("PascalCase", () => RE.PASCAL_CASE.test(filteredName))
109+
.with("camelCase", () => RE.CAMEL_CASE.test(filteredName))
110+
.with("kebab-case", () => RE.KEBAB_CASE.test(filteredName))
111+
.with("snake_case", () => RE.SNAKE_CASE.test(filteredName))
107112
.exhaustive();
108113
}
109114

0 commit comments

Comments
 (0)