Skip to content

Commit 9d3bdfc

Browse files
feat(eslint-plugin): [member-ordering] add support for grouping readonly fields (#6349)
* feat(eslint-plugin): [member-ordering] add support for grouping readonly fields * refactor: inline readonly assertions * chore: remove comments * test: add more tests for readonly TSAbstractPropertyDefinition and TSPropertySignature * feat: add support for readonly signatures --------- Co-authored-by: Josh Goldberg <[email protected]>
1 parent d55211c commit 9d3bdfc

File tree

3 files changed

+729
-14
lines changed

3 files changed

+729
-14
lines changed

packages/eslint-plugin/docs/rules/member-ordering.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ The supported member attributes are, in order:
5858

5959
- **Accessibility** (`'public' | 'protected' | 'private' | '#private'`)
6060
- **Decoration** (`'decorated'`): Whether the member has an explicit accessibility decorator
61-
- **Kind** (`'call-signature' | 'constructor' | 'field' | 'get' | 'method' | 'set' | 'signature'`)
61+
- **Kind** (`'call-signature' | 'constructor' | 'field' | 'readonly-field' | 'get' | 'method' | 'set' | 'signature' | 'readonly-signature'`)
6262

6363
Member attributes may be joined with a `'-'` to combine into more specific groups.
6464
For example, `'public-field'` would come before `'private-field'`.
@@ -1014,37 +1014,60 @@ The most explicit and granular form is the following:
10141014
[
10151015
// Index signature
10161016
"signature",
1017+
"readonly-signature",
10171018

10181019
// Fields
10191020
"public-static-field",
1021+
"public-static-readonly-field",
10201022
"protected-static-field",
1023+
"protected-static-readonly-field",
10211024
"private-static-field",
1025+
"private-static-readonly-field",
10221026
"#private-static-field",
1027+
"#private-static-readonly-field",
10231028

10241029
"public-decorated-field",
1030+
"public-decorated-readonly-field",
10251031
"protected-decorated-field",
1032+
"protected-decorated-readonly-field",
10261033
"private-decorated-field",
1034+
"private-decorated-readonly-field",
10271035

10281036
"public-instance-field",
1037+
"public-instance-readonly-field",
10291038
"protected-instance-field",
1039+
"protected-instance-readonly-field",
10301040
"private-instance-field",
1041+
"private-instance-readonly-field",
10311042
"#private-instance-field",
1043+
"#private-instance-readonly-field",
10321044

10331045
"public-abstract-field",
1046+
"public-abstract-readonly-field",
10341047
"protected-abstract-field",
1048+
"protected-abstract-readonly-field",
10351049

10361050
"public-field",
1051+
"public-readonly-field",
10371052
"protected-field",
1053+
"protected-readonly-field",
10381054
"private-field",
1055+
"private-readonly-field"
10391056
"#private-field",
1057+
"#private-readonly-field"
10401058

10411059
"static-field",
1060+
"static-readonly-field",
10421061
"instance-field",
1062+
"instance-readonly-field"
10431063
"abstract-field",
1064+
"abstract-readonly-field",
10441065

10451066
"decorated-field",
1067+
"decorated-readonly-field",
10461068

10471069
"field",
1070+
"readonly-field",
10481071

10491072
// Static initialization
10501073
"static-initialization",
@@ -1290,6 +1313,22 @@ The third grouping option is to ignore both scope and accessibility.
12901313
]
12911314
```
12921315

1316+
### Member Group Types (Readonly Fields)
1317+
1318+
It is possible to group fields by their `readonly` modifiers.
1319+
1320+
```jsonc
1321+
[
1322+
// Index signature
1323+
"readonly-signature",
1324+
"signature",
1325+
1326+
// Fields
1327+
"readonly-field", // = ["public-static-readonly-field", "protected-static-readonly-field", "private-static-readonly-field", "public-instance-readonly-field", "protected-instance-readonly-field", "private-instance-readonly-field", "public-abstract-readonly-field", "protected-abstract-readonly-field"]
1328+
"field" // = ["public-static-field", "protected-static-field", "private-static-field", "public-instance-field", "protected-instance-field", "private-instance-field", "public-abstract-field", "protected-abstract-field"]
1329+
]
1330+
```
1331+
12931332
### Grouping Different Member Types at the Same Rank
12941333

12951334
It is also possible to group different member types at the same rank.

packages/eslint-plugin/src/rules/member-ordering.ts

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,30 @@ export type MessageIds =
99
| 'incorrectOrder'
1010
| 'incorrectRequiredMembersOrder';
1111

12+
type ReadonlyType = 'readonly-field' | 'readonly-signature';
13+
1214
type MemberKind =
1315
| 'call-signature'
1416
| 'constructor'
17+
| ReadonlyType
1518
| 'field'
1619
| 'get'
1720
| 'method'
1821
| 'set'
1922
| 'signature'
2023
| 'static-initialization';
2124

22-
type DecoratedMemberKind = 'field' | 'method' | 'get' | 'set';
25+
type DecoratedMemberKind =
26+
| Exclude<ReadonlyType, 'readonly-signature'>
27+
| 'field'
28+
| 'method'
29+
| 'get'
30+
| 'set';
2331

24-
type NonCallableMemberKind = Exclude<MemberKind, 'constructor' | 'signature'>;
32+
type NonCallableMemberKind = Exclude<
33+
MemberKind,
34+
'constructor' | 'signature' | 'readonly-signature'
35+
>;
2536

2637
type MemberScope = 'static' | 'instance' | 'abstract';
2738

@@ -31,7 +42,7 @@ type BaseMemberType =
3142
| MemberKind
3243
| `${Accessibility}-${Exclude<
3344
MemberKind,
34-
'signature' | 'static-initialization'
45+
'signature' | 'readonly-signature' | 'static-initialization'
3546
>}`
3647
| `${Accessibility}-decorated-${DecoratedMemberKind}`
3748
| `decorated-${DecoratedMemberKind}`
@@ -258,7 +269,9 @@ export const defaultOrder: MemberType[] = [
258269
const allMemberTypes = Array.from(
259270
(
260271
[
272+
'readonly-signature',
261273
'signature',
274+
'readonly-field',
262275
'field',
263276
'method',
264277
'call-signature',
@@ -273,6 +286,7 @@ const allMemberTypes = Array.from(
273286
(['public', 'protected', 'private', '#private'] as const).forEach(
274287
accessibility => {
275288
if (
289+
type !== 'readonly-signature' &&
276290
type !== 'signature' &&
277291
type !== 'static-initialization' &&
278292
type !== 'call-signature' &&
@@ -284,7 +298,8 @@ const allMemberTypes = Array.from(
284298
// Only class instance fields, methods, get and set can have decorators attached to them
285299
if (
286300
accessibility !== '#private' &&
287-
(type === 'field' ||
301+
(type === 'readonly-field' ||
302+
type === 'field' ||
288303
type === 'method' ||
289304
type === 'get' ||
290305
type === 'set')
@@ -295,6 +310,7 @@ const allMemberTypes = Array.from(
295310

296311
if (
297312
type !== 'constructor' &&
313+
type !== 'readonly-signature' &&
298314
type !== 'signature' &&
299315
type !== 'call-signature'
300316
) {
@@ -340,15 +356,17 @@ function getNodeType(node: Member): MemberKind | null {
340356
case AST_NODE_TYPES.TSConstructSignatureDeclaration:
341357
return 'constructor';
342358
case AST_NODE_TYPES.TSAbstractPropertyDefinition:
343-
return 'field';
359+
return node.readonly ? 'readonly-field' : 'field';
344360
case AST_NODE_TYPES.PropertyDefinition:
345361
return node.value && functionExpressions.includes(node.value.type)
346362
? 'method'
363+
: node.readonly
364+
? 'readonly-field'
347365
: 'field';
348366
case AST_NODE_TYPES.TSPropertySignature:
349-
return 'field';
367+
return node.readonly ? 'readonly-field' : 'field';
350368
case AST_NODE_TYPES.TSIndexSignature:
351-
return 'signature';
369+
return node.readonly ? 'readonly-signature' : 'signature';
352370
case AST_NODE_TYPES.StaticBlock:
353371
return 'static-initialization';
354372
default:
@@ -514,27 +532,50 @@ function getRank(
514532
const decorated = 'decorators' in node && node.decorators!.length > 0;
515533
if (
516534
decorated &&
517-
(type === 'field' ||
535+
(type === 'readonly-field' ||
536+
type === 'field' ||
518537
type === 'method' ||
519538
type === 'get' ||
520539
type === 'set')
521540
) {
522541
memberGroups.push(`${accessibility}-decorated-${type}`);
523542
memberGroups.push(`decorated-${type}`);
543+
544+
if (type === 'readonly-field') {
545+
memberGroups.push(`${accessibility}-decorated-field`);
546+
memberGroups.push(`decorated-field`);
547+
}
524548
}
525549

526-
if (type !== 'signature' && type !== 'static-initialization') {
550+
if (
551+
type !== 'readonly-signature' &&
552+
type !== 'signature' &&
553+
type !== 'static-initialization'
554+
) {
527555
if (type !== 'constructor') {
528556
// Constructors have no scope
529557
memberGroups.push(`${accessibility}-${scope}-${type}`);
530558
memberGroups.push(`${scope}-${type}`);
559+
560+
if (type === 'readonly-field') {
561+
memberGroups.push(`${accessibility}-${scope}-field`);
562+
memberGroups.push(`${scope}-field`);
563+
}
531564
}
532565

533566
memberGroups.push(`${accessibility}-${type}`);
567+
if (type === 'readonly-field') {
568+
memberGroups.push(`${accessibility}-field`);
569+
}
534570
}
535571
}
536572

537573
memberGroups.push(type);
574+
if (type === 'readonly-signature') {
575+
memberGroups.push('signature');
576+
} else if (type === 'readonly-field') {
577+
memberGroups.push('field');
578+
}
538579

539580
// ...then get the rank order for those member groups based on the node
540581
return getRankOrder(memberGroups, orderConfig);
@@ -621,15 +662,43 @@ export default util.createRule<Options, MessageIds>({
621662
interfaces: {
622663
oneOf: [
623664
neverConfig,
624-
arrayConfig(['signature', 'field', 'method', 'constructor']),
625-
objectConfig(['signature', 'field', 'method', 'constructor']),
665+
arrayConfig([
666+
'readonly-signature',
667+
'signature',
668+
'readonly-field',
669+
'field',
670+
'method',
671+
'constructor',
672+
]),
673+
objectConfig([
674+
'readonly-signature',
675+
'signature',
676+
'readonly-field',
677+
'field',
678+
'method',
679+
'constructor',
680+
]),
626681
],
627682
},
628683
typeLiterals: {
629684
oneOf: [
630685
neverConfig,
631-
arrayConfig(['signature', 'field', 'method', 'constructor']),
632-
objectConfig(['signature', 'field', 'method', 'constructor']),
686+
arrayConfig([
687+
'readonly-signature',
688+
'signature',
689+
'readonly-field',
690+
'field',
691+
'method',
692+
'constructor',
693+
]),
694+
objectConfig([
695+
'readonly-signature',
696+
'signature',
697+
'readonly-field',
698+
'field',
699+
'method',
700+
'constructor',
701+
]),
633702
],
634703
},
635704
},

0 commit comments

Comments
 (0)