Skip to content

Commit 7ca54c3

Browse files
authored
fix(eslint-plugin): [naming-convention] allow an array of selectors with types and modifiers (#2415)
1 parent 189162d commit 7ca54c3

File tree

2 files changed

+166
-32
lines changed

2 files changed

+166
-32
lines changed

Diff for: packages/eslint-plugin/src/rules/naming-convention.ts

+46-28
Original file line numberDiff line numberDiff line change
@@ -278,28 +278,29 @@ function selectorsSchema(): JSONSchema.JSONSchema4 {
278278
},
279279
additionalItems: false,
280280
},
281+
modifiers: {
282+
type: 'array',
283+
items: {
284+
type: 'string',
285+
enum: util.getEnumNames(Modifiers),
286+
},
287+
additionalItems: false,
288+
},
289+
types: {
290+
type: 'array',
291+
items: {
292+
type: 'string',
293+
enum: util.getEnumNames(TypeModifiers),
294+
},
295+
additionalItems: false,
296+
},
281297
},
282298
},
283-
modifiers: {
284-
type: 'array',
285-
items: {
286-
type: 'string',
287-
enum: util.getEnumNames(Modifiers),
288-
},
289-
additionalItems: false,
290-
},
291-
types: {
292-
type: 'array',
293-
items: {
294-
type: 'string',
295-
enum: util.getEnumNames(TypeModifiers),
296-
},
297-
additionalItems: false,
298-
},
299299
required: ['selector', 'format'],
300300
additionalProperties: false,
301301
};
302302
}
303+
303304
const SCHEMA: JSONSchema.JSONSchema4 = {
304305
type: 'array',
305306
items: {
@@ -819,7 +820,9 @@ type ParsedOptions = Record<SelectorsString, null | ValidatorFunction>;
819820
type Context = Readonly<TSESLint.RuleContext<MessageIds, Options>>;
820821

821822
function parseOptions(context: Context): ParsedOptions {
822-
const normalizedOptions = context.options.map(opt => normalizeOption(opt));
823+
const normalizedOptions = context.options
824+
.map(opt => normalizeOption(opt))
825+
.reduce((acc, val) => acc.concat(val), []);
823826
return util.getEnumNames(Selectors).reduce((acc, k) => {
824827
acc[k] = createValidator(k, context, normalizedOptions);
825828
return acc;
@@ -1257,7 +1260,8 @@ function isMetaSelector(
12571260
): selector is MetaSelectorsString {
12581261
return selector in MetaSelectors;
12591262
}
1260-
function normalizeOption(option: Selector): NormalizedSelector {
1263+
1264+
function normalizeOption(option: Selector): NormalizedSelector[] {
12611265
let weight = 0;
12621266
option.modifiers?.forEach(mod => {
12631267
weight |= Modifiers[mod];
@@ -1309,16 +1313,30 @@ function normalizeOption(option: Selector): NormalizedSelector {
13091313
? option.selector
13101314
: [option.selector];
13111315

1312-
return {
1313-
selector: selectors
1314-
.map(selector =>
1315-
isMetaSelector(selector)
1316-
? MetaSelectors[selector]
1317-
: Selectors[selector],
1318-
)
1319-
.reduce((accumulator, selector) => accumulator | selector),
1320-
...normalizedOption,
1321-
};
1316+
const selectorsAllowedToHaveTypes: (Selectors | MetaSelectors)[] = [
1317+
Selectors.variable,
1318+
Selectors.parameter,
1319+
Selectors.property,
1320+
Selectors.parameterProperty,
1321+
Selectors.accessor,
1322+
];
1323+
1324+
const config: NormalizedSelector[] = [];
1325+
selectors
1326+
.map(selector =>
1327+
isMetaSelector(selector) ? MetaSelectors[selector] : Selectors[selector],
1328+
)
1329+
.forEach(selector =>
1330+
selectorsAllowedToHaveTypes.includes(selector)
1331+
? config.push({ selector: selector, ...normalizedOption })
1332+
: config.push({
1333+
selector: selector,
1334+
...normalizedOption,
1335+
types: null,
1336+
}),
1337+
);
1338+
1339+
return config;
13221340
}
13231341

13241342
function isCorrectType(

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

+120-4
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,77 @@ ruleTester.run('naming-convention', rule, {
835835
},
836836
],
837837
},
838+
{
839+
code: `
840+
let isFoo = 1;
841+
class foo {
842+
shouldBoo: number;
843+
}
844+
`,
845+
parserOptions,
846+
options: [
847+
{
848+
selector: ['variable', 'parameter', 'property', 'accessor'],
849+
types: ['number'],
850+
format: ['PascalCase'],
851+
prefix: ['is', 'should', 'has', 'can', 'did', 'will'],
852+
},
853+
],
854+
},
855+
{
856+
code: `
857+
class foo {
858+
private readonly FooBoo: boolean;
859+
}
860+
`,
861+
parserOptions,
862+
options: [
863+
{
864+
selector: ['property', 'accessor'],
865+
types: ['boolean'],
866+
modifiers: ['private', 'readonly'],
867+
format: ['PascalCase'],
868+
},
869+
],
870+
},
871+
{
872+
code: `
873+
class foo {
874+
private fooBoo: number;
875+
}
876+
`,
877+
options: [
878+
{
879+
selector: ['property', 'accessor'],
880+
modifiers: ['private'],
881+
format: ['camelCase'],
882+
},
883+
],
884+
},
885+
{
886+
code: `
887+
const isfooBar = 1;
888+
function fun(goodfunFoo: number) {}
889+
class foo {
890+
private VanFooBar: number;
891+
}
892+
`,
893+
parserOptions,
894+
options: [
895+
{
896+
selector: ['property', 'accessor'],
897+
modifiers: ['private'],
898+
format: ['StrictPascalCase'],
899+
prefix: ['Van'],
900+
},
901+
{
902+
selector: ['variable', 'parameter'],
903+
types: ['number'],
904+
format: ['camelCase'],
905+
prefix: ['is', 'good'],
906+
},
907+
],
908+
},
838909
],
839910
invalid: [
840911
{
@@ -871,19 +942,16 @@ ruleTester.run('naming-convention', rule, {
871942
declare const any_camelCase01: any;
872943
declare const any_camelCase02: any | null;
873944
declare const any_camelCase03: any | null | undefined;
874-
875945
declare const string_camelCase01: string;
876946
declare const string_camelCase02: string | null;
877947
declare const string_camelCase03: string | null | undefined;
878948
declare const string_camelCase04: 'a' | null | undefined;
879949
declare const string_camelCase05: string | 'a' | null | undefined;
880-
881950
declare const number_camelCase06: number;
882951
declare const number_camelCase07: number | null;
883952
declare const number_camelCase08: number | null | undefined;
884953
declare const number_camelCase09: 1 | null | undefined;
885954
declare const number_camelCase10: number | 2 | null | undefined;
886-
887955
declare const boolean_camelCase11: boolean;
888956
declare const boolean_camelCase12: boolean | null;
889957
declare const boolean_camelCase13: boolean | null | undefined;
@@ -955,7 +1023,6 @@ ruleTester.run('naming-convention', rule, {
9551023
| undefined;
9561024
declare const array_camelCase6: [] | null | undefined;
9571025
declare const array_camelCase7: [number] | null | undefined;
958-
9591026
declare const array_camelCase8:
9601027
| readonly number[]
9611028
| Array<string>
@@ -1166,5 +1233,54 @@ ruleTester.run('naming-convention', rule, {
11661233
},
11671234
],
11681235
},
1236+
{
1237+
code: `
1238+
const myfoo_bar = 'abcs';
1239+
function fun(myfoo: string) {}
1240+
class foo {
1241+
Myfoo: string;
1242+
}
1243+
`,
1244+
options: [
1245+
{
1246+
selector: ['variable', 'property', 'parameter'],
1247+
types: ['string'],
1248+
format: ['PascalCase'],
1249+
prefix: ['my', 'My'],
1250+
},
1251+
],
1252+
parserOptions,
1253+
errors: Array(3).fill({ messageId: 'doesNotMatchFormatTrimmed' }),
1254+
},
1255+
{
1256+
code: `
1257+
class foo {
1258+
private readonly fooBar: boolean;
1259+
}
1260+
`,
1261+
options: [
1262+
{
1263+
selector: ['property', 'accessor'],
1264+
modifiers: ['private', 'readonly'],
1265+
format: ['PascalCase'],
1266+
},
1267+
],
1268+
errors: [{ messageId: 'doesNotMatchFormat' }],
1269+
},
1270+
{
1271+
code: `
1272+
function my_foo_bar() {}
1273+
`,
1274+
parserOptions,
1275+
options: [
1276+
{
1277+
selector: ['variable', 'function'],
1278+
types: ['string'],
1279+
format: ['PascalCase'],
1280+
prefix: ['my', 'My'],
1281+
},
1282+
],
1283+
errors: [{ messageId: 'doesNotMatchFormatTrimmed' }],
1284+
},
11691285
],
11701286
});

0 commit comments

Comments
 (0)