Skip to content

Commit 6afcaea

Browse files
feat(eslint-plugin): added member group support to member-ordering rule (#4538)
* feat(eslint-plugin): added member group support to member-ordering rule * test(eslint-plugin): added more test cases for member-ordering * test(eslint-plugin): added more test cases for member-ordering Co-authored-by: Josh Goldberg <[email protected]>
1 parent 208b6d0 commit 6afcaea

File tree

3 files changed

+215
-13
lines changed

3 files changed

+215
-13
lines changed

Diff for: packages/eslint-plugin/docs/rules/member-ordering.md

+23
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,29 @@ The third grouping option is to ignore both scope and accessibility.
292292
]
293293
```
294294

295+
### Grouping different member types at the same rank
296+
297+
It is also possible to group different member types at the same rank.
298+
299+
```jsonc
300+
[
301+
// Index signature
302+
"signature",
303+
304+
// Fields
305+
"field",
306+
307+
// Constructors
308+
"constructor",
309+
310+
// Getters and Setters at the same rank
311+
["get", "set"],
312+
313+
// Methods
314+
"method"
315+
]
316+
```
317+
295318
### Default configuration
296319

297320
The default configuration looks as follows:

Diff for: packages/eslint-plugin/src/rules/member-ordering.ts

+36-13
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ type Order =
1313
| 'alphabetically-case-insensitive'
1414
| 'as-written';
1515

16+
type MemberType = string | string[];
17+
1618
interface SortedOrderConfig {
17-
memberTypes?: string[] | 'never';
19+
memberTypes?: MemberType[] | 'never';
1820
order: Order;
1921
}
2022

21-
type OrderConfig = string[] | SortedOrderConfig | 'never';
23+
type OrderConfig = MemberType[] | SortedOrderConfig | 'never';
2224
type Member = TSESTree.ClassElement | TSESTree.TypeElement;
2325

2426
export type Options = [
@@ -36,14 +38,24 @@ const neverConfig: JSONSchema.JSONSchema4 = {
3638
enum: ['never'],
3739
};
3840

39-
const arrayConfig = (memberTypes: string[]): JSONSchema.JSONSchema4 => ({
41+
const arrayConfig = (memberTypes: MemberType[]): JSONSchema.JSONSchema4 => ({
4042
type: 'array',
4143
items: {
42-
enum: memberTypes,
44+
oneOf: [
45+
{
46+
enum: memberTypes,
47+
},
48+
{
49+
type: 'array',
50+
items: {
51+
enum: memberTypes,
52+
},
53+
},
54+
],
4355
},
4456
});
4557

46-
const objectConfig = (memberTypes: string[]): JSONSchema.JSONSchema4 => ({
58+
const objectConfig = (memberTypes: MemberType[]): JSONSchema.JSONSchema4 => ({
4759
type: 'object',
4860
properties: {
4961
memberTypes: {
@@ -339,12 +351,20 @@ function getMemberName(
339351
*
340352
* @return Index of the matching member type in the order configuration.
341353
*/
342-
function getRankOrder(memberGroups: string[], orderConfig: string[]): number {
354+
function getRankOrder(
355+
memberGroups: string[],
356+
orderConfig: MemberType[],
357+
): number {
343358
let rank = -1;
344359
const stack = memberGroups.slice(); // Get a copy of the member groups
345360

346361
while (stack.length > 0 && rank === -1) {
347-
rank = orderConfig.indexOf(stack.shift()!);
362+
const memberGroup = stack.shift()!;
363+
rank = orderConfig.findIndex(memberType =>
364+
Array.isArray(memberType)
365+
? memberType.includes(memberGroup)
366+
: memberType === memberGroup,
367+
);
348368
}
349369

350370
return rank;
@@ -358,7 +378,7 @@ function getRankOrder(memberGroups: string[], orderConfig: string[]): number {
358378
*/
359379
function getRank(
360380
node: Member,
361-
orderConfig: string[],
381+
orderConfig: MemberType[],
362382
supportsModifiers: boolean,
363383
): number {
364384
const type = getNodeType(node);
@@ -414,7 +434,7 @@ function getRank(
414434
}
415435

416436
/**
417-
* Gets the lowest possible rank higher than target.
437+
* Gets the lowest possible rank(s) higher than target.
418438
* e.g. given the following order:
419439
* ...
420440
* public-static-method
@@ -427,15 +447,16 @@ function getRank(
427447
* and considering that a public-instance-method has already been declared, so ranks contains
428448
* public-instance-method, then the lowest possible rank for public-static-method is
429449
* public-instance-method.
450+
* If a lowest possible rank is a member group, a comma separated list of ranks is returned.
430451
* @param ranks the existing ranks in the object.
431452
* @param target the target rank.
432453
* @param order the current order to be validated.
433-
* @returns the name of the lowest possible rank without dashes (-).
454+
* @returns the name(s) of the lowest possible rank without dashes (-).
434455
*/
435456
function getLowestRank(
436457
ranks: number[],
437458
target: number,
438-
order: string[],
459+
order: MemberType[],
439460
): string {
440461
let lowest = ranks[ranks.length - 1];
441462

@@ -445,7 +466,9 @@ function getLowestRank(
445466
}
446467
});
447468

448-
return order[lowest].replace(/-/g, ' ');
469+
const lowestRank = order[lowest];
470+
const lowestRanks = Array.isArray(lowestRank) ? lowestRank : [lowestRank];
471+
return lowestRanks.map(rank => rank.replace(/-/g, ' ')).join(', ');
449472
}
450473

451474
export default util.createRule<Options, MessageIds>({
@@ -523,7 +546,7 @@ export default util.createRule<Options, MessageIds>({
523546
*/
524547
function checkGroupSort(
525548
members: Member[],
526-
groupOrder: string[],
549+
groupOrder: MemberType[],
527550
supportsModifiers: boolean,
528551
): Array<Member[]> | null {
529552
const previousRanks: number[] = [];

Diff for: packages/eslint-plugin/tests/rules/member-ordering.test.ts

+156
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,74 @@ class Foo {
14171417
},
14181418
],
14191419
},
1420+
{
1421+
code: `
1422+
class Foo {
1423+
A: string;
1424+
constructor() {}
1425+
get B() {}
1426+
set B() {}
1427+
get C() {}
1428+
set C() {}
1429+
D(): void;
1430+
} `,
1431+
options: [
1432+
{
1433+
default: ['field', 'constructor', ['get', 'set'], 'method'],
1434+
},
1435+
],
1436+
},
1437+
{
1438+
code: `
1439+
class Foo {
1440+
A: string;
1441+
constructor() {}
1442+
B(): void;
1443+
} `,
1444+
options: [
1445+
{
1446+
default: ['field', 'constructor', [], 'method'],
1447+
},
1448+
],
1449+
},
1450+
{
1451+
code: `
1452+
class Foo {
1453+
A: string;
1454+
constructor() {}
1455+
@Dec() private B: string;
1456+
private C(): void;
1457+
set D() {}
1458+
E(): void;
1459+
} `,
1460+
options: [
1461+
{
1462+
default: [
1463+
'public-field',
1464+
'constructor',
1465+
['private-decorated-field', 'public-set', 'private-method'],
1466+
'public-method',
1467+
],
1468+
},
1469+
],
1470+
},
1471+
{
1472+
code: `
1473+
class Foo {
1474+
A: string;
1475+
constructor() {}
1476+
get B() {}
1477+
get C() {}
1478+
set B() {}
1479+
set C() {}
1480+
D(): void;
1481+
} `,
1482+
options: [
1483+
{
1484+
default: ['field', 'constructor', ['get'], ['set'], 'method'],
1485+
},
1486+
],
1487+
},
14201488
],
14211489
invalid: [
14221490
{
@@ -3823,6 +3891,94 @@ class Foo {
38233891
},
38243892
],
38253893
},
3894+
{
3895+
code: `
3896+
class Foo {
3897+
A: string;
3898+
get B() {}
3899+
constructor() {}
3900+
set B() {}
3901+
get C() {}
3902+
set C() {}
3903+
D(): void;
3904+
} `,
3905+
options: [
3906+
{
3907+
default: ['field', 'constructor', ['get', 'set'], 'method'],
3908+
},
3909+
],
3910+
errors: [
3911+
{
3912+
messageId: 'incorrectGroupOrder',
3913+
data: {
3914+
name: 'constructor',
3915+
rank: 'get, set',
3916+
},
3917+
line: 5,
3918+
column: 5,
3919+
},
3920+
],
3921+
},
3922+
{
3923+
code: `
3924+
class Foo {
3925+
A: string;
3926+
private C(): void;
3927+
constructor() {}
3928+
@Dec() private B: string;
3929+
set D() {}
3930+
E(): void;
3931+
} `,
3932+
options: [
3933+
{
3934+
default: [
3935+
'public-field',
3936+
'constructor',
3937+
['private-decorated-field', 'public-set', 'private-method'],
3938+
'public-method',
3939+
],
3940+
},
3941+
],
3942+
errors: [
3943+
{
3944+
messageId: 'incorrectGroupOrder',
3945+
data: {
3946+
name: 'constructor',
3947+
rank: 'private decorated field, public set, private method',
3948+
},
3949+
line: 5,
3950+
column: 5,
3951+
},
3952+
],
3953+
},
3954+
{
3955+
code: `
3956+
class Foo {
3957+
A: string;
3958+
constructor() {}
3959+
get B() {}
3960+
set B() {}
3961+
get C() {}
3962+
set C() {}
3963+
D(): void;
3964+
} `,
3965+
options: [
3966+
{
3967+
default: ['field', 'constructor', 'get', ['set'], 'method'],
3968+
},
3969+
],
3970+
errors: [
3971+
{
3972+
messageId: 'incorrectGroupOrder',
3973+
data: {
3974+
name: 'C',
3975+
rank: 'set',
3976+
},
3977+
line: 7,
3978+
column: 5,
3979+
},
3980+
],
3981+
},
38263982
],
38273983
};
38283984

0 commit comments

Comments
 (0)