Skip to content

feat(eslint-plugin): Support abstract members in member-ordering rule (#395) #1004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 16, 2019
28 changes: 24 additions & 4 deletions packages/eslint-plugin/docs/rules/member-ordering.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ There are multiple ways to specify the member types. The most explicit and granu
'public-instance-field',
'protected-instance-field',
'private-instance-field',
'public-abstract-field',
'protected-abstract-field',
'private-abstract-field',

// Constructors
'public-constructor',
Expand All @@ -50,14 +53,17 @@ There are multiple ways to specify the member types. The most explicit and granu
'public-instance-method',
'protected-instance-method',
'private-instance-method',
'public-abstract-method',
'protected-abstract-method',
'private-abstract-method',
]
```

Note: If you only specify some of the possible types, the non-specified ones can have any particular order. This means that they can be placed before, within or after the specified types and the linter won't complain about it.

### Member group types (with accessibility, ignoring scope)

It is also possible to group member types by their accessibility (`static`, `instance`), ignoring their scope.
It is also possible to group member types by their accessibility (`static`, `instance`, `abstract`), ignoring their scope.

```json5
[
Expand Down Expand Up @@ -85,13 +91,15 @@ Another option is to group the member types by their scope (`public`, `protected
// Fields
'static-field', // = ['public-static-field', 'protected-static-field', 'private-static-field'])
'instance-field', // = ['public-instance-field', 'protected-instance-field', 'private-instance-field'])
'abstract-field', // = ['public-abstract-field', 'protected-abstract-field', 'private-abstract-field'])

// Constructors
'constructor', // = ['public-constructor', 'protected-constructor', 'private-constructor'])

// Methods
'static-method', // = ['public-static-method', 'protected-static-method', 'private-static-method'])
'instance-method', // = ['public-instance-method', 'protected-instance-method', 'private-instance-method']
'instance-method', // = ['public-instance-method', 'protected-instance-method', 'private-instance-method'])
'abstract-method', // = ['public-abstract-method', 'protected-abstract-method', 'private-abstract-method'])
]
```

Expand All @@ -102,13 +110,15 @@ The third grouping option is to ignore both scope and accessibility.
```json5
[
// Fields
'field', // = ['public-static-field', 'protected-static-field', 'private-static-field', 'public-instance-field', 'protected-instance-field', 'private-instance-field'])
'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', private-abstract-field'])

// Constructors
// Only the accessibility of constructors is configurable. See above.

// Methods
'method', // = ['public-static-method', 'protected-static-method', 'private-static-method', 'public-instance-method', 'protected-instance-method', 'private-instance-method'])
'method', // = ['public-static-method', 'protected-static-method', 'private-static-method', 'public-instance-method', 'protected-instance-method', 'private-instance-method',
// 'public-abstract-method', 'protected-abstract-method', 'private-abstract-method'])
]
```

Expand All @@ -127,12 +137,17 @@ The default configuration looks as follows:
"protected-instance-field",
"private-instance-field",

"public-abstract-field",
"protected-abstract-field",
"private-abstract-field",

"public-field",
"protected-field",
"private-field",

"static-field",
"instance-field",
"abstract-field",

"field",

Expand All @@ -146,12 +161,17 @@ The default configuration looks as follows:
"protected-instance-method",
"private-instance-method",

"public-abstract-method",
"protected-abstract-method",
"private-abstract-method",

"public-method",
"protected-method",
"private-method",

"static-method",
"instance-method",
"abstract-method",

"method"
]
Expand Down
31 changes: 26 additions & 5 deletions packages/eslint-plugin/src/rules/member-ordering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const allMemberTypes = ['field', 'method', 'constructor'].reduce<string[]>(
all.push(`${accessibility}-${type}`); // e.g. `public-field`

if (type !== 'constructor') {
// There is no `static-constructor` or `instance-constructor
['static', 'instance'].forEach(scope => {
// There is no `static-constructor` or `instance-constructor or `abstract-constructor`
['static', 'instance', 'abstract'].forEach(scope => {
if (!all.includes(`${scope}-${type}`)) {
all.push(`${scope}-${type}`);
}
Expand Down Expand Up @@ -138,12 +138,17 @@ export default util.createRule<Options, MessageIds>({
'protected-instance-field',
'private-instance-field',

'public-abstract-field',
'protected-abstract-field',
'private-abstract-field',

'public-field',
'protected-field',
'private-field',

'static-field',
'instance-field',
'abstract-field',

'field',

Expand All @@ -157,12 +162,17 @@ export default util.createRule<Options, MessageIds>({
'protected-instance-method',
'private-instance-method',

'public-abstract-method',
'protected-abstract-method',
'private-abstract-method',

'public-method',
'protected-method',
'private-method',

'static-method',
'instance-method',
'abstract-method',

'method',
],
Expand All @@ -185,15 +195,15 @@ export default util.createRule<Options, MessageIds>({
): string | null {
// TODO: add missing TSCallSignatureDeclaration
// TODO: add missing TSIndexSignature
// TODO: add missing TSAbstractClassProperty
// TODO: add missing TSAbstractMethodDefinition
switch (node.type) {
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.MethodDefinition:
return node.kind;
case AST_NODE_TYPES.TSMethodSignature:
return 'method';
case AST_NODE_TYPES.TSConstructSignatureDeclaration:
return 'constructor';
case AST_NODE_TYPES.TSAbstractClassProperty:
case AST_NODE_TYPES.ClassProperty:
return node.value && functionExpressions.includes(node.value.type)
? 'method'
Expand All @@ -215,8 +225,10 @@ export default util.createRule<Options, MessageIds>({
switch (node.type) {
case AST_NODE_TYPES.TSPropertySignature:
case AST_NODE_TYPES.TSMethodSignature:
case AST_NODE_TYPES.TSAbstractClassProperty:
case AST_NODE_TYPES.ClassProperty:
return util.getNameFromPropertyName(node.key);
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.MethodDefinition:
return node.kind === 'constructor'
? 'constructor'
Expand Down Expand Up @@ -268,7 +280,16 @@ export default util.createRule<Options, MessageIds>({
return order.length - 1;
}

const scope = 'static' in node && node.static ? 'static' : 'instance';
const abstract =
node.type === 'TSAbstractClassProperty' ||
node.type === 'TSAbstractMethodDefinition';

const scope =
'static' in node && node.static
? 'static'
: abstract
? 'abstract'
: 'instance';
const accessibility =
'accessibility' in node && node.accessibility
? node.accessibility
Expand Down
5 changes: 4 additions & 1 deletion packages/eslint-plugin/src/util/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ export function findFirstResult<T, U>(
* or ClassProperty node, with handling for computed property names.
*/
export function getNameFromClassMember(
methodDefinition: TSESTree.MethodDefinition | TSESTree.ClassProperty,
methodDefinition:
| TSESTree.MethodDefinition
| TSESTree.ClassProperty
| TSESTree.TSAbstractMethodDefinition,
sourceCode: TSESLint.SourceCode,
): string {
if (keyCanBeReadAsPropertyName(methodDefinition.key)) {
Expand Down
144 changes: 141 additions & 3 deletions packages/eslint-plugin/tests/rules/member-ordering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1211,12 +1211,45 @@ type Foo = {
`,
options: [{ default: ['method', 'constructor', 'field'] }],
},
`
{
code: `
abstract class Foo {
B: string;
abstract A: () => {}
}
`,
},
{
code: `
interface Foo {
public B: string;
[A:string]: number;
}
`,
},
{
code: `
abstract class Foo {
private static C: string;
B: string;
private D: string;
protected static F(): {};
public E(): {};
public abstract A = () => {};
protected abstract G(): void;
}
`,
},
{
code: `
abstract class Foo {
protected typeChecker: (data: any) => boolean;
public abstract required: boolean;
abstract verify(): void;
}
`,
options: [{ classes: ['field', 'constructor', 'method'] }],
},
],
invalid: [
{
Expand Down Expand Up @@ -3319,7 +3352,7 @@ type Foo = {
{
code: `
abstract class Foo {
abstract A: () => {}
abstract A = () => {};
B: string;
}
`,
Expand All @@ -3328,12 +3361,117 @@ abstract class Foo {
messageId: 'incorrectOrder',
data: {
name: 'B',
rank: 'method',
rank: 'public abstract method',
},
line: 4,
column: 5,
},
],
},
{
code: `
abstract class Foo {
abstract A: () => {};
B: string;
public C() {};
private D() {};
abstract E() {};
}
`,
errors: [
{
messageId: 'incorrectOrder',
data: {
name: 'B',
rank: 'public abstract field',
},
line: 4,
column: 5,
},
],
},
{
code: `
abstract class Foo {
B: string;
abstract C = () => {};
abstract A: () => {};
}
`,
options: [{ default: ['method', 'constructor', 'field'] }],
errors: [
{
messageId: 'incorrectOrder',
data: {
name: 'C',
rank: 'field',
},
line: 4,
column: 5,
},
],
},
{
code: `
class Foo {
C: number;
[A:string]: number;
public static D(): {};
private static [B:string]: number;
}
`,
options: [
{
default: [
'field',
'method',
'public-static-method',
'private-static-method',
],
},
],
errors: [
{
messageId: 'incorrectOrder',
data: {
name: 'D',
rank: 'private static method',
},
line: 5,
column: 5,
},
],
},
{
code: `
abstract class Foo {
abstract B: string;
abstract A(): void;
public C(): {};

}
`,
options: [{ default: ['method', 'constructor', 'field'] }],
errors: [
{
messageId: 'incorrectOrder',
data: {
name: 'A',
rank: 'field',
},
line: 4,
column: 5,
},
{
messageId: 'incorrectOrder',
data: {
name: 'C',
rank: 'field',
},
line: 5,
column: 5,
},
],
},
],
});