diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/fixture.ts b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/fixture.ts new file mode 100644 index 000000000000..bd45e0a8a625 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/fixture.ts @@ -0,0 +1,3 @@ +abstract class Foo { + abstract constructor() { } +} diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/snapshots/1-TSESTree-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/snapshots/1-TSESTree-Error.shot new file mode 100644 index 000000000000..8a67121b79c1 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/snapshots/1-TSESTree-Error.shot @@ -0,0 +1,9 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-constructor > TSESTree - Error`] +TSError + 1 | abstract class Foo { +> 2 | abstract constructor() { } + | ^^^^^^^^ 'abstract' modifier can only appear on a class, method, or property declaration. + 3 | } + 4 | diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/snapshots/2-Babel-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/snapshots/2-Babel-Error.shot new file mode 100644 index 000000000000..b78e289f0c55 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/snapshots/2-Babel-Error.shot @@ -0,0 +1,10 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-constructor > Babel - Error`] +BabelError + 1 | abstract class Foo { +> 2 | abstract constructor() { } + | ^ Method 'constructor' cannot have an implementation because it is marked abstract. (2:2) + 3 | } + 4 | + diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/snapshots/3-Alignment-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/snapshots/3-Alignment-Error.shot new file mode 100644 index 000000000000..dbd705849820 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-constructor/snapshots/3-Alignment-Error.shot @@ -0,0 +1,4 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-constructor > Error Alignment`] +Both errored diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/fixture.ts b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/fixture.ts new file mode 100644 index 000000000000..0ac2bfca1dfd --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/fixture.ts @@ -0,0 +1,3 @@ +abstract class Foo { + abstract get getter() { } +} diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/snapshots/1-TSESTree-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/snapshots/1-TSESTree-Error.shot new file mode 100644 index 000000000000..d7c0c1bcc98a --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/snapshots/1-TSESTree-Error.shot @@ -0,0 +1,9 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-getter-with-implementation > TSESTree - Error`] +TSError + 1 | abstract class Foo { +> 2 | abstract get getter() { } + | ^^^^^^ An abstract accessor cannot have an implementation. + 3 | } + 4 | diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/snapshots/2-Babel-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/snapshots/2-Babel-Error.shot new file mode 100644 index 000000000000..fe0fe6660cf0 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/snapshots/2-Babel-Error.shot @@ -0,0 +1,10 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-getter-with-implementation > Babel - Error`] +BabelError + 1 | abstract class Foo { +> 2 | abstract get getter() { } + | ^ Method 'getter' cannot have an implementation because it is marked abstract. (2:2) + 3 | } + 4 | + diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/snapshots/3-Alignment-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/snapshots/3-Alignment-Error.shot new file mode 100644 index 000000000000..748024e02a80 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-getter-with-implementation/snapshots/3-Alignment-Error.shot @@ -0,0 +1,4 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-getter-with-implementation > Error Alignment`] +Both errored diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/fixture.ts b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/fixture.ts new file mode 100644 index 000000000000..80fe821c5d29 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/fixture.ts @@ -0,0 +1,3 @@ +abstract class Foo { + abstract method() { } +} diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/snapshots/1-TSESTree-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/snapshots/1-TSESTree-Error.shot new file mode 100644 index 000000000000..a65c609416d4 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/snapshots/1-TSESTree-Error.shot @@ -0,0 +1,9 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-method-with-implementation > TSESTree - Error`] +TSError + 1 | abstract class Foo { +> 2 | abstract method() { } + | ^^^^^^ Method 'method' cannot have an implementation because it is marked abstract. + 3 | } + 4 | diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/snapshots/2-Babel-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/snapshots/2-Babel-Error.shot new file mode 100644 index 000000000000..c345ce0399ec --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/snapshots/2-Babel-Error.shot @@ -0,0 +1,10 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-method-with-implementation > Babel - Error`] +BabelError + 1 | abstract class Foo { +> 2 | abstract method() { } + | ^ Method 'method' cannot have an implementation because it is marked abstract. (2:2) + 3 | } + 4 | + diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/snapshots/3-Alignment-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/snapshots/3-Alignment-Error.shot new file mode 100644 index 000000000000..fff6cbc7c6ce --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-method-with-implementation/snapshots/3-Alignment-Error.shot @@ -0,0 +1,4 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-method-with-implementation > Error Alignment`] +Both errored diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/fixture.ts b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/fixture.ts new file mode 100644 index 000000000000..fa7888c34cc9 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/fixture.ts @@ -0,0 +1,3 @@ +abstract class Foo { + abstract set setter(v) { } +} diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/snapshots/1-TSESTree-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/snapshots/1-TSESTree-Error.shot new file mode 100644 index 000000000000..febb4fe5dcde --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/snapshots/1-TSESTree-Error.shot @@ -0,0 +1,9 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-setter-with-implementation > TSESTree - Error`] +TSError + 1 | abstract class Foo { +> 2 | abstract set setter(v) { } + | ^^^^^^ An abstract accessor cannot have an implementation. + 3 | } + 4 | diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/snapshots/2-Babel-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/snapshots/2-Babel-Error.shot new file mode 100644 index 000000000000..c45ee034b7c3 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/snapshots/2-Babel-Error.shot @@ -0,0 +1,10 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-setter-with-implementation > Babel - Error`] +BabelError + 1 | abstract class Foo { +> 2 | abstract set setter(v) { } + | ^ Method 'setter' cannot have an implementation because it is marked abstract. (2:2) + 3 | } + 4 | + diff --git a/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/snapshots/3-Alignment-Error.shot b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/snapshots/3-Alignment-Error.shot new file mode 100644 index 000000000000..1e222f7febc0 --- /dev/null +++ b/packages/ast-spec/src/declaration/ClassDeclaration/fixtures/_error_/abstract-setter-with-implementation/snapshots/3-Alignment-Error.shot @@ -0,0 +1,4 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AST Fixtures > declaration > ClassDeclaration > _error_ > abstract-setter-with-implementation > Error Alignment`] +Both errored diff --git a/packages/eslint-plugin/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index d53fe04d6d22..f885b156163c 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -4465,7 +4465,7 @@ abstract class Foo { B: string; public C() {} private D() {} - abstract E() {} + E() {} } `, errors: [ @@ -4478,6 +4478,15 @@ abstract class Foo { line: 4, messageId: 'incorrectGroupOrder', }, + { + column: 3, + data: { + name: 'E', + rank: 'private instance method', + }, + line: 7, + messageId: 'incorrectGroupOrder', + }, ], }, { diff --git a/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts b/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts index c43b293c686e..076534645409 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts @@ -632,7 +632,7 @@ ruleTester.run('naming-convention', rule, { { code: ` class Ignored { - private static abstract some_name() {} + private static some_name() {} IgnoredDueToModifiers() {} } `, @@ -644,7 +644,7 @@ ruleTester.run('naming-convention', rule, { }, { format: ['UPPER_CASE'], - modifiers: ['abstract', 'static'], + modifiers: ['static'], selector: 'classMethod', }, ], @@ -1875,7 +1875,7 @@ ruleTester.run('naming-convention', rule, { { code: ` class Ignored { - private static abstract some_name() {} + private static some_name() {} IgnoredDueToModifiers() {} } `, @@ -1886,7 +1886,7 @@ ruleTester.run('naming-convention', rule, { }, { format: ['snake_case'], - modifiers: ['abstract', 'static'], + modifiers: ['static'], selector: 'classMethod', }, ], @@ -2118,10 +2118,10 @@ ruleTester.run('naming-convention', rule, { } } abstract class foo { - public abstract Bar() { + public Bar() { return 42; } - public abstract async async_bar() { + public async async_bar() { return 42; } } diff --git a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts index 2b6ddbdb9870..587445eba9b9 100644 --- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts +++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts @@ -156,11 +156,6 @@ function foo(): Promise | boolean { code: ` abstract class Test { abstract test1(): Promise; - - // abstract method with body is always an error but it still parses into valid AST - abstract test2(): Promise { - return Promise.resolve(1); - } } `, }, diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index bec26fede610..8f03943b36fc 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -14,6 +14,7 @@ import { getDecorators, getModifiers } from './getModifiers'; import { canContainDirective, createError, + declarationNameToString, findNextToken, getBinaryExpressionType, getContainingFunction, @@ -1599,6 +1600,18 @@ export class Converter { } // otherwise, it is a non-type accessor - intentional fallthrough case SyntaxKind.MethodDeclaration: { + const isAbstract = hasModifier(SyntaxKind.AbstractKeyword, node); + + if (isAbstract && node.body) { + this.#throwError( + node.name, + node.kind === SyntaxKind.GetAccessor || + node.kind === SyntaxKind.SetAccessor + ? 'An abstract accessor cannot have an implementation.' + : `Method '${declarationNameToString(node.name, this.ast)}' cannot have an implementation because it is marked abstract.`, + ); + } + const method = this.createNode< TSESTree.FunctionExpression | TSESTree.TSEmptyBodyFunctionExpression >(node, { @@ -1654,10 +1667,7 @@ export class Converter { /** * TypeScript class methods can be defined as "abstract" */ - const methodDefinitionType = hasModifier( - SyntaxKind.AbstractKeyword, - node, - ) + const methodDefinitionType = isAbstract ? AST_NODE_TYPES.TSAbstractMethodDefinition : AST_NODE_TYPES.MethodDefinition; diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index 640b493d63bb..11c7c3c33db2 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -919,3 +919,12 @@ export function getNamespaceModifiers( } return modifiers; } + +// `ts.declarationNameToString` +export function declarationNameToString( + name: ts.Node, + ast: ts.SourceFile, +): string { + const text = ast.text.slice(name.pos, name.end).trimStart(); + return text || '(Missing)'; +}