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 3 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
58 changes: 52 additions & 6 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.

```json5
[
// 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 Down Expand Up @@ -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
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