From 10ff7644ceebd84bec78dacc9af58060e3593d76 Mon Sep 17 00:00:00 2001 From: Svyatoslav Zaytsev Date: Sun, 23 Oct 2022 17:36:40 +0300 Subject: [PATCH] fix(eslint-plugin): Support private fields (#4182) --- .../docs/rules/member-ordering.md | 50 +++- .../src/rules/member-ordering.ts | 102 ++++--- .../tests/rules/member-ordering.test.ts | 249 +++++++++++++++++- 3 files changed, 351 insertions(+), 50 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index 9a6ca8d5dbc8..cc8ee1100f3c 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -49,7 +49,7 @@ The `OrderConfig` settings for each kind of construct may configure sorting on o You can define many different groups based on different attributes of members. The supported member attributes are, in order: -- **Accessibility** (`'public' | 'protected' | 'private'`) +- **Accessibility** (`'public' | 'protected' | 'private' | '#private'`) - **Decoration** (`'decorated'`): Whether the member has an explicit accessibility decorator - **Kind** (`'call-signature' | 'constructor' | 'field' | 'get' | 'method' | 'set' | 'signature'`) @@ -65,11 +65,13 @@ The default configuration looks as follows: "default": [ // Index signature "signature", + "call-signature", // Fields "public-static-field", "protected-static-field", "private-static-field", + "#private-static-field", "public-decorated-field", "protected-decorated-field", @@ -78,14 +80,15 @@ The default configuration looks as follows: "public-instance-field", "protected-instance-field", "private-instance-field", + "#private-instance-field", "public-abstract-field", "protected-abstract-field", - "private-abstract-field", "public-field", "protected-field", "private-field", + "#private-field", "static-field", "instance-field", @@ -109,6 +112,7 @@ The default configuration looks as follows: "public-static-get", "protected-static-get", "private-static-get", + "#private-static-get", "public-decorated-get", "protected-decorated-get", @@ -117,14 +121,15 @@ The default configuration looks as follows: "public-instance-get", "protected-instance-get", "private-instance-get", + "#private-instance-get", "public-abstract-get", "protected-abstract-get", - "private-abstract-get", "public-get", "protected-get", "private-get", + "#private-get", "static-get", "instance-get", @@ -138,6 +143,7 @@ The default configuration looks as follows: "public-static-set", "protected-static-set", "private-static-set", + "#private-static-set", "public-decorated-set", "protected-decorated-set", @@ -146,14 +152,15 @@ The default configuration looks as follows: "public-instance-set", "protected-instance-set", "private-instance-set", + "#private-instance-set", "public-abstract-set", "protected-abstract-set", - "private-abstract-set", "public-set", "protected-set", "private-set", + "#private-set", "static-set", "instance-set", @@ -167,6 +174,7 @@ The default configuration looks as follows: "public-static-method", "protected-static-method", "private-static-method", + "#private-static-method", "public-decorated-method", "protected-decorated-method", @@ -175,14 +183,15 @@ The default configuration looks as follows: "public-instance-method", "protected-instance-method", "private-instance-method", + "#private-instance-method", "public-abstract-method", "protected-abstract-method", - "private-abstract-method", "public-method", "protected-method", "private-method", + "#private-method", "static-method", "instance-method", @@ -902,15 +911,32 @@ The most explicit and granular form is the following: "public-static-field", "protected-static-field", "private-static-field", + "#private-static-field", + "public-decorated-field", "protected-decorated-field", "private-decorated-field", + "public-instance-field", "protected-instance-field", "private-instance-field", + "#private-instance-field", + "public-abstract-field", "protected-abstract-field", - "private-abstract-field", + + "public-field", + "protected-field", + "private-field", + "#private-field", + + "static-field", + "instance-field", + "abstract-field", + + "decorated-field", + + "field", // Static initialization "static-initialization", @@ -924,6 +950,7 @@ The most explicit and granular form is the following: "public-static-get", "protected-static-get", "private-static-get", + "#private-static-get", "public-decorated-get", "protected-decorated-get", @@ -932,14 +959,15 @@ The most explicit and granular form is the following: "public-instance-get", "protected-instance-get", "private-instance-get", + "#private-instance-get", "public-abstract-get", "protected-abstract-get", - "private-abstract-get", "public-get", "protected-get", "private-get", + "#private-get", "static-get", "instance-get", @@ -953,6 +981,7 @@ The most explicit and granular form is the following: "public-static-set", "protected-static-set", "private-static-set", + "#private-static-set", "public-decorated-set", "protected-decorated-set", @@ -961,10 +990,10 @@ The most explicit and granular form is the following: "public-instance-set", "protected-instance-set", "private-instance-set", + "#private-instance-set", "public-abstract-set", "protected-abstract-set", - "private-abstract-set", "public-set", "protected-set", @@ -982,15 +1011,16 @@ The most explicit and granular form is the following: "public-static-method", "protected-static-method", "private-static-method", + "#private-static-method", "public-decorated-method", "protected-decorated-method", "private-decorated-method", "public-instance-method", "protected-instance-method", "private-instance-method", + "#private-instance-method", "public-abstract-method", - "protected-abstract-method", - "private-abstract-method" + "protected-abstract-method" ] ``` diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 66fe9b623342..e3343aaf4fbd 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -21,15 +21,17 @@ type NonCallableMemberKind = Exclude; type MemberScope = 'static' | 'instance' | 'abstract'; +type Accessibility = TSESTree.Accessibility | '#private'; + type BaseMemberType = | MemberKind - | `${TSESTree.Accessibility}-${Exclude< + | `${Accessibility}-${Exclude< MemberKind, 'signature' | 'static-initialization' >}` - | `${TSESTree.Accessibility}-decorated-${DecoratedMemberKind}` + | `${Accessibility}-decorated-${DecoratedMemberKind}` | `decorated-${DecoratedMemberKind}` - | `${TSESTree.Accessibility}-${MemberScope}-${NonCallableMemberKind}` + | `${Accessibility}-${MemberScope}-${NonCallableMemberKind}` | `${MemberScope}-${NonCallableMemberKind}`; type MemberType = BaseMemberType | BaseMemberType[]; @@ -102,6 +104,7 @@ export const defaultOrder: MemberType[] = [ 'public-static-field', 'protected-static-field', 'private-static-field', + '#private-static-field', 'public-decorated-field', 'protected-decorated-field', @@ -110,14 +113,15 @@ export const defaultOrder: MemberType[] = [ 'public-instance-field', 'protected-instance-field', 'private-instance-field', + '#private-instance-field', 'public-abstract-field', 'protected-abstract-field', - 'private-abstract-field', 'public-field', 'protected-field', 'private-field', + '#private-field', 'static-field', 'instance-field', @@ -141,6 +145,7 @@ export const defaultOrder: MemberType[] = [ 'public-static-get', 'protected-static-get', 'private-static-get', + '#private-static-get', 'public-decorated-get', 'protected-decorated-get', @@ -149,14 +154,15 @@ export const defaultOrder: MemberType[] = [ 'public-instance-get', 'protected-instance-get', 'private-instance-get', + '#private-instance-get', 'public-abstract-get', 'protected-abstract-get', - 'private-abstract-get', 'public-get', 'protected-get', 'private-get', + '#private-get', 'static-get', 'instance-get', @@ -170,6 +176,7 @@ export const defaultOrder: MemberType[] = [ 'public-static-set', 'protected-static-set', 'private-static-set', + '#private-static-set', 'public-decorated-set', 'protected-decorated-set', @@ -178,14 +185,15 @@ export const defaultOrder: MemberType[] = [ 'public-instance-set', 'protected-instance-set', 'private-instance-set', + '#private-instance-set', 'public-abstract-set', 'protected-abstract-set', - 'private-abstract-set', 'public-set', 'protected-set', 'private-set', + '#private-set', 'static-set', 'instance-set', @@ -199,6 +207,7 @@ export const defaultOrder: MemberType[] = [ 'public-static-method', 'protected-static-method', 'private-static-method', + '#private-static-method', 'public-decorated-method', 'protected-decorated-method', @@ -207,14 +216,15 @@ export const defaultOrder: MemberType[] = [ 'public-instance-method', 'protected-instance-method', 'private-instance-method', + '#private-instance-method', 'public-abstract-method', 'protected-abstract-method', - 'private-abstract-method', 'public-method', 'protected-method', 'private-method', + '#private-method', 'static-method', 'instance-method', @@ -240,30 +250,49 @@ const allMemberTypes = Array.from( ).reduce>((all, type) => { all.add(type); - (['public', 'protected', 'private'] as const).forEach(accessibility => { - if (type !== 'signature' && type !== 'static-initialization') { - all.add(`${accessibility}-${type}`); // e.g. `public-field` - } + (['public', 'protected', 'private', '#private'] as const).forEach( + accessibility => { + if ( + type !== 'signature' && + type !== 'static-initialization' && + type !== 'call-signature' && + !(type === 'constructor' && accessibility === '#private') + ) { + all.add(`${accessibility}-${type}`); // e.g. `public-field` + } - // Only class instance fields, methods, get and set can have decorators attached to them - if ( - type === 'field' || - type === 'method' || - type === 'get' || - type === 'set' - ) { - all.add(`${accessibility}-decorated-${type}`); - all.add(`decorated-${type}`); - } + // Only class instance fields, methods, get and set can have decorators attached to them + if ( + accessibility !== '#private' && + (type === 'field' || + type === 'method' || + type === 'get' || + type === 'set') + ) { + all.add(`${accessibility}-decorated-${type}`); + all.add(`decorated-${type}`); + } - if (type !== 'constructor' && type !== 'signature') { - // There is no `static-constructor` or `instance-constructor` or `abstract-constructor` - (['static', 'instance', 'abstract'] as const).forEach(scope => { - all.add(`${scope}-${type}`); - all.add(`${accessibility}-${scope}-${type}`); - }); - } - }); + if ( + type !== 'constructor' && + type !== 'signature' && + type !== 'call-signature' + ) { + // There is no `static-constructor` or `instance-constructor` or `abstract-constructor` + if (accessibility === '#private' || accessibility === 'private') { + (['static', 'instance'] as const).forEach(scope => { + all.add(`${scope}-${type}`); + all.add(`${accessibility}-${scope}-${type}`); + }); + } else { + (['static', 'instance', 'abstract'] as const).forEach(scope => { + all.add(`${scope}-${type}`); + all.add(`${accessibility}-${scope}-${type}`); + }); + } + } + }, + ); return all; }, new Set()), @@ -397,6 +426,16 @@ function getRankOrder( return rank; } +function getAccessibility(node: Member): Accessibility { + if ('accessibility' in node && node.accessibility) { + return node.accessibility; + } + if ('key' in node && node.key?.type === AST_NODE_TYPES.PrivateIdentifier) { + return '#private'; + } + return 'public'; +} + /** * Gets the rank of the node given the order. * @param node the node to be evaluated. @@ -425,10 +464,7 @@ function getRank( : abstract ? 'abstract' : 'instance'; - const accessibility = - 'accessibility' in node && node.accessibility - ? node.accessibility - : 'public'; + const accessibility = getAccessibility(node); // Collect all existing member groups that apply to this node... // (e.g. 'public-instance-field', 'instance-field', 'public-field', 'constructor' etc.) diff --git a/packages/eslint-plugin/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index 68bbfa7eff6e..e4b0d712635d 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -423,16 +423,20 @@ class Foo { public static A: string; protected static B: string = ""; private static C: string = ""; + static #C: string = ""; public D: string = ""; protected E: string = ""; private F: string = ""; + #F: string = ""; constructor() {} public static G() {} protected static H() {} private static I() {} + static #I() {} public J() {} protected K() {} private L() {} + #L() {} } `, { @@ -442,16 +446,20 @@ class Foo { public static A: string; protected static B: string = ""; private static C: string = ""; + static #C: string = ""; public D: string = ""; protected E: string = ""; private F: string = ""; + #F: string = ""; constructor() {} public static G() {} protected static H() {} private static I() {} + static #I() {} public J() {} protected K() {} private L() {} + #L() {} } `, options: [{ default: 'never' }], @@ -463,16 +471,20 @@ class Foo { public static A: string; protected static B: string = ""; private static C: string = ""; + static #C: string = ""; public D: string = ""; protected E: string = ""; private F: string = ""; + #F: string = ""; constructor() {} public static G() {} protected static H() {} private static I() {} + static #I() {} public J() {} protected K() {} private L() {} + #L() {} } `, options: [{ default: ['signature', 'field', 'constructor', 'method'] }], @@ -485,15 +497,19 @@ class Foo { public static A: string; protected static B: string = ""; private static C: string = ""; + static #C: string = ""; public D: string = ""; protected E: string = ""; private F: string = ""; + #F: string = ""; public static G() {} protected static H() {} private static I() {} + static #I() {} public J() {} protected K() {} private L() {} + #L() {} } `, options: [{ default: ['field', 'method'] }], @@ -525,16 +541,20 @@ class Foo { public static G() {} protected static H() {} private static I() {} + static #I() {} public J() {} protected K() {} private L() {} + #L() {} [Z: string]: any; public static A: string; protected static B: string = ""; private static C: string = ""; + static #C: string = ""; public D: string = ""; protected E: string = ""; private F: string = ""; + #F: string = ""; constructor() {} } `, @@ -617,6 +637,8 @@ class Foo { public static A: string; private static C: string = ""; private F: string = ""; + static #M: string = ""; + #N: string = ""; protected static B: string = ""; protected E: string = ""; } @@ -629,6 +651,7 @@ class Foo { 'constructor', 'public-field', 'private-field', + '#private-field', 'protected-field', ], }, @@ -651,6 +674,7 @@ class Foo { protected E: string = ""; private static C: string = ""; private F: string = ""; + #M: string = ""; } `, options: [ @@ -665,6 +689,7 @@ class Foo { 'public-field', 'protected-field', 'private-field', + '#private-field', ], }, ], @@ -679,12 +704,16 @@ class Foo { constructor() {} protected K() {} private L() {} + #P() {} protected static H() {} private static I() {} + static #O() {} protected static B: string = ""; private static C: string = ""; + static #N: string = ""; protected E: string = ""; private F: string = ""; + #M: string = ""; [Z: string]: any; } `, @@ -708,15 +737,19 @@ class Foo { public static G() {} protected static H() {} private static I() {} + static #I() {} protected K() {} private L() {} + #L() {} constructor() {} [Z: string]: any; public static A: string; private F: string = ""; + #F: string = ""; protected static B: string = ""; public D: string = ""; private static C: string = ""; + static #C: string = ""; protected E: string = ""; } `, @@ -725,9 +758,10 @@ class Foo { classes: [ 'public-method', 'protected-static-method', - 'private-static-method', + '#private-static-method', 'protected-instance-method', 'private-instance-method', + '#private-instance-method', 'constructor', 'signature', 'field', @@ -762,6 +796,31 @@ class Foo { }, { code: ` +class Foo { + private L() {} + private static I() {} + static #H() {} + static #B: string = ""; + public static G() {} + public J() {} + #K() {} + private static C: string = ""; + private F: string = ""; + #E: string = ""; + public static A: string; + public D: string = ""; + constructor() {} + [Z: string]: any; +} + `, + options: [ + { + classes: ['private-instance-method', 'protected-static-field'], + }, + ], + }, + { + code: ` class Foo { private L() {} private static I() {} @@ -1517,6 +1576,60 @@ class Foo { }, ], }, + { + name: 'with private identifier', + code: ` +// no accessibility === public +class Foo { + imPublic() {}; + #imPrivate() {}; +} + `, + options: [ + { + default: { + memberTypes: ['public-method', '#private-method'], + order: 'alphabetically-case-insensitive', + }, + }, + ], + }, + { + name: 'private and #private member order', + code: ` +// no accessibility === public +class Foo { + private imPrivate() {}; + #imPrivate() {}; +} + `, + options: [ + { + default: { + memberTypes: ['private-method', '#private-method'], + order: 'alphabetically-case-insensitive', + }, + }, + ], + }, + { + name: '#private and private member order', + code: ` +// no accessibility === public +class Foo { + #imPrivate() {}; + private imPrivate() {}; +} + `, + options: [ + { + default: { + memberTypes: ['#private-method', 'private-method'], + order: 'alphabetically-case-insensitive', + }, + }, + ], + }, ], invalid: [ { @@ -2338,16 +2451,20 @@ class Foo { public static A: string = ""; protected static B: string = ""; private static C: string = ""; + static #C: string = ""; public D: string = ""; protected E: string = ""; private F: string = ""; + #F: string = ""; constructor() {} public J() {} protected K() {} private L() {} + #L() {} public static G() {} protected static H() {} private static I() {} + static #I() {} } `, errors: [ @@ -2357,7 +2474,7 @@ class Foo { name: 'G', rank: 'public instance method', }, - line: 14, + line: 17, column: 5, }, { @@ -2366,7 +2483,7 @@ class Foo { name: 'H', rank: 'public instance method', }, - line: 15, + line: 18, column: 5, }, { @@ -2375,7 +2492,16 @@ class Foo { name: 'I', rank: 'public instance method', }, - line: 16, + line: 19, + column: 5, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'I', + rank: 'public instance method', + }, + line: 20, column: 5, }, ], @@ -2387,15 +2513,19 @@ class Foo { public static A: string = ""; protected static B: string = ""; private static C: string = ""; + static #C: string = ""; public D: string = ""; protected E: string = ""; private F: string = ""; + #F: string = ""; public J() {} protected K() {} private L() {} + #L() {} public static G() {} protected static H() {} private static I() {} + static #I() {} [Z: string]: any; } `, @@ -2431,7 +2561,7 @@ class Foo { { messageId: 'incorrectGroupOrder', data: { - name: 'D', + name: 'C', rank: 'constructor', }, line: 7, @@ -2440,7 +2570,7 @@ class Foo { { messageId: 'incorrectGroupOrder', data: { - name: 'E', + name: 'D', rank: 'constructor', }, line: 8, @@ -2449,12 +2579,30 @@ class Foo { { messageId: 'incorrectGroupOrder', data: { - name: 'F', + name: 'E', rank: 'constructor', }, line: 9, column: 5, }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'F', + rank: 'constructor', + }, + line: 10, + column: 5, + }, + { + messageId: 'incorrectGroupOrder', + data: { + name: 'F', + rank: 'constructor', + }, + line: 11, + column: 5, + }, ], }, { @@ -4138,6 +4286,93 @@ class Foo { }, ], }, + { + name: 'with private identifier', + code: ` +// no accessibility === public +class Foo { + #imPrivate() {}; + imPublic() {}; +} + `, + options: [ + { + default: { + memberTypes: ['public-method', '#private-method'], + order: 'alphabetically-case-insensitive', + }, + }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'imPublic', + rank: '#private method', + }, + line: 5, + column: 5, + }, + ], + }, + { + name: 'private and #private member order', + code: ` +// no accessibility === public +class Foo { + #imPrivate() {}; + private imPrivate() {}; +} + `, + options: [ + { + default: { + memberTypes: ['private-method', '#private-method'], + order: 'alphabetically-case-insensitive', + }, + }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'imPrivate', + rank: '#private method', + }, + line: 5, + column: 5, + }, + ], + }, + { + name: '#private and private member order', + code: ` +// no accessibility === public +class Foo { + private imPrivate() {}; + #imPrivate() {}; +} + `, + options: [ + { + default: { + memberTypes: ['#private-method', 'private-method'], + order: 'alphabetically-case-insensitive', + }, + }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'imPrivate', + rank: 'private method', + }, + line: 5, + column: 5, + }, + ], + }, ], };