Skip to content

Commit

Permalink
feat(eslint-plugin): [member-ordering] add decorators support (#1870)
Browse files Browse the repository at this point in the history
  • Loading branch information
miluoshi committed Apr 27, 2020
1 parent 1b4e430 commit f7ec192
Show file tree
Hide file tree
Showing 3 changed files with 414 additions and 66 deletions.
80 changes: 63 additions & 17 deletions packages/eslint-plugin/docs/rules/member-ordering.md
Expand Up @@ -62,6 +62,9 @@ There are multiple ways to specify the member types. The most explicit and granu
"public-static-field",
"protected-static-field",
"private-static-field",
"public-decorated-field",
"protected-decorated-field",
"private-decorated-field",
"public-instance-field",
"protected-instance-field",
"private-instance-field",
Expand All @@ -78,6 +81,9 @@ There are multiple ways to specify the member types. The most explicit and granu
"public-static-method",
"protected-static-method",
"private-static-method",
"public-decorated-method",
"protected-decorated-method",
"private-decorated-method",
"public-instance-method",
"protected-instance-method",
"private-instance-method",
Expand All @@ -99,17 +105,45 @@ It is also possible to group member types by their accessibility (`static`, `ins
// No accessibility for index signature. See above.

// 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"])
"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"])
"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 (with accessibility and a decorator)

It is also possible to group methods or fields with a decorator separately, optionally specifying
their accessibility.

```jsonc
[
// Index signature
// No decorators for index signature.

// Fields
"public-decorated-field",
"protected-decorated-field",
"private-decorated-field",

"decorated-field", // = ["public-decorated-field", "protected-decorated-field", "private-decorated-field"]

// Constructors
// There are no decorators for constructors.

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

"decorated-method" // = ["public-decorated-method", "protected-decorated-method", "private-decorated-method"]
]
```

Expand All @@ -123,17 +157,17 @@ Another option is to group the member types by their scope (`public`, `protected
// No scope for index signature. See above.

// 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"])
"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"])
"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"])
"abstract-method" // = ["public-abstract-method", "protected-abstract-method", "private-abstract-method"])
"static-method", // = ["public-static-method", "protected-static-method", "private-static-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 @@ -148,14 +182,14 @@ The third grouping option is to ignore both scope and accessibility.

// Fields
"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"])
// "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",
// "public-abstract-method", "protected-abstract-method", "private-abstract-method"])
// "public-abstract-method", "protected-abstract-method", "private-abstract-method"]
]
```

Expand All @@ -174,6 +208,10 @@ The default configuration looks as follows:
"protected-static-field",
"private-static-field",

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

"public-instance-field",
"protected-instance-field",
"private-instance-field",
Expand All @@ -190,6 +228,8 @@ The default configuration looks as follows:
"instance-field",
"abstract-field",

"decorated-field",

"field",

// Constructors
Expand All @@ -204,6 +244,10 @@ The default configuration looks as follows:
"protected-static-method",
"private-static-method",

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

"public-instance-method",
"protected-instance-method",
"private-instance-method",
Expand All @@ -220,6 +264,8 @@ The default configuration looks as follows:
"instance-method",
"abstract-method",

"decorated-method",

"method"
]
}
Expand Down Expand Up @@ -749,7 +795,7 @@ type Foo = {

It is possible to sort all members within a group alphabetically.

#### Configuration: `{ default: { memberTypes: <Default Order>, order: "alphabetically" } }`
#### Configuration: `{ "default": { "memberTypes": <Default Order>, "order": "alphabetically" } }`

This will apply the default order (see above) and enforce an alphabetic order within each group.

Expand Down Expand Up @@ -795,7 +841,7 @@ interface Foo {

It is also possible to sort all members and ignore the member groups completely.

#### Configuration: `{ default: { memberTypes: "never", order: "alphabetically" } }`
#### Configuration: `{ "default": { "memberTypes": "never", "order": "alphabetically" } }`

##### Incorrect example

Expand Down
120 changes: 75 additions & 45 deletions packages/eslint-plugin/src/rules/member-ordering.ts
Expand Up @@ -60,6 +60,10 @@ export const defaultOrder = [
'protected-static-field',
'private-static-field',

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

'public-instance-field',
'protected-instance-field',
'private-instance-field',
Expand All @@ -76,6 +80,8 @@ export const defaultOrder = [
'instance-field',
'abstract-field',

'decorated-field',

'field',

// Constructors
Expand All @@ -90,6 +96,10 @@ export const defaultOrder = [
'protected-static-method',
'private-static-method',

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

'public-instance-method',
'protected-instance-method',
'private-instance-method',
Expand All @@ -106,6 +116,8 @@ export const defaultOrder = [
'instance-method',
'abstract-method',

'decorated-method',

'method',
];

Expand All @@ -119,6 +131,18 @@ const allMemberTypes = ['signature', 'field', 'method', 'constructor'].reduce<
all.push(`${accessibility}-${type}`); // e.g. `public-field`
}

// Only class instance fields and methods can have decorators attached to them
if (type === 'field' || type === 'method') {
const decoratedMemberType = `${accessibility}-decorated-${type}`;
const decoratedMemberTypeNoAccessibility = `decorated-${type}`;
if (!all.includes(decoratedMemberType)) {
all.push(decoratedMemberType);
}
if (!all.includes(decoratedMemberTypeNoAccessibility)) {
all.push(decoratedMemberTypeNoAccessibility);
}
}

if (type !== 'constructor' && type !== 'signature') {
// There is no `static-constructor` or `instance-constructor` or `abstract-constructor`
['static', 'instance', 'abstract'].forEach(scope => {
Expand Down Expand Up @@ -258,6 +282,12 @@ function getRank(
const memberGroups = [];

if (supportsModifiers) {
const decorated = 'decorators' in node && node.decorators!.length > 0;
if (decorated && (type === 'field' || type === 'method')) {
memberGroups.push(`${accessibility}-decorated-${type}`);
memberGroups.push(`decorated-${type}`);
}

if (type !== 'constructor') {
// Constructors have no scope
memberGroups.push(`${accessibility}-${scope}-${type}`);
Expand Down Expand Up @@ -396,27 +426,29 @@ export default util.createRule<Options, MessageIds>({
const name = getMemberName(member, context.getSourceCode());
const rankLastMember = previousRanks[previousRanks.length - 1];

if (rank !== -1) {
// Works for 1st item because x < undefined === false for any x (typeof string)
if (rank < rankLastMember) {
context.report({
node: member,
messageId: 'incorrectGroupOrder',
data: {
name,
rank: getLowestRank(previousRanks, rank, groupOrder),
},
});
if (rank === -1) {
return;
}

isCorrectlySorted = false;
} else if (rank === rankLastMember) {
// Same member group --> Push to existing member group array
memberGroups[memberGroups.length - 1].push(member);
} else {
// New member group --> Create new member group array
previousRanks.push(rank);
memberGroups.push([member]);
}
// Works for 1st item because x < undefined === false for any x (typeof string)
if (rank < rankLastMember) {
context.report({
node: member,
messageId: 'incorrectGroupOrder',
data: {
name,
rank: getLowestRank(previousRanks, rank, groupOrder),
},
});

isCorrectlySorted = false;
} else if (rank === rankLastMember) {
// Same member group --> Push to existing member group array
memberGroups[memberGroups.length - 1].push(member);
} else {
// New member group --> Create new member group array
previousRanks.push(rank);
memberGroups.push([member]);
}
});

Expand Down Expand Up @@ -472,36 +504,34 @@ export default util.createRule<Options, MessageIds>({
orderConfig: OrderConfig,
supportsModifiers: boolean,
): void {
if (orderConfig !== 'never') {
// Standardize config
let order = null;
let memberTypes;
if (orderConfig === 'never') {
return;
}

if (Array.isArray(orderConfig)) {
memberTypes = orderConfig;
} else {
order = orderConfig.order;
memberTypes = orderConfig.memberTypes;
}
// Standardize config
let order = null;
let memberTypes;

// Check order
if (Array.isArray(memberTypes)) {
const grouped = checkGroupSort(
members,
memberTypes,
supportsModifiers,
);
if (Array.isArray(orderConfig)) {
memberTypes = orderConfig;
} else {
order = orderConfig.order;
memberTypes = orderConfig.memberTypes;
}

if (grouped === null) {
return;
}
// Check order
if (Array.isArray(memberTypes)) {
const grouped = checkGroupSort(members, memberTypes, supportsModifiers);

if (order === 'alphabetically') {
grouped.some(groupMember => !checkAlphaSort(groupMember));
}
} else if (order === 'alphabetically') {
checkAlphaSort(members);
if (grouped === null) {
return;
}

if (order === 'alphabetically') {
grouped.some(groupMember => !checkAlphaSort(groupMember));
}
} else if (order === 'alphabetically') {
checkAlphaSort(members);
}
}

Expand Down

0 comments on commit f7ec192

Please sign in to comment.