forked from rEl1cx/eslint-react
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add jsx-filename-naming-convention
- Loading branch information
Showing
17 changed files
with
492 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* eslint-disable security/detect-non-literal-regexp */ | ||
// Copied from https://github.com/epaew/eslint-plugin-filenames-simple/blob/master/src/utils/case-validator.ts | ||
import { getRule } from "./preset-rules"; | ||
|
||
type RecommendationBuilder = (name: string) => string; | ||
|
||
export class CaseValidator { | ||
readonly #expression: RegExp; | ||
readonly #ignorePatterns: RegExp[]; | ||
readonly #recommendationBuilder: RecommendationBuilder; | ||
|
||
constructor( | ||
expression: RegExp, | ||
ignorePatterns: RegExp[], | ||
recommendationBuilder: RecommendationBuilder = () => { | ||
throw new Error("Not implemented"); | ||
}, | ||
) { | ||
this.#expression = expression; | ||
this.#ignorePatterns = ignorePatterns; | ||
this.#recommendationBuilder = recommendationBuilder; | ||
} | ||
|
||
getRecommendedName(name: string): string { | ||
const recommendedName = this.#recommendationBuilder(name); | ||
if (this.#expression.test(recommendedName)) { | ||
return recommendedName; | ||
} | ||
|
||
throw new Error("Failed to build recommendation."); | ||
} | ||
|
||
validate(name: string): boolean { | ||
if (this.#ignorePatterns.some((re) => re.test(name))) { | ||
return true; | ||
} | ||
|
||
return this.#expression.test(name); | ||
} | ||
} | ||
|
||
export const getCaseValidator = (ruleName: string, ignorePattern: string[] = []): CaseValidator => { | ||
const { expression, recommendationBuilder } = getRule(ruleName); | ||
return new CaseValidator( | ||
expression, | ||
ignorePattern.map((pattern) => new RegExp(`^${pattern}$`, "u")), | ||
recommendationBuilder, | ||
); | ||
}; |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* eslint-disable security/detect-object-injection */ | ||
/* eslint-disable security/detect-non-literal-regexp */ | ||
// Copied from https://github.com/epaew/eslint-plugin-filenames-simple/blob/master/src/utils/preset-rules.ts | ||
import { splitName } from "./split-name"; | ||
|
||
type Rule = { | ||
expression: RegExp; | ||
recommendationBuilder?: (name: string) => string; | ||
}; | ||
type PresetRules = { | ||
[key: string]: Required<Rule> | undefined; | ||
PascalCase: Required<Rule>; | ||
camelCase: Required<Rule>; | ||
"kebab-case": Required<Rule>; | ||
snake_case: Required<Rule>; | ||
}; | ||
|
||
export const presetRules: PresetRules = { | ||
PascalCase: { | ||
expression: /^[A-Z][\dA-Za-z]*$/u, | ||
recommendationBuilder: (name: string): string => { | ||
return splitName(name) | ||
.map((word) => { | ||
const [first, ...rest] = word; | ||
return `${first?.toUpperCase() ?? ""}${rest.join("")}`; | ||
}) | ||
.join(""); | ||
}, | ||
}, | ||
camelCase: { | ||
expression: /^[a-z][\dA-Za-z]*$/u, | ||
recommendationBuilder: (name: string): string => { | ||
return splitName(name) | ||
.map((word, i) => { | ||
if (i === 0) { | ||
return word; | ||
} | ||
|
||
const [first, ...rest] = word; | ||
return `${first?.toUpperCase() ?? ""}${rest.join("")}`; | ||
}) | ||
.join(""); | ||
}, | ||
}, | ||
"kebab-case": { | ||
expression: /^[a-z][\d\-a-z]*$/u, | ||
recommendationBuilder: (name: string): string => { | ||
return splitName(name).join("-"); | ||
}, | ||
}, | ||
snake_case: { | ||
expression: /^[a-z][\d_a-z]*$/u, | ||
recommendationBuilder: (name: string): string => { | ||
return splitName(name).join("_"); | ||
}, | ||
}, | ||
}; | ||
|
||
export const getRule = (expression: string): Rule => { | ||
const rule = presetRules[expression]; | ||
return rule ?? { expression: new RegExp(`^${expression}$`, "u") }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* | ||
* Copied from https://github.com/epaew/eslint-plugin-filenames-simple/blob/master/src/utils/split-name.ts | ||
* Split the file/variable name written in camelCase, kebab-case, PascalCase, and snake_case. | ||
*/ | ||
export const splitName = (name: string): string[] => { | ||
return name | ||
.replace(/_/gu, "-") | ||
.replace(/([\da-z])([A-Z])|([A-Z])([A-Z])(?=[a-z])/gu, "$1$3-$2$4") | ||
.toLowerCase() | ||
.split("-"); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# jsx-filename-naming-convention | ||
|
||
## Rule Details | ||
|
||
Examples of **correct** case for this rule: | ||
|
||
```bash | ||
npx eslint --rule 'react/jsx-filename-naming-convention: ["error", { "rule": "PascalCase" }]' . | ||
|
||
src/components/ExampleComponent.tsx | ||
|
||
✨ Done in 0.61s. | ||
``` | ||
|
||
```bash | ||
npx eslint --rule 'react/jsx-filename-naming-convention: ["error", { "rule": "kebab-case" }]' . | ||
|
||
src/components/example-component.tsx | ||
|
||
✨ Done in 0.61s. | ||
``` | ||
|
||
Examples of **incorrect** case for this rule: | ||
|
||
```bash | ||
npx eslint --rule 'react/jsx-filename-naming-convention: ["error", { "rule": "PascalCase" }]' . | ||
|
||
src/components/exampleComponent.tsx | ||
1:1 error "File name `exampleComponent.tsx` does not match `PascalCase`. Should rename to `ExampleComponent.tsx` react/jsx-filename-naming-convention | ||
✖ 1 problems (1 errors, 0 warnings) | ||
``` | ||
```bash | ||
npx eslint --rule 'react/jsx-filename-naming-convention: ["error", { "rule": "kebab-case" }]' . | ||
src/components/example_component.tsx | ||
1:1 error "File name `example_component.tsx` does not match `kebab-case`. Should rename to `example-component.tsx` react/jsx-filename-naming-convention | ||
|
||
✖ 1 problems (1 errors, 0 warnings) | ||
``` | ||
|
||
## Rule Options | ||
|
||
### Type Signature | ||
|
||
```ts | ||
type Options = { | ||
rule: "PascalCase" | "kebab-case" | "camelCase" | "snake_case"; | ||
}; | ||
``` | ||
|
||
### Default Option | ||
|
||
```json | ||
"react/jsx-filename-naming-convention": ["error", { | ||
"rule": "PascalCase" | ||
}] | ||
``` | ||
|
||
- `rule`: The naming convention to enforce. Defaults to `PascalCase` | ||
|
||
## When Not To Use It | ||
|
||
If you are not using JSX, or if you don't want to enforce specific naming conventions for filenames. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import RuleTester, { getFixturesRootDir } from "../../test/rule-tester"; | ||
import rule from "./jsx-filename-naming-convention"; | ||
|
||
const rootDir = getFixturesRootDir(); | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: "@typescript-eslint/parser", | ||
parserOptions: { | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
ecmaVersion: 2021, | ||
sourceType: "module", | ||
project: "./tsconfig.json", | ||
tsconfigRootDir: rootDir, | ||
}, | ||
}); | ||
|
||
const RULE_NAME = "jsx-filename-naming-convention"; | ||
|
||
const code = "export {}"; | ||
|
||
ruleTester.run(RULE_NAME, rule, { | ||
valid: [ | ||
{ | ||
filename: "PascalCase.tsx", | ||
code, | ||
}, | ||
{ | ||
filename: "PascalCase.tsx", | ||
code, | ||
options: [{ rule: "PascalCase" }], | ||
}, | ||
{ | ||
filename: "camelCase.tsx", | ||
code, | ||
options: [{ rule: "camelCase" }], | ||
}, | ||
{ | ||
filename: "kebab-case.tsx", | ||
code, | ||
options: [{ rule: "kebab-case" }], | ||
}, | ||
{ | ||
filename: "snake_case.tsx", | ||
code, | ||
options: [{ rule: "snake_case" }], | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
filename: "pascalCase.tsx", | ||
code, | ||
errors: [ | ||
{ | ||
messageId: "filenameCaseMismatchWithSuggestion", | ||
data: { | ||
name: "pascalCase.tsx", | ||
rule: "PascalCase", | ||
suggestion: "PascalCase.tsx", | ||
}, | ||
}, | ||
], | ||
}, | ||
{ | ||
filename: "camelCase.tsx", | ||
code, | ||
options: [{ rule: "PascalCase" }], | ||
errors: [ | ||
{ | ||
messageId: "filenameCaseMismatchWithSuggestion", | ||
data: { | ||
name: "camelCase.tsx", | ||
rule: "PascalCase", | ||
suggestion: "CamelCase.tsx", | ||
}, | ||
}, | ||
], | ||
}, | ||
{ | ||
filename: "kebab-case.tsx", | ||
code, | ||
options: [{ rule: "PascalCase" }], | ||
errors: [ | ||
{ | ||
messageId: "filenameCaseMismatchWithSuggestion", | ||
data: { | ||
name: "kebab-case.tsx", | ||
rule: "PascalCase", | ||
suggestion: "KebabCase.tsx", | ||
}, | ||
}, | ||
], | ||
}, | ||
{ | ||
filename: "snake_case.tsx", | ||
code, | ||
options: [{ rule: "PascalCase" }], | ||
errors: [ | ||
{ | ||
messageId: "filenameCaseMismatchWithSuggestion", | ||
data: { | ||
name: "snake_case.tsx", | ||
rule: "PascalCase", | ||
suggestion: "SnakeCase.tsx", | ||
}, | ||
}, | ||
], | ||
}, | ||
{ | ||
filename: "PascalCase.tsx", | ||
code, | ||
options: [{ rule: "camelCase" }], | ||
errors: [ | ||
{ | ||
messageId: "filenameCaseMismatchWithSuggestion", | ||
data: { | ||
name: "PascalCase.tsx", | ||
rule: "camelCase", | ||
suggestion: "pascalCase.tsx", | ||
}, | ||
}, | ||
], | ||
}, | ||
{ | ||
filename: "kebab-case.tsx", | ||
code, | ||
options: [{ rule: "camelCase" }], | ||
errors: [ | ||
{ | ||
messageId: "filenameCaseMismatchWithSuggestion", | ||
data: { | ||
name: "kebab-case.tsx", | ||
rule: "camelCase", | ||
suggestion: "kebabCase.tsx", | ||
}, | ||
}, | ||
], | ||
}, | ||
{ | ||
filename: "snake_case.tsx", | ||
code, | ||
options: [{ rule: "camelCase" }], | ||
errors: [ | ||
{ | ||
messageId: "filenameCaseMismatchWithSuggestion", | ||
data: { | ||
name: "snake_case.tsx", | ||
rule: "camelCase", | ||
suggestion: "snakeCase.tsx", | ||
}, | ||
}, | ||
], | ||
}, | ||
], | ||
}); |
Oops, something went wrong.