Skip to content

Commit

Permalink
Merge pull request #12 from epaew/feature/pluralize
Browse files Browse the repository at this point in the history
Add rule `pluralize`
  • Loading branch information
epaew authored Jun 8, 2020
2 parents 57abf46 + 65aa839 commit 291b623
Show file tree
Hide file tree
Showing 12 changed files with 371 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Unreleased
## Features
* [#12](https://github.com/epaew/eslint-plugin-filenames-simple/pull/12) Add new rule `pluralize`

# 0.3.1
## Bugfix
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ This plugin is inspired by [eslint-plugin-filenames](https://github.com/selaux/e
* [named-export](./docs/rules/named-export.md)
* [naming-convention](./docs/rules/naming-convention.md)
* [no-index](./docs/rules/no-index.md)
* [pluralize](./docs/rules/pluralize.md)
## CHANGELOG
[CHANGELOG](./CHANGELOG.md)
Expand Down
45 changes: 45 additions & 0 deletions docs/rules/pluralize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# filenames-simple/pluralize
This rule ensures that filenames are plural (or singular).

## Configuration example
```json
{
"plugins": [
"filenames-simple"
],
"rules": {
"filenames-simple/pluralize": [
"error",
{
"parentDir": "plural",
"file": "singular"
},
{
"uncountable": ["water"]
}
]
}
}
```

## Available options
### rule: (the first option)
Specify one of the following naming conventions for each `parentDir` or `file`.

#### Naming convention presets
* singular
* plural

### dictionaries: (the second option)
Specify the dictionary to pass to the `pluralize` library.
See also: https://github.com/blakeembrey/pluralize#usage

#### Keys and examples
* irregular: array of arguments for `pluralize.addIrregularRule()`
* e.g. `[["singular", "plural"], ["person", "people"]]`
* plural: array of arguments for `pluralize.addPluralRule()`
* e.g. `[["plural", "singular"]]`
* singular: array of arguments for `pluralize.addSingularRule()`
* e.g. `[["singular", "plural"]]`
* uncountable: array of arguments for `pluralize.addUncountableRule()`
* e.g. `["uncountable", "water"]`
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@
"test": "jest",
"test:coverage": "jest --coverage"
},
"dependencies": {
"pluralize": "^8.0.0"
},
"devDependencies": {
"@types/eslint": "^6.8.1",
"@types/glob": "^7.1.1",
"@types/jest": "^25.2.3",
"@types/node": "^14.0.5",
"@types/pluralize": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^3.0.2",
"@typescript-eslint/parser": "^3.0.2",
"@typescript-eslint/typescript-estree": "^3.1.0",
Expand Down
1 change: 1 addition & 0 deletions src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const all: Linter.BaseConfig = {
'filenames-simple/named-export': 'error',
'filenames-simple/naming-convention': 'error',
'filenames-simple/no-index': 'error',
'filenames-simple/pluralize': ['error', { parentDir: 'plural', file: 'singular' }],
},
};
1 change: 1 addition & 0 deletions src/configs/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const recommended: Linter.BaseConfig = {
'filenames-simple/named-export': 'warn',
'filenames-simple/naming-convention': 'error',
'filenames-simple/no-index': 'off',
'filenames-simple/pluralize': 'off',
},
};

Expand Down
2 changes: 2 additions & 0 deletions src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { extname } from './extname';
import { namedExport } from './named-export';
import { namingConvention } from './naming-convention';
import { noIndex } from './no-index';
import { pluralize } from './pluralize';

type Rules = { [key: string]: Rule.RuleModule | undefined };

Expand All @@ -14,4 +15,5 @@ export const rules: Rules = {
'named-export': namedExport,
'naming-convention': namingConvention,
'no-index': noIndex,
pluralize,
};
93 changes: 93 additions & 0 deletions src/rules/pluralize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import path from 'path';
import { Rule } from 'eslint';
import { correct, Dictionaries, isValidName, setDictionaries } from '../utils/pluralize';

type Rules = {
parentDir?: 'singular' | 'plural';
file?: 'singular' | 'plural';
};
type Options = {
dictionaries?: Dictionaries;
rules: Rules;
};

const irregular = {
type: 'array',
items: {
type: 'array',
items: [{ type: 'string' }, { type: 'string' }],
maxItems: 2,
minItems: 2,
},
minItems: 1,
uniqueItems: true,
};
const plural = irregular;
const singular = irregular;

const fetchFilename = (context: Rule.RuleContext): [string, string[]] => {
const absolutePath = path.resolve(context.getFilename());
const [dirname, basename] = absolutePath.split(path.sep).slice(-2);
const filename = basename.split('.');

return [dirname, filename];
};

const parseOptions = (context: Rule.RuleContext): Options => {
const [rules = {}, dictionaries = {}] = context.options;
return { rules, dictionaries };
};

export const pluralize: Rule.RuleModule = {
meta: {
type: 'suggestion',
schema: [
{
type: 'object',
properties: {
parentDir: { enum: ['singular', 'plural'] },
file: { enum: ['singular', 'plural'] },
},
minProperties: 1,
},
{
type: 'object',
properties: {
irregular,
plural,
singular,
uncountable: {
type: 'array',
items: { type: 'string' },
minItems: 1,
uniqueItems: true,
},
},
minProperties: 1,
},
],
},
create: context => {
const { rules, dictionaries } = parseOptions(context);
dictionaries && setDictionaries(dictionaries);

return {
Program: node => {
const [dirname, [filename, ...rest]] = fetchFilename(context);

if (isValidName(dirname, rules.parentDir) && isValidName(filename, rules.file)) return;

context.report({
node: node,
message:
'The filename must follow the pluralize rule. Should rename to {{ dirname }}/{{ filename }}.{{ extname }}',
data: {
dirname: correct(dirname, rules.parentDir),
filename: correct(filename, rules.file),
extname: rest.join('.'),
},
});
},
};
},
};
44 changes: 44 additions & 0 deletions src/utils/pluralize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import pluralize from 'pluralize';

type Rule = 'singular' | 'plural';
export type Dictionaries = {
irregular?: [string, string][];
plural?: [string, string][];
singular?: [string, string][];
uncountable?: string[];
};

export const correct = (name: string, rule?: Rule) => {
const corrector = {
singular: pluralize.singular,
plural: pluralize.plural,
};
return rule ? corrector[rule](name) : name;
};

export const isValidName = (name: string, rule?: Rule) => {
const validator = {
singular: pluralize.isSingular,
plural: pluralize.isPlural,
};
return rule ? validator[rule](name) : true;
};

export const setDictionaries = (dictionaries: Dictionaries) => {
const keys: Array<keyof Dictionaries> = ['irregular', 'plural', 'singular', 'uncountable'];
const dictionarySetter = {
irregular: ([singular, plural]: [string, string]) =>
pluralize.addIrregularRule(singular, plural),
plural: ([plural, singular]: [string, string]) =>
pluralize.addPluralRule(new RegExp(plural), singular),
singular: ([singular, plural]: [string, string]) =>
pluralize.addSingularRule(new RegExp(singular), plural),
uncountable: (uncountable: string) => pluralize.addUncountableRule(uncountable),
};

keys.forEach(key => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
dictionaries[key] && dictionaries[key].forEach(dictionarySetter[key]);
});
};
77 changes: 77 additions & 0 deletions tests/rules/pluralize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { RuleTester } from 'eslint';
import { pluralize } from '#/rules/pluralize';

const ruleTester = new RuleTester();

ruleTester.run('pluralize', pluralize, {
valid: [
{
code: '',
filename: 'src/controller/index.js',
},
{
code: '',
filename: 'src/controller/index.js',
options: [{ parentDir: 'singular' }],
},
{
code: '',
filename: 'src/controller/index.js',
options: [{ file: 'singular' }],
},
{
code: '',
filename: 'src/controller/index.js',
options: [{ parentDir: 'singular', file: 'singular' }],
},
{
code: '',
filename: 'src/controllers/index.js',
options: [{ parentDir: 'plural', file: 'singular' }],
},
{
code: '',
filename: 'src/controller/indices.js',
options: [{ parentDir: 'singular', file: 'plural' }],
},
{
code: '',
filename: 'src/controllers/indices.js',
options: [{ parentDir: 'plural', file: 'plural' }],
},
{
code: '',
filename: 'src/lib/index.js',
options: [
{ parentDir: 'plural', file: 'singular' },
{ irregular: [['person', 'people']], uncountable: ['lib'] },
],
},
],
invalid: [
{
code: '',
filename: 'src/controller/index.js',
options: [{ parentDir: 'plural', file: 'singular' }],
errors: [
'The filename must follow the pluralize rule. Should rename to controllers/index.js',
],
},
{
code: '',
filename: 'src/controller/index.js',
options: [{ parentDir: 'plural' }],
errors: [
'The filename must follow the pluralize rule. Should rename to controllers/index.js',
],
},
{
code: '',
filename: 'src/controller/index.js',
options: [{ file: 'plural' }],
errors: [
'The filename must follow the pluralize rule. Should rename to controller/indices.js',
],
},
],
});
Loading

0 comments on commit 291b623

Please sign in to comment.