Skip to content

Commit

Permalink
Updated name to optionalityOrder
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaKGoldberg committed Nov 28, 2022
1 parent bb8444f commit 832aa08
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 36 deletions.
94 changes: 93 additions & 1 deletion packages/eslint-plugin/docs/rules/member-ordering.md
Expand Up @@ -24,6 +24,7 @@ type OrderConfig = MemberType[] | SortedOrderConfig | 'never';

interface SortedOrderConfig {
memberTypes?: MemberType[] | 'never';
optionalityOrder?: 'optional-first' | 'required-first';
order:
| 'alphabetically'
| 'alphabetically-case-insensitive'
Expand All @@ -44,9 +45,10 @@ You can configure `OrderConfig` options for:
- **`interfaces`**?: override ordering specifically for interfaces
- **`typeLiterals`**?: override ordering specifically for type literals

The `OrderConfig` settings for each kind of construct may configure sorting on one or both two levels:
The `OrderConfig` settings for each kind of construct may configure sorting on up to three levels:

- **`memberTypes`**: organizing on member type groups such as methods vs. properties
- **`optionalityOrder`**: whether to put all optional members first or all required members first
- **`order`**: organizing based on member names, such as alphabetically

### Groups
Expand Down Expand Up @@ -902,6 +904,96 @@ interface Foo {
}
```

#### Sorting Optional Members First or Last

The `optionalityOrder` option may be enabled to place all optional members in a group at the beginning or end of that group.

This config places all optional members before all required members:

```jsonc
// .eslintrc.json
{
"rules": {
"@typescript-eslint/member-ordering": [
"error",
{
"default": {
"optionalityOrder": "optional-first",
"order": "alphabetically"
}
}
]
}
}
```

<!--tabs-->

##### ❌ Incorrect

```ts
interface Foo {
a: boolean;
b?: number;
c: string;
}
```

##### ✅ Correct

```ts
interface Foo {
b?: number;
a: boolean;
c: string;
}
```

<!--/tabs-->

This config places all required members before all optional members:

```jsonc
// .eslintrc.json
{
"rules": {
"@typescript-eslint/member-ordering": [
"error",
{
"default": {
"optionalityOrder": "required-first",
"order": "alphabetically"
}
}
]
}
}
```

<!--tabs-->

##### ❌ Incorrect

```ts
interface Foo {
a: boolean;
b?: number;
c: string;
}
```

##### ✅ Correct

```ts
interface Foo {
a: boolean;
c: string;
b?: number;
}
```

<!--/tabs-->

## All Supported Options

### Member Types (Granular Form)
Expand Down
29 changes: 18 additions & 11 deletions packages/eslint-plugin/src/rules/member-ordering.ts
Expand Up @@ -48,13 +48,15 @@ type Order = AlphabeticalOrder | 'as-written';

interface SortedOrderConfig {
memberTypes?: MemberType[] | 'never';
optionalityOrder?: OptionalityOrder;
order: Order;
required?: 'first' | 'last';
}

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

type OptionalityOrder = 'optional-first' | 'required-first';

export type Options = [
{
default?: OrderConfig;
Expand Down Expand Up @@ -103,9 +105,9 @@ const objectConfig = (memberTypes: MemberType[]): JSONSchema.JSONSchema4 => ({
'natural-case-insensitive',
],
},
required: {
optionalityOrder: {
type: 'string',
enum: ['first', 'last'],
enum: ['optional-first', 'required-first'],
},
},
additionalProperties: false,
Expand Down Expand Up @@ -723,12 +725,13 @@ export default util.createRule<Options, MessageIds>({
* on the given 'required' parameter.
*
* @param members Members to be validated.
* @param optionalityOrder Where to place optional members, if not intermixed.
*
* @return True if all required and optional members are correctly sorted.
*/
function checkRequiredOrder(
members: Member[],
required: 'first' | 'last' | undefined,
optionalityOrder: OptionalityOrder | undefined,
): boolean {
const switchIndex = members.findIndex(
(member, i) =>
Expand All @@ -741,14 +744,18 @@ export default util.createRule<Options, MessageIds>({
loc: member.loc,
data: {
member: getMemberName(member, context.getSourceCode()),
optionalOrRequired: required === 'first' ? 'required' : 'optional',
optionalOrRequired:
optionalityOrder === 'optional-first' ? 'required' : 'optional',
},
});

// if the optionality of the first item is correct (based on required)
// if the optionality of the first item is correct (based on optionalityOrder)
// then the first 0 inclusive to switchIndex exclusive members all
// have the correct optionality
if (isMemberOptional(members[0]) !== (required === 'last')) {
if (
isMemberOptional(members[0]) !==
(optionalityOrder === 'required-first')
) {
report(members[0]);
return false;
}
Expand Down Expand Up @@ -785,7 +792,7 @@ export default util.createRule<Options, MessageIds>({
// Standardize config
let order: Order | undefined;
let memberTypes: string | MemberType[] | undefined;
let required: 'first' | 'last' | undefined;
let optionalityOrder: OptionalityOrder | undefined;

// returns true if everything is good and false if an error was reported
const checkOrder = (memberSet: Member[]): boolean => {
Expand Down Expand Up @@ -821,10 +828,10 @@ export default util.createRule<Options, MessageIds>({
} else {
order = orderConfig.order;
memberTypes = orderConfig.memberTypes;
required = orderConfig.required;
optionalityOrder = orderConfig.optionalityOrder;
}

if (!required) {
if (!optionalityOrder) {
checkOrder(members);
return;
}
Expand All @@ -835,7 +842,7 @@ export default util.createRule<Options, MessageIds>({
);

if (switchIndex !== -1) {
if (!checkRequiredOrder(members, required)) {
if (!checkRequiredOrder(members, optionalityOrder)) {
return;
}
checkOrder(members.slice(0, switchIndex));
Expand Down

0 comments on commit 832aa08

Please sign in to comment.