10000 feat(eslint-plugin): [naming-convention] add `requireDouble`, `allowD… · dopecodez/typescript-eslint@dd0576a · GitHub
[go: up one dir, main page]

Skip to content

Commit dd0576a

Browse files
authored
feat(eslint-plugin): [naming-convention] add requireDouble, allowDouble, allowSingleOrDouble options for underscores (typescript-eslint#2812)
1 parent 6a06944 commit dd0576a

File tree

3 files changed

+223
-30
lines changed

3 files changed

+223
-30
lines changed

packages/eslint-plugin/docs/rules/naming-convention.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,20 @@ type Options = {
3333
regex: string;
3434
match: boolean;
3535
};
36-
leadingUnderscore?: 'forbid' | 'allow' | 'require';
37-
trailingUnderscore?: 'forbid' | 'allow' | 'require';
36+
leadingUnderscore?:
37+
| 'forbid'
38+
| 'require'
39+
| 'requireDouble'
40+
| 'allow'
41+
| 'allowDouble'
42+
| 'allowSingleOrDouble';
43+
trailingUnderscore?:
44+
| 'forbid'
45+
| 'require'
46+
| 'requireDouble'
47+
| 'allow'
48+
| 'allowDouble'
49+
| 'allowSingleOrDouble';
3850
prefix?: string[];
3951
suffix?: string[];
4052

@@ -141,8 +153,11 @@ Alternatively, `filter` accepts a regular expression (anything accepted into `ne
141153
The `leadingUnderscore` / `trailingUnderscore` options control whether leading/trailing underscores are considered valid. Accepts one of the following values:
142154

143155
- `forbid` - a leading/trailing underscore is not allowed at all.
144-
- `allow` - existence of a leading/trailing underscore is not explicitly enforced.
145-
- `require` - a leading/trailing underscore must be included.
156+
- `require` - a single leading/trailing underscore must be included.
157+
- `requireDouble` - two leading/trailing underscores must be included.
158+
- `allow` - existence of a single leading/trailing underscore is not explicitly enforced.
159+
- `allowDouble` - existence of a double leading/trailing underscore is not explicitly enforced.
160+
- `allowSingleOrDouble` - existence of a single or a double leading/trailing underscore is not explicitly enforced.
146161

147162
#### `prefix` / `suffix`
148163

packages/eslint-plugin/src/rules/naming-convention.ts

Lines changed: 91 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,24 @@ type MessageIds =
1919
// #region Options Type Config
2020

2121
enum PredefinedFormats {
22-
camelCase = 1 << 0,
23-
strictCamelCase = 1 << 1,
24-
PascalCase = 1 << 2,
25-
StrictPascalCase = 1 << 3,
26-
snake_case = 1 << 4,
27-
UPPER_CASE = 1 << 5,
22+
camelCase = 1,
23+
strictCamelCase,
24+
PascalCase,
25+
StrictPascalCase,
26+
snake_case,
27+
UPPER_CASE,
2828
}
2929
type PredefinedFormatsString = keyof typeof PredefinedFormats;
3030

3131
enum UnderscoreOptions {
32-
forbid = 1 << 0,
33-
allow = 1 << 1,
34-
require = 1 << 2,
32+
forbid = 1,
33+
allow,
34+
require,
35+
36+
// special cases as it's common practice to use double underscore
37+
requireDouble,
38+
allowDouble,
39+
allowSingleOrDouble,
3540
}
3641
type UnderscoreOptionsString = keyof typeof UnderscoreOptions;
3742

@@ -483,7 +488,7 @@ export default util.createRule<Options, MessageIds>({
483488
unexpectedUnderscore:
484489
'{{type}} name `{{name}}` must not have a {{position}} underscore.',
485490
missingUnderscore:
486-
'{{type}} name `{{name}}` must have a {{position}} underscore.',
491+
'{{type}} name `{{name}}` must have {{count}} {{position}} underscore(s).',
487492
missingAffix:
488493
'{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}',
489494
satisfyCustom:
@@ -1143,19 +1148,22 @@ function createValidator(
11431148
processedName,
11441149
position,
11451150
custom,
1151+
count,
11461152
}: {
11471153
affixes?: string[];
11481154
formats?: PredefinedFormats[];
11491155
originalName: string;
11501156
processedName?: string;
11511157
position?: 'leading' | 'trailing' | 'prefix' | 'suffix';
11521158
custom?: NonNullable<NormalizedSelector['custom']>;
1159+
count?: 'one' | 'two';
11531160
}): Record<string, unknown> {
11541161
return {
11551162
type: selectorTypeToMessageString(type),
11561163
name: originalName,
11571164
processedName,
11581165
position,
1166+
count,
11591167
affixes: affixes?.join(', '),
11601168
formats: formats?.map(f => PredefinedFormats[f]).join(', '),
11611169
regex: custom?.regex?.toString(),
@@ -1186,47 +1194,107 @@ function createValidator(
11861194
return name;
11871195
}
11881196

1189-
const hasUnderscore =
1190-
position === 'leading' ? name.startsWith('_') : name.endsWith('_');
1191-
const trimUnderscore =
1197+
const hasSingleUnderscore =
1198+
position === 'leading'
1199+
? (): boolean => name.startsWith('_')
1200+
: (): boolean => name.endsWith('_');
1201+
const trimSingleUnderscore =
11921202
position === 'leading'
11931203
? (): string => name.slice(1)
11941204
: (): string => name.slice(0, -1);
11951205

1206+
const hasDoubleUnderscore =
1207+
position === 'leading'
1208+
? (): boolean => name.startsWith('__')
1209+
: (): boolean => name.endsWith('__');
1210+
const trimDoubleUnderscore =
1211+
position === 'leading'
1212+
? (): string => name.slice(2)
1213+
: (): string => name.slice(0, -2);
1214+
11961215
switch (option) {
1197-
case UnderscoreOptions.allow:
1198-
// no check - the user doesn't care if it's there or not
1199-
break;
1216+
// ALLOW - no conditions as the user doesn't care if it's there or not
1217+
case UnderscoreOptions.allow: {
1218+
if (hasSingleUnderscore()) {
1219+
return trimSingleUnderscore();
1220+
}
1221+
1222+
return name;
1223+
}
1224+
1225+
case UnderscoreOptions.allowDouble: {
1226+
if (hasDoubleUnderscore()) {
1227+
return trimDoubleUnderscore();
1228+
}
12001229

1201-
case UnderscoreOptions.forbid:
1202-
if (hasUnderscore) {
1230+
return name;
1231+
}
1232+
1233+
case UnderscoreOptions.allowSingleOrDouble: {
1234+
if (hasDoubleUnderscore()) {
1235+
return trimDoubleUnderscore();
1236+
}
1237+
1238+
if (hasSingleUnderscore()) {
1239+
return trimSingleUnderscore();
1240+
}
1241+
1242+
return name;
1243+
}
1244+
1245+
// FORBID
1246+
case UnderscoreOptions.forbid: {
1247+
if (hasSingleUnderscore()) {
12031248
context.report({
12041249
node,
12051250
messageId: 'unexpectedUnderscore',
12061251
data: formatReportData({
12071252
originalName,
12081253
position,
1254+
count: 'one',
12091255
}),
12101256
});
12111257
return null;
12121258
}
1213-
break;
12141259

1215-
case UnderscoreOptions.require:
1216-
if (!hasUnderscore) {
1260+
return name;
1261+
}
1262+
1263+
// REQUIRE
1264+
case UnderscoreOptions.require: {
1265+
if (!hasSingleUnderscore()) {
12171266
context.report({
12181267
node,
12191268
messageId: 'missingUnderscore',
12201269
data: formatReportData({
12211270
originalName,
12221271
position,
1272+
count: 'one',
1273+
}),
1274+
});
1275+
return null;
1276+
}
1277+
1278+
return trimSingleUnderscore();
1279+
}
1280+
1281+
case UnderscoreOptions.requireDouble: {
1282+
if (!hasDoubleUnderscore()) {
1283+
context.report({
1284+
node,
1285+
messageId: 'missingUnderscore',
1286+
data: formatReportData({
1287+
originalName,
1288+
position,
1289+
count: 'two',
12231290
}),
12241291
});
12251292
return null;
12261293
}
1227-
}
12281294

1229-
return hasUnderscore ? trimUnderscore() : name;
1295+
return trimDoubleUnderscore();
1296+
}
1297+
}
12301298
}
12311299

12321300
/**

packages/eslint-plugin/tests/rules/naming-convention.test.ts

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ function createValidTestCases(cases: Cases): TSESLint.ValidTestCase<Options>[] {
128128
format,
129129
leadingUnderscore: 'require',
130130
}),
131+
createCase(`__${name}`, {
132+
...test.options,
133+
format,
134+
leadingUnderscore: 'requireDouble',
135+
}),
131136
createCase(`_${name}`, {
132137
...test.options,
133138
format,
@@ -138,6 +143,36 @@ function createValidTestCases(cases: Cases): TSESLint.ValidTestCase<Options>[] {
138143
format,
139144
leadingUnderscore: 'allow',
140145
}),
146+
createCase(`__${name}`, {
147+
...test.options,
148+
format,
149+
leadingUnderscore: 'allowDouble',
150+
}),
151+
createCase(name, {
152+
...test.options,
153+
format,
154+
leadingUnderscore: 'allowDouble',
155+
}),
156+
createCase(`_${name}`, {
157+
...test.options,
158+
format,
159+
leadingUnderscore: 'allowSingleOrDouble',
160+
}),
161+
createCase(name, {
162+
...test.options,
163+
format,
164+
leadingUnderscore: 'allowSingleOrDouble',
165+
}),
166+
createCase(`__${name}`, {
167+
...test.options,
168+
format,
169+
leadingUnderscore: 'allowSingleOrDouble',
170+
}),
171+
createCase(name, {
172+
...test.options,
173+
format,
174+
leadingUnderscore: 'allowSingleOrDouble',
175+
}),
141176

142177
// trailingUnderscore
143178
createCase(name, {
@@ -150,6 +185,11 @@ function createValidTestCases(cases: Cases): TSESLint.ValidTestCase<Options>[] {
150185
format,
151186
trailingUnderscore: 'require',
152187
}),
188+
createCase(`${name}__`, {
189+
...test.options,
190+
format,
191+
trailingUnderscore: 'requireDouble',
192+
}),
153193
createCase(`${name}_`, {
154194
...test.options,
155195
format,
@@ -160,6 +200,36 @@ function createValidTestCases(cases: Cases): TSESLint.ValidTestCase<Options>[] {
160200
format,
161201
trailingUnderscore: 'allow',
162202
}),
203+
createCase(`${name}__`, {
204+
...test.options,
205+
format,
206+
trailingUnderscore: 'allowDouble',
207+
}),
208+
createCase(name, {
209+
...test.options,
210+
format,
211+
trailingUnderscore: 'allowDouble',
212+
}),
213+
createCase(`${name}_`, {
214+
...test.options,
215+
format,
216+
trailingUnderscore: 'allowSingleOrDouble',
217+
}),
218+
createCase(name, {
219+
...test.options,
220+
format,
221+
trailingUnderscore: 'allowSingleOrDouble',
222+
}),
223+
createCase(`${name}__`, {
224+
...test.options,
225+
format,
226+
trailingUnderscore: 'allowSingleOrDouble',
227+
}),
228+
createCase(name, {
229+
...test.options,
230+
format,
231+
trailingUnderscore: 'allowSingleOrDouble',
232+
}),
163233

164234
// prefix
165235
createCase(`MyPrefix${name}`, {
@@ -283,7 +353,27 @@ function createInvalidTestCases(
283353
leadingUnderscore: 'require',
284354
},
285355
'missingUnderscore',
286-
{ position: 'leading' },
356+
{ position: 'leading', count: 'one' },
357+
),
358+
createCase(
359+
name,
360+
{
361+
...test.options,
362+
format,
363+
leadingUnderscore: 'requireDouble',
364+
},
365+
'missingUnderscore',
366+
{ position: 'leading', count: 'two' },
367+
),
368+
createCase(
369+
`_${name}`,
370+
{
371+
...test.options,
372+
format,
373+
leadingUnderscore: 'requireDouble',
374+
},
375+
'missingUnderscore',
376+
{ position: 'leading', count: 'two' },
287377
),
288378

289379
// trailingUnderscore
@@ -305,7 +395,27 @@ function createInvalidTestCases(
305395
trailingUnderscore: 'require',
306396
},
307397
'missingUnderscore',
308-
{ position: 'trailing' },
398+
{ position: 'trailing', count: 'one' },
399+
),
400+
createCase(
401+
name,
402+
{
403+
...test.options,
404+
format,
405+
trailingUnderscore: 'requireDouble',
406+
},
407+
'missingUnderscore',
408+
{ position: 'trailing', count: 'two' },
409+
),
410+
createCase(
411+
`${name}_`,
412+
{
413+
...test.options,
414+
format,
415+
trailingUnderscore: 'requireDouble',
416+
},
417+
'missingUnderscore',
418+
{ position: 'trailing', count: 'two' },
309419
),
310420

311421
// prefix
@@ -1188,7 +1298,7 @@ ruleTester.run('naming-convention', rule, {
11881298
// this line is intentionally broken out
11891299
UnusedTypeParam
11901300
> = {};
1191-
1301+
11921302
export const used_var = 1;
11931303
export function used_func(
11941304
// this line is intentionally broken out

0 commit comments

Comments
 (0)
0