Skip to content

Commit

Permalink
feat(eslint-plugin): sort members alphabetically
Browse files Browse the repository at this point in the history
  • Loading branch information
timkraut committed Mar 13, 2019
1 parent 18484a2 commit bd124fb
Show file tree
Hide file tree
Showing 3 changed files with 379 additions and 74 deletions.
174 changes: 100 additions & 74 deletions packages/eslint-plugin/docs/rules/member-ordering.md
Expand Up @@ -5,83 +5,109 @@ expressions easier to read, navigate and edit.

## Rule Details

This rule aims to standardise the way interfaces, type literals, classes and class expressions are structured.
This rule aims to standardise the way interfaces, type literals, classes and class expressions are structured and ordered.

It allows to group members by their type (e.g. `public-static-field`, `protected-static-field`, `private-static-field`, `public-instance-field`, ...). By default, their order is the same inside `classes`, `classExpressions`, `interfaces` and `typeLiterals`. It is possible to define the order for any of those individually or to change the default order for all of them by setting the `default` option.

## Options

This rule, in its default state, does not require any argument, in which case the following order is enforced:

- `public-static-field`
- `protected-static-field`
- `private-static-field`
- `public-instance-field`
- `protected-instance-field`
- `private-instance-field`
- `public-field` (ignores scope)
- `protected-field` (ignores scope)
- `private-field` (ignores scope)
- `static-field` (ignores accessibility)
- `instance-field` (ignores accessibility)
- `field` (ignores scope and/or accessibility)
- `constructor` (ignores scope and/or accessibility)
- `public-static-method`
- `protected-static-method`
- `private-static-method`
- `public-instance-method`
- `protected-instance-method`
- `private-instance-method`
- `public-method` (ignores scope)
- `protected-method` (ignores scope)
- `private-method` (ignores scope)
- `static-method` (ignores accessibility)
- `instance-method` (ignores accessibility)
- `method` (ignores scope and/or accessibility)

The rule can also take one or more of the following options:

- `default`, use this to change the default order (used when no specific configuration has been provided).
- `classes`, use this to change the order in classes.
- `classExpressions`, use this to change the order in class expressions.
- `interfaces`, use this to change the order in interfaces.
- `typeLiterals`, use this to change the order in type literals.

### default

Disable using `never` or use one of the following values to specify an order:

- Fields:
`public-static-field`
`protected-static-field`
`private-static-field`
`public-instance-field`
`protected-instance-field`
`private-instance-field`
`public-field` (= public-_-field)
`protected-field` (= protected-_-field)
`private-field` (= private-_-field)
`static-field` (= _-static-field)
`instance-field` (= \*-instance-field)
`field` (= all)

- Constructors:
`public-constructor`
`protected-constructor`
`private-constructor`
`constructor` (= \*-constructor)

- Methods:
`public-static-method`
`protected-static-method`
`private-static-method`
`public-instance-method`
`protected-instance-method`
`private-instance-method`
`public-method` (= public-_-method)
`protected-method` (= protected-_-method)
`private-method` (= private-_-method)
`static-method` (= _-static-method)
`instance-method` (= \*-instance-method)
`method` (= all)
The configuration options look as follows:

```js
{
default?: Array<MemberType> | never
classes?: Array<MemberType> | never
classExpressions?: Array<MemberType> | never
interfaces?: Array<MemberType> | never
typeLiterals?: Array<MemberType> | never
}
```

See below for the possible definitions of `memberTypes`.

### Member types (granular form)

There are multiple ways to specify the member types. The most explicit and granular form is the following:

```
// Fields
- 'public-static-field'
- 'protected-static-field'
- 'private-static-field'
- 'public-instance-field'
- 'protected-instance-field'
- 'private-instance-field'
// Constructors
- 'public-constructor'
- 'protected-constructor'
- 'private-constructor'
// Methods
- 'public-static-method'
- 'protected-static-method'
- 'private-static-method'
- 'public-instance-method'
- 'protected-instance-method'
- 'private-instance-method'
```

Note: If nothing else is specified, this is the default order used when this rule is enabled.

### Member group types (ignoring scope)

It is also possible to group member types as follows, ignoring their scope (`static`, `instance`).

```
// Fields
- 'public-field' // = ['public-static-field', 'public-instance-field'])
- 'protected-field' // = ['protected-static-field', 'protected-instance-field'])
- 'private-field' // = ['private-static-field', 'private-instance-field'])
// Constructors
// Only the accessibility of constructors is configurable. See below.
// Methods
- 'public-method' // = ['public-static-method', 'public-instance-method'])
- 'protected-method' // = ['protected-static-method', 'protected-instance-method'])
- 'private-method' // = ['private-static-method', 'private-instance-method'])
```

### Member group types (ignoring accessibility)

Another option is to group the member types as follows, ignoring their accessibility (`public`, `protected`, `private`).

```
// Fields
- 'static-field' // = ['public-static-field', 'protected-static-field', 'private-static-field'])
- 'instance-field' // = ['public-instance-field', 'protected-instance-field', 'private-instance-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'])
```

### Member group types (ignoring scope and accessibility)

The third grouping option is to ignore both scope (`static`, `instance`) and accessibility (`public`, `protected`, `private`).

```
// Fields
- 'field' // = ['public-static-field', 'protected-static-field', 'private-static-field', 'public-instance-field', 'protected-instance-field', 'private-instance-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'])
```

## Examples

### Default configuration

Examples of **incorrect** code for the `{ "default": [...] }` option:

Expand Down
199 changes: 199 additions & 0 deletions packages/eslint-plugin/src/rules/sort-interface-members.ts
@@ -0,0 +1,199 @@
/**
* @fileoverview Forbids unsorted interface members
*/

import * as util from '../util';
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree';

type Options = [];
type MessageIds = 'notSorted';

function isPropertySignature(
member: TSESTree.TypeElement,
): member is TSESTree.TSPropertySignature {
return member.type === AST_NODE_TYPES.TSPropertySignature;
}

function isMethodSignature(
member: TSESTree.TypeElement,
): member is TSESTree.TSMethodSignature {
return member.type === AST_NODE_TYPES.TSMethodSignature;
}

function isIndexSignature(
member: TSESTree.TypeElement,
): member is TSESTree.TSIndexSignature {
return member.type === AST_NODE_TYPES.TSIndexSignature;
}

function isConstructSignatureDeclaration(
member: TSESTree.TypeElement,
): member is TSESTree.TSConstructSignatureDeclaration {
return member.type === AST_NODE_TYPES.TSConstructSignatureDeclaration;
}

function isCallSignatureDeclaration(
member: TSESTree.TypeElement,
): member is TSESTree.TSCallSignatureDeclaration {
return member.type === AST_NODE_TYPES.TSCallSignatureDeclaration;
}

export default util.createRule<Options, MessageIds>({
name: 'sort-interface-members',
meta: {
type: 'suggestion',
docs: {
description: 'Forbids unsorted interface members',
category: 'Stylistic Issues',
recommended: false,
},
schema: [],
messages: {
notSorted: 'The interface members are not sorted alphabetically.',
},
},
defaultOptions: [],
create(context) {
return {
TSInterfaceBody(node) {
const members = node.body;
const propertySignatures: TSESTree.TSPropertySignature[] = [];
const methodSignatures: TSESTree.TSMethodSignature[] = [];
const indexSignatures: TSESTree.TSIndexSignature[] = [];
const constructSignatureDeclarations: TSESTree.TSConstructSignatureDeclaration[] = [];
const callSignatureDeclarations: TSESTree.TSCallSignatureDeclaration[] = [];

// TODO This algorithm assumes an order of TSESTree.TSPropertySignature > TSMethodSignature > TSIndexSignature > TSConstructSignatureDeclaration > TSCallSignatureDeclaration - it is only used to evaluate if it works alongside the member-ordering rule
for (let i = 0; i < members.length; i++) {
if (isPropertySignature(members[i])) {
if (
methodSignatures.length > 0 ||
indexSignatures.length > 0 ||
constructSignatureDeclarations.length > 0 ||
callSignatureDeclarations.length > 0
) {
return context.report({
messageId: 'notSorted',
node: members[i],
});
}

propertySignatures.push(members[i] as TSESTree.TSPropertySignature);
} else if (isMethodSignature(members[i])) {
if (
indexSignatures.length > 0 ||
constructSignatureDeclarations.length > 0 ||
callSignatureDeclarations.length > 0
) {
return context.report({
messageId: 'notSorted',
node: members[i],
});
}

methodSignatures.push(members[i] as TSESTree.TSMethodSignature);
} else if (isIndexSignature(members[i])) {
if (
constructSignatureDeclarations.length > 0 ||
callSignatureDeclarations.length > 0
) {
return context.report({
messageId: 'notSorted',
node: members[i],
});
}

indexSignatures.push(members[i] as TSESTree.TSIndexSignature);
} else if (isConstructSignatureDeclaration(members[i])) {
if (callSignatureDeclarations.length > 0) {
return context.report({
messageId: 'notSorted',
node: members[i],
});
}

constructSignatureDeclarations.push(members[
i
] as TSESTree.TSConstructSignatureDeclaration);
} else if (isCallSignatureDeclaration(members[i])) {
callSignatureDeclarations.push(members[
i
] as TSESTree.TSCallSignatureDeclaration);
}
}

for (let i = 0; i < propertySignatures.length - 1; i++) {
const currentItem = propertySignatures[i].key as TSESTree.Identifier;
const nextItem = propertySignatures[i + 1].key as TSESTree.Identifier;

if (currentItem.name > nextItem.name) {
return context.report({
messageId: 'notSorted',
node: currentItem,
});
}
}

for (let i = 0; i < methodSignatures.length - 1; i++) {
const currentItem = methodSignatures[i].key as TSESTree.Identifier;
const nextItem = methodSignatures[i + 1].key as TSESTree.Identifier;

if (currentItem.name > nextItem.name) {
return context.report({
messageId: 'notSorted',
node: currentItem,
});
}
}

for (let i = 0; i < indexSignatures.length - 1; i++) {
const currentItem = indexSignatures[i]
.parameters[0] as TSESTree.Identifier;
const nextItem = indexSignatures[i + 1]
.parameters[0] as TSESTree.Identifier;

if (currentItem.name > nextItem.name) {
return context.report({
messageId: 'notSorted',
node: currentItem,
});
}
}

for (let i = 0; i < constructSignatureDeclarations.length - 1; i++) {
const currentItem = ((constructSignatureDeclarations[i]
.returnType as TSESTree.TSTypeAnnotation)
.typeAnnotation as TSESTree.TSTypeReference).typeName;
const nextItem = ((constructSignatureDeclarations[i + 1]
.returnType as TSESTree.TSTypeAnnotation)
.typeAnnotation as TSESTree.TSTypeReference).typeName;

if (currentItem > nextItem) {
return context.report({
messageId: 'notSorted',
node: currentItem,
});
}
}

for (let i = 0; i < callSignatureDeclarations.length - 1; i++) {
const currentItem = ((callSignatureDeclarations[i]
.returnType as TSESTree.TSTypeAnnotation)
.typeAnnotation as TSESTree.TSTypeReference).typeName;
const nextItem = ((callSignatureDeclarations[i + 1]
.returnType as TSESTree.TSTypeAnnotation)
.typeAnnotation as TSESTree.TSTypeReference).typeName;

if (currentItem > nextItem) {
return context.report({
messageId: 'notSorted',
node: currentItem,
});
}
}

return; // No rule violation found
},
};
},
});

0 comments on commit bd124fb

Please sign in to comment.