diff --git a/docs/content/rules/sort-classes.mdx b/docs/content/rules/sort-classes.mdx index ef597353d..cc030edfc 100644 --- a/docs/content/rules/sort-classes.mdx +++ b/docs/content/rules/sort-classes.mdx @@ -321,7 +321,7 @@ Predefined groups are characterized by a single selector and potentially multipl #### Methods - Selectors: `get-method`, `set-method`, `method`. -- Modifiers: `static`, `abstract`, `decorated`, `override`, `protected`, `private`, `public`, `optional`. +- Modifiers: `static`, `abstract`, `decorated`, `override`, `protected`, `private`, `public`, `optional`, `async`. - Example: `private-static-accessor-property`, `protected-abstract-override-method` or `static-get-method`. The `optional` modifier is incompatible with the `get-method` and `set-method` selectors. @@ -339,7 +339,7 @@ The `abstract` modifier is incompatible with the `static`, `private` and `decora #### Properties - Selectors: `function-property`, `property`. -- Modifiers: `static`, `declare`, `abstract`, `decorated`, `override`, `readonly`, `protected`, `private`, `public`, `optional`. +- Modifiers: `static`, `declare`, `abstract`, `decorated`, `override`, `readonly`, `protected`, `private`, `public`, `optional`, `async`. - Example: `readonly-decorated-property`. The `abstract` modifier is incompatible with the `static`, `private` and `decorated` modifiers. @@ -349,6 +349,8 @@ The `declare` modifier is incompatible with the `override` and `decorated` modif The `function-property` selector will match properties whose values are defined functions or arrow-functions. As such, the `declare` and `abstract` modifiers are incompatible with this selector. +The `async` modifier is reserved for the `function-property` selector. + #### Index-signatures - Selector: `index-signature`. - Modifiers: `static`, `readonly`. diff --git a/rules/sort-classes.ts b/rules/sort-classes.ts index cb7057828..4bc085c98 100644 --- a/rules/sort-classes.ts +++ b/rules/sort-classes.ts @@ -448,6 +448,10 @@ export default createEslintRule({ modifiers.push('optional') } + if (member.value.async) { + modifiers.push('async') + } + if (member.kind === 'constructor') { selectors.push('constructor') } @@ -544,26 +548,20 @@ export default createEslintRule({ modifiers.push('optional') } - let isFunctionProperty = + if ( member.value?.type === 'ArrowFunctionExpression' || member.value?.type === 'FunctionExpression' - if (isFunctionProperty) { + ) { + if (member.value.async) { + modifiers.push('async') + } selectors.push('function-property') - } - - if (!isFunctionProperty && member.value) { + } else if (member.value) { memberValue = sourceCode.getText(member.value) + dependencies = extractDependencies(member.value, member.static) } selectors.push('property') - - if ( - member.type === 'PropertyDefinition' && - member.value && - !isFunctionProperty - ) { - dependencies = extractDependencies(member.value, member.static) - } } for (let officialGroup of generatePredefinedGroups({ diff --git a/rules/sort-classes.types.ts b/rules/sort-classes.types.ts index 7a3eb7c2f..0c2bccc14 100644 --- a/rules/sort-classes.types.ts +++ b/rules/sort-classes.types.ts @@ -7,6 +7,7 @@ type PublicOrProtectedOrPrivateModifier = | ProtectedModifier | PrivateModifier | PublicModifier +type AsyncModifier = 'async' type StaticModifier = 'static' type AbstractModifier = 'abstract' type OverrideModifier = 'override' @@ -23,6 +24,7 @@ export type Modifier = | ReadonlyModifier | DeclareModifier | StaticModifier + | AsyncModifier type ConstructorSelector = 'constructor' type FunctionPropertySelector = 'function-property' @@ -50,6 +52,7 @@ type PublicOrProtectedOrPrivateModifierPrefix = WithDashSuffixOrEmpty< ProtectedModifier | PrivateModifier | PublicModifier > +type AsyncModifierPrefix = WithDashSuffixOrEmpty type OverrideModifierPrefix = WithDashSuffixOrEmpty type OptionalModifierPrefix = WithDashSuffixOrEmpty type ReadonlyModifierPrefix = WithDashSuffixOrEmpty @@ -67,13 +70,13 @@ type GetMethodOrSetMethodSelector = GetMethodSelector | SetMethodSelector type ConstructorGroup = `${PublicOrProtectedOrPrivateModifierPrefix}${ConstructorSelector}` type FunctionPropertyGroup = - `${PublicOrProtectedOrPrivateModifierPrefix}${StaticModifierPrefix}${OverrideModifierPrefix}${ReadonlyModifierPrefix}${DecoratedModifierPrefix}${FunctionPropertySelector}` + `${PublicOrProtectedOrPrivateModifierPrefix}${StaticModifierPrefix}${OverrideModifierPrefix}${ReadonlyModifierPrefix}${DecoratedModifierPrefix}${AsyncModifierPrefix}${FunctionPropertySelector}` type DeclarePropertyGroup = `${DeclareModifierPrefix}${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${ReadonlyModifierPrefix}${OptionalModifierPrefix}${PropertySelector}` type NonDeclarePropertyGroup = `${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${OverrideModifierPrefix}${ReadonlyModifierPrefix}${DecoratedModifierPrefix}${OptionalModifierPrefix}${PropertySelector}` type MethodGroup = - `${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${OverrideModifierPrefix}${DecoratedModifierPrefix}${OptionalModifierPrefix}${MethodSelector}` + `${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${OverrideModifierPrefix}${DecoratedModifierPrefix}${AsyncModifierPrefix}${OptionalModifierPrefix}${MethodSelector}` type GetMethodOrSetMethodGroup = `${PublicOrProtectedOrPrivateModifierPrefix}${StaticOrAbstractModifierPrefix}${OverrideModifierPrefix}${DecoratedModifierPrefix}${GetMethodOrSetMethodSelector}` type AccessorPropertyGroup = @@ -121,17 +124,19 @@ interface AllowedModifiersPerSelector { | OverrideModifier | OptionalModifier | StaticModifier - 'accessor-property': + | AsyncModifier + 'function-property': | PublicOrProtectedOrPrivateModifier | DecoratedModifier - | AbstractModifier | OverrideModifier + | ReadonlyModifier | StaticModifier - 'function-property': + | AsyncModifier + 'accessor-property': | PublicOrProtectedOrPrivateModifier | DecoratedModifier + | AbstractModifier | OverrideModifier - | ReadonlyModifier | StaticModifier 'set-method': | PublicOrProtectedOrPrivateModifier @@ -213,6 +218,7 @@ export let allSelectors: Selector[] = [ ] export let allModifiers: Modifier[] = [ + 'async', 'protected', 'private', 'public', diff --git a/test/sort-classes.test.ts b/test/sort-classes.test.ts index 91d26d6dd..2a1f28ef4 100644 --- a/test/sort-classes.test.ts +++ b/test/sort-classes.test.ts @@ -189,7 +189,7 @@ describe(ruleName, () => { code: dedent` abstract class Class { - p?(): void; + async p?(): Promise; o?; @@ -197,9 +197,9 @@ describe(ruleName, () => { static readonly [key: string]: string; - private n = function() {}; + private n = async function() {}; - private m = () => {}; + private m = async () => {}; declare private static readonly l; @@ -257,9 +257,9 @@ describe(ruleName, () => { declare private static readonly l; - private m = () => {}; + private m = async () => {}; - private n = function() {}; + private n = async function() {}; static readonly [key: string]: string; @@ -267,7 +267,7 @@ describe(ruleName, () => { o?; - p?(): void; + async p?(): Promise; } `, options: [ @@ -287,11 +287,11 @@ describe(ruleName, () => { 'protected-property', 'private-property', 'declare-private-static-readonly-property', - 'function-property', + 'async-function-property', 'static-readonly-index-signature', 'static-block', 'public-optional-property', - 'public-optional-method', + 'public-optional-async-method', ], }, ], @@ -300,7 +300,7 @@ describe(ruleName, () => { messageId: 'unexpectedClassesGroupOrder', data: { left: 'p', - leftGroup: 'public-optional-method', + leftGroup: 'public-optional-async-method', right: 'o', rightGroup: 'public-optional-property', }, @@ -329,7 +329,7 @@ describe(ruleName, () => { left: 'static readonly [key: string]', leftGroup: 'static-readonly-index-signature', right: 'n', - rightGroup: 'function-property', + rightGroup: 'async-function-property', }, }, { @@ -343,7 +343,7 @@ describe(ruleName, () => { messageId: 'unexpectedClassesGroupOrder', data: { left: 'm', - leftGroup: 'function-property', + leftGroup: 'async-function-property', right: 'l', rightGroup: 'declare-private-static-readonly-property', }, @@ -926,6 +926,51 @@ describe(ruleName, () => { }, ) } + + ruleTester.run( + `${ruleName}(${type}): prioritize optional over async`, + rule, + { + valid: [], + invalid: [ + { + code: dedent` + export class Class { + + a: string; + + async z?(): Promise; + } + `, + output: dedent` + export class Class { + + async z?(): Promise; + + a: string; + } + `, + options: [ + { + ...options, + groups: [`optional-method`, 'property', 'async-method'], + }, + ], + errors: [ + { + messageId: 'unexpectedClassesGroupOrder', + data: { + left: 'a', + leftGroup: 'property', + right: 'z', + rightGroup: `optional-method`, + }, + }, + ], + }, + ], + }, + ) }) describe(`${ruleName}(${type}): accessor modifiers priority`, () => { @@ -1596,6 +1641,51 @@ describe(ruleName, () => { }, ) } + + ruleTester.run( + `${ruleName}(${type}): prioritize optional over async`, + rule, + { + valid: [], + invalid: [ + { + code: dedent` + export class Class { + + a(): void {} + + z?: Promise = async () => {}; + } + `, + output: dedent` + export class Class { + + z?: Promise = async () => {}; + + a(): void {} + } + `, + options: [ + { + ...options, + groups: ['optional-property', 'method', `async-property`], + }, + ], + errors: [ + { + messageId: 'unexpectedClassesGroupOrder', + data: { + left: 'a', + leftGroup: 'method', + right: 'z', + rightGroup: `optional-property`, + }, + }, + ], + }, + ], + }, + ) }) ruleTester.run(