Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(eslint-plugin): added member group support to member-ordering ru…
…le (#4538)

* feat(eslint-plugin): added member group support to member-ordering rule

* test(eslint-plugin): added more test cases for member-ordering

* test(eslint-plugin): added more test cases for member-ordering

Co-authored-by: Josh Goldberg <me@joshuakgoldberg.com>
  • Loading branch information
grabofus and JoshuaKGoldberg committed Feb 24, 2022
1 parent 208b6d0 commit 6afcaea
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 13 deletions.
23 changes: 23 additions & 0 deletions packages/eslint-plugin/docs/rules/member-ordering.md
Expand Up @@ -292,6 +292,29 @@ The third grouping option is to ignore both scope and accessibility.
]
```

### Grouping different member types at the same rank

It is also possible to group different member types at the same rank.

```jsonc
[
// Index signature
"signature",

// Fields
"field",

// Constructors
"constructor",

// Getters and Setters at the same rank
["get", "set"],

// Methods
"method"
]
```

### Default configuration

The default configuration looks as follows:
Expand Down
49 changes: 36 additions & 13 deletions packages/eslint-plugin/src/rules/member-ordering.ts
Expand Up @@ -13,12 +13,14 @@ type Order =
| 'alphabetically-case-insensitive'
| 'as-written';

type MemberType = string | string[];

interface SortedOrderConfig {
memberTypes?: string[] | 'never';
memberTypes?: MemberType[] | 'never';
order: Order;
}

type OrderConfig = string[] | SortedOrderConfig | 'never';
type OrderConfig = MemberType[] | SortedOrderConfig | 'never';
type Member = TSESTree.ClassElement | TSESTree.TypeElement;

export type Options = [
Expand All @@ -36,14 +38,24 @@ const neverConfig: JSONSchema.JSONSchema4 = {
enum: ['never'],
};

const arrayConfig = (memberTypes: string[]): JSONSchema.JSONSchema4 => ({
const arrayConfig = (memberTypes: MemberType[]): JSONSchema.JSONSchema4 => ({
type: 'array',
items: {
enum: memberTypes,
oneOf: [
{
enum: memberTypes,
},
{
type: 'array',
items: {
enum: memberTypes,
},
},
],
},
});

const objectConfig = (memberTypes: string[]): JSONSchema.JSONSchema4 => ({
const objectConfig = (memberTypes: MemberType[]): JSONSchema.JSONSchema4 => ({
type: 'object',
properties: {
memberTypes: {
Expand Down Expand Up @@ -339,12 +351,20 @@ function getMemberName(
*
* @return Index of the matching member type in the order configuration.
*/
function getRankOrder(memberGroups: string[], orderConfig: string[]): number {
function getRankOrder(
memberGroups: string[],
orderConfig: MemberType[],
): number {
let rank = -1;
const stack = memberGroups.slice(); // Get a copy of the member groups

while (stack.length > 0 && rank === -1) {
rank = orderConfig.indexOf(stack.shift()!);
const memberGroup = stack.shift()!;
rank = orderConfig.findIndex(memberType =>
Array.isArray(memberType)
? memberType.includes(memberGroup)
: memberType === memberGroup,
);
}

return rank;
Expand All @@ -358,7 +378,7 @@ function getRankOrder(memberGroups: string[], orderConfig: string[]): number {
*/
function getRank(
node: Member,
orderConfig: string[],
orderConfig: MemberType[],
supportsModifiers: boolean,
): number {
const type = getNodeType(node);
Expand Down Expand Up @@ -414,7 +434,7 @@ function getRank(
}

/**
* Gets the lowest possible rank higher than target.
* Gets the lowest possible rank(s) higher than target.
* e.g. given the following order:
* ...
* public-static-method
Expand All @@ -427,15 +447,16 @@ function getRank(
* and considering that a public-instance-method has already been declared, so ranks contains
* public-instance-method, then the lowest possible rank for public-static-method is
* public-instance-method.
* If a lowest possible rank is a member group, a comma separated list of ranks is returned.
* @param ranks the existing ranks in the object.
* @param target the target rank.
* @param order the current order to be validated.
* @returns the name of the lowest possible rank without dashes (-).
* @returns the name(s) of the lowest possible rank without dashes (-).
*/
function getLowestRank(
ranks: number[],
target: number,
order: string[],
order: MemberType[],
): string {
let lowest = ranks[ranks.length - 1];

Expand All @@ -445,7 +466,9 @@ function getLowestRank(
}
});

return order[lowest].replace(/-/g, ' ');
const lowestRank = order[lowest];
const lowestRanks = Array.isArray(lowestRank) ? lowestRank : [lowestRank];
return lowestRanks.map(rank => rank.replace(/-/g, ' ')).join(', ');
}

export default util.createRule<Options, MessageIds>({
Expand Down Expand Up @@ -523,7 +546,7 @@ export default util.createRule<Options, MessageIds>({
*/
function checkGroupSort(
members: Member[],
groupOrder: string[],
groupOrder: MemberType[],
supportsModifiers: boolean,
): Array<Member[]> | null {
const previousRanks: number[] = [];
Expand Down
156 changes: 156 additions & 0 deletions packages/eslint-plugin/tests/rules/member-ordering.test.ts
Expand Up @@ -1417,6 +1417,74 @@ class Foo {
},
],
},
{
code: `
class Foo {
A: string;
constructor() {}
get B() {}
set B() {}
get C() {}
set C() {}
D(): void;
} `,
options: [
{
default: ['field', 'constructor', ['get', 'set'], 'method'],
},
],
},
{
code: `
class Foo {
A: string;
constructor() {}
B(): void;
} `,
options: [
{
default: ['field', 'constructor', [], 'method'],
},
],
},
{
code: `
class Foo {
A: string;
constructor() {}
@Dec() private B: string;
private C(): void;
set D() {}
E(): void;
} `,
options: [
{
default: [
'public-field',
'constructor',
['private-decorated-field', 'public-set', 'private-method'],
'public-method',
],
},
],
},
{
code: `
class Foo {
A: string;
constructor() {}
get B() {}
get C() {}
set B() {}
set C() {}
D(): void;
} `,
options: [
{
default: ['field', 'constructor', ['get'], ['set'], 'method'],
},
],
},
],
invalid: [
{
Expand Down Expand Up @@ -3823,6 +3891,94 @@ class Foo {
},
],
},
{
code: `
class Foo {
A: string;
get B() {}
constructor() {}
set B() {}
get C() {}
set C() {}
D(): void;
} `,
options: [
{
default: ['field', 'constructor', ['get', 'set'], 'method'],
},
],
errors: [
{
messageId: 'incorrectGroupOrder',
data: {
name: 'constructor',
rank: 'get, set',
},
line: 5,
column: 5,
},
],
},
{
code: `
class Foo {
A: string;
private C(): void;
constructor() {}
@Dec() private B: string;
set D() {}
E(): void;
} `,
options: [
{
default: [
'public-field',
'constructor',
['private-decorated-field', 'public-set', 'private-method'],
'public-method',
],
},
],
errors: [
{
messageId: 'incorrectGroupOrder',
data: {
name: 'constructor',
rank: 'private decorated field, public set, private method',
},
line: 5,
column: 5,
},
],
},
{
code: `
class Foo {
A: string;
constructor() {}
get B() {}
set B() {}
get C() {}
set C() {}
D(): void;
} `,
options: [
{
default: ['field', 'constructor', 'get', ['set'], 'method'],
},
],
errors: [
{
messageId: 'incorrectGroupOrder',
data: {
name: 'C',
rank: 'set',
},
line: 7,
column: 5,
},
],
},
],
};

Expand Down

0 comments on commit 6afcaea

Please sign in to comment.