From 3a08af1450fdcefedca850e718deaafffee0b9f9 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Fri, 4 Sep 2015 17:45:57 -0700 Subject: [PATCH] Check for class expressions as well as class declarations --- src/compiler/checker.ts | 18 +++++++++--- .../diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 7 ++++- ...xpressionExtendingAbstractClass.errors.txt | 14 ++++++++++ .../classExpressionExtendingAbstractClass.js | 28 +++++++++++++++++++ .../classExpressionExtendingAbstractClass.ts | 7 +++++ 6 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/classExpressionExtendingAbstractClass.errors.txt create mode 100644 tests/baselines/reference/classExpressionExtendingAbstractClass.js create mode 100644 tests/cases/compiler/classExpressionExtendingAbstractClass.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a7e9561c57f..45ea200a091 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12625,6 +12625,10 @@ namespace ts { return s.flags & SymbolFlags.Instantiated ? getSymbolLinks(s).target : s; } + function getClassLikeDeclarationOfSymbol(symbol: Symbol): Declaration { + return forEach(symbol.declarations, d => isClassLike(d) ? d : undefined); + } + function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: ObjectType): void { // TypeScript 1.0 spec (April 2014): 8.2.3 @@ -12662,14 +12666,20 @@ namespace ts { if (derived === base) { // derived class inherits base without override/redeclaration - let derivedClassDecl = getDeclarationOfKind(type.symbol, SyntaxKind.ClassDeclaration); + let derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol); // It is an error to inherit an abstract member without implementing it or being declared abstract. // If there is no declaration for the derived class (as in the case of class expressions), // then the class cannot be declared abstract. - if ( baseDeclarationFlags & NodeFlags.Abstract && (!derivedClassDecl || !(derivedClassDecl.flags & NodeFlags.Abstract))) { - error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, - typeToString(type), symbolToString(baseProperty), typeToString(baseType)); + if (baseDeclarationFlags & NodeFlags.Abstract && (!derivedClassDecl || !(derivedClassDecl.flags & NodeFlags.Abstract))) { + if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { + error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, + symbolToString(baseProperty), typeToString(baseType)); + } + else { + error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, + typeToString(type), symbolToString(baseProperty), typeToString(baseType)); + } } } else { diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index f7351bf6912..e1b1bf3bc9d 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -427,6 +427,7 @@ namespace ts { Cannot_emit_namespaced_JSX_elements_in_React: { code: 2650, category: DiagnosticCategory.Error, key: "Cannot emit namespaced JSX elements in React" }, A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums: { code: 2651, category: DiagnosticCategory.Error, key: "A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums." }, Merged_declaration_0_cannot_include_a_default_export_declaration_Consider_adding_a_separate_export_default_0_declaration_instead: { code: 2652, category: DiagnosticCategory.Error, key: "Merged declaration '{0}' cannot include a default export declaration. Consider adding a separate 'export default {0}' declaration instead." }, + Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1: { code: 2653, category: DiagnosticCategory.Error, key: "Non-abstract class expression does not implement inherited abstract member '{0}' from class '{1}'." }, Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." }, Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: { code: 4002, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported class has or is using private name '{1}'." }, Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1: { code: 4004, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported interface has or is using private name '{1}'." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9fd08ef9bca..f41e24461c4 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1692,11 +1692,16 @@ "A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.": { "category": "Error", "code": 2651 - }, + }, "Merged declaration '{0}' cannot include a default export declaration. Consider adding a separate 'export default {0}' declaration instead.": { "category": "Error", "code": 2652 }, + "Non-abstract class expression does not implement inherited abstract member '{0}' from class '{1}'.": { + "category": "Error", + "code": 2653 + }, + "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", "code": 4000 diff --git a/tests/baselines/reference/classExpressionExtendingAbstractClass.errors.txt b/tests/baselines/reference/classExpressionExtendingAbstractClass.errors.txt new file mode 100644 index 00000000000..b5d709fbf60 --- /dev/null +++ b/tests/baselines/reference/classExpressionExtendingAbstractClass.errors.txt @@ -0,0 +1,14 @@ +tests/cases/compiler/classExpressionExtendingAbstractClass.ts(5,9): error TS2653: Non-abstract class expression does not implement inherited abstract member 'foo' from class 'A'. + + +==== tests/cases/compiler/classExpressionExtendingAbstractClass.ts (1 errors) ==== + abstract class A { + abstract foo(): void; + } + + var C = class extends A { // no error reported! + ~~~~~ +!!! error TS2653: Non-abstract class expression does not implement inherited abstract member 'foo' from class 'A'. + }; + + \ No newline at end of file diff --git a/tests/baselines/reference/classExpressionExtendingAbstractClass.js b/tests/baselines/reference/classExpressionExtendingAbstractClass.js new file mode 100644 index 00000000000..a5149538da2 --- /dev/null +++ b/tests/baselines/reference/classExpressionExtendingAbstractClass.js @@ -0,0 +1,28 @@ +//// [classExpressionExtendingAbstractClass.ts] +abstract class A { + abstract foo(): void; +} + +var C = class extends A { // no error reported! +}; + + + +//// [classExpressionExtendingAbstractClass.js] +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var A = (function () { + function A() { + } + return A; +})(); +var C = (function (_super) { + __extends(class_1, _super); + function class_1() { + _super.apply(this, arguments); + } + return class_1; +})(A); diff --git a/tests/cases/compiler/classExpressionExtendingAbstractClass.ts b/tests/cases/compiler/classExpressionExtendingAbstractClass.ts new file mode 100644 index 00000000000..aadec5be7a4 --- /dev/null +++ b/tests/cases/compiler/classExpressionExtendingAbstractClass.ts @@ -0,0 +1,7 @@ +abstract class A { + abstract foo(): void; +} + +var C = class extends A { // no error reported! +}; +