Skip to content
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

feat(eslint-plugin): [member-ordering] add decorators support #1870

Merged
merged 7 commits into from Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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