diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index 808493494c378..538235e10a5e3 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -1269,6 +1269,23 @@ impl<'a> ClassElement<'a> { Self::StaticBlock(_) | Self::TSIndexSignature(_) => false, } } + + /// Has this property been marked as abstract? + /// + /// ```ts + /// abstract class Foo { // <-- not considered + /// foo: string; // <-- false + /// abstract bar: string; // <-- true + /// } + /// ``` + pub fn is_abstract(&self) -> bool { + match self { + Self::MethodDefinition(method) => method.r#type.is_abstract(), + Self::AccessorProperty(accessor) => accessor.r#type.is_abstract(), + Self::PropertyDefinition(property) => property.r#type.is_abstract(), + Self::StaticBlock(_) | Self::TSIndexSignature(_) => false, + } + } } impl PropertyDefinitionType { diff --git a/crates/oxc_semantic/src/checker/mod.rs b/crates/oxc_semantic/src/checker/mod.rs index 98130d6ca53e6..809ead5daac87 100644 --- a/crates/oxc_semantic/src/checker/mod.rs +++ b/crates/oxc_semantic/src/checker/mod.rs @@ -70,6 +70,7 @@ pub fn check<'a>(node: &AstNode<'a>, ctx: &SemanticBuilder<'a>) { } AstKind::Class(class) => { js::check_class(class, node, ctx); + ts::check_class(class, ctx); } AstKind::MethodDefinition(method) => { js::check_method_definition(method, ctx); diff --git a/crates/oxc_semantic/src/checker/typescript.rs b/crates/oxc_semantic/src/checker/typescript.rs index 807280799b099..a1806a1025c33 100644 --- a/crates/oxc_semantic/src/checker/typescript.rs +++ b/crates/oxc_semantic/src/checker/typescript.rs @@ -185,6 +185,27 @@ pub fn check_ts_import_equals_declaration<'a>( } } +/// - Abstract properties can only appear within an abstract class. (1253) +/// - Abstract methods can only appear within an abstract class. (1244) +fn abstract_elem_in_concrete_class(is_property: bool, span: Span) -> OxcDiagnostic { + let (code, elem_kind) = if is_property { (1253, "properties") } else { (1244, "methods") }; + OxcDiagnostic::error(format!( + "TS({code}): Abstract {elem_kind} can only appear within an abstract class." + )) + .with_label(span) +} + +pub fn check_class<'a>(class: &Class<'a>, ctx: &SemanticBuilder<'a>) { + if !class.r#abstract { + for elem in &class.body.body { + if elem.is_abstract() { + let span = elem.property_key().map_or_else(|| elem.span(), GetSpan::span); + ctx.error(abstract_elem_in_concrete_class(elem.is_property(), span)); + } + } + } +} + fn abstract_element_cannot_have_initializer( code: u32, elem_name: &str, diff --git a/tasks/coverage/parser_babel.snap b/tasks/coverage/parser_babel.snap index 66b5308072c20..d5c9ea06ef336 100644 --- a/tasks/coverage/parser_babel.snap +++ b/tasks/coverage/parser_babel.snap @@ -3,7 +3,7 @@ commit: 12619ffe parser_babel Summary: AST Parsed : 2093/2101 (99.62%) Positive Passed: 2083/2101 (99.14%) -Negative Passed: 1377/1501 (91.74%) +Negative Passed: 1380/1501 (91.94%) Expect Syntax Error: "annex-b/disabled/1.1-html-comments-close/input.js" Expect Syntax Error: "annex-b/disabled/3.1-sloppy-labeled-functions/input.js" Expect Syntax Error: "annex-b/disabled/3.1-sloppy-labeled-functions-if-body/input.js" @@ -42,9 +42,6 @@ Expect Syntax Error: "esprima/invalid-syntax/migrated_0276/input.js" Expect Syntax Error: "typescript/cast/satisfies-const-error/input.ts" Expect Syntax Error: "typescript/cast/unparenthesized-assert-and-assign/input.ts" Expect Syntax Error: "typescript/cast/unparenthesized-type-assertion-and-assign/input.ts" -Expect Syntax Error: "typescript/class/abstract-method-in-non-abstract-class-1/input.ts" -Expect Syntax Error: "typescript/class/abstract-method-in-non-abstract-class-2/input.ts" -Expect Syntax Error: "typescript/class/abstract-method-in-non-abstract-class-3/input.ts" Expect Syntax Error: "typescript/class/constructor-with-invalid-order-modifiers-1/input.ts" Expect Syntax Error: "typescript/class/constructor-with-invalid-order-modifiers-2/input.ts" Expect Syntax Error: "typescript/class/constructor-with-invalid-order-modifiers-3/input.ts" @@ -9971,6 +9968,28 @@ Expect to Parse: "typescript/types/const-type-parameters-babel-7/input.ts" 2 │ func(a: T); ╰──── + × TS(1244): Abstract methods can only appear within an abstract class. + ╭─[typescript/class/abstract-method-in-non-abstract-class-1/input.ts:2:12] + 1 │ class Foo { + 2 │ abstract method(); + · ────── + 3 │ } + ╰──── + + × TS(1244): Abstract methods can only appear within an abstract class. + ╭─[typescript/class/abstract-method-in-non-abstract-class-2/input.ts:4:16] + 3 │ return class { + 4 │ abstract m(); + · ─ + 5 │ } + ╰──── + + × TS(1244): Abstract methods can only appear within an abstract class. + ╭─[typescript/class/abstract-method-in-non-abstract-class-3/input.ts:1:41] + 1 │ abstract class C { p = class { abstract method() } } + · ────── + ╰──── + × TS(1245): Method 'method' cannot have an implementation because it is marked abstract. ╭─[typescript/class/abstract-method-with-body/input.ts:2:12] 1 │ abstract class Foo { @@ -10115,6 +10134,14 @@ Expect to Parse: "typescript/types/const-type-parameters-babel-7/input.ts" × TS(1245): Method 'd' cannot have an implementation because it is marked abstract. ╭─[typescript/class/generator-method-with-modifiers/input.ts:5:13] 4 │ static *c() {} + 5 │ abstract *d() {} + · ─ + 6 │ readonly *e() {} + ╰──── + + × TS(1244): Abstract methods can only appear within an abstract class. + ╭─[typescript/class/generator-method-with-modifiers/input.ts:5:13] + 4 │ static *c() {} 5 │ abstract *d() {} · ─ 6 │ readonly *e() {} diff --git a/tasks/coverage/parser_typescript.snap b/tasks/coverage/parser_typescript.snap index a4fe5cd551996..a354da75413ca 100644 --- a/tasks/coverage/parser_typescript.snap +++ b/tasks/coverage/parser_typescript.snap @@ -3,7 +3,7 @@ commit: d8086f14 parser_typescript Summary: AST Parsed : 5279/5283 (99.92%) Positive Passed: 5272/5283 (99.79%) -Negative Passed: 1090/4875 (22.36%) +Negative Passed: 1094/4875 (22.44%) Expect Syntax Error: "compiler/ClassDeclaration10.ts" Expect Syntax Error: "compiler/ClassDeclaration11.ts" Expect Syntax Error: "compiler/ClassDeclaration13.ts" @@ -27,7 +27,6 @@ Expect Syntax Error: "compiler/ParameterList8.ts" Expect Syntax Error: "compiler/abstractClassInLocalScopeIsAbstract.ts" Expect Syntax Error: "compiler/abstractClassUnionInstantiation.ts" Expect Syntax Error: "compiler/abstractPropertyInConstructor.ts" -Expect Syntax Error: "compiler/abstractPropertyNegative.ts" Expect Syntax Error: "compiler/accessInstanceMemberFromStaticMethod01.ts" Expect Syntax Error: "compiler/accessStaticMemberFromInstanceMethod01.ts" Expect Syntax Error: "compiler/accessorAccidentalCallDiagnostic.ts" @@ -100,7 +99,6 @@ Expect Syntax Error: "compiler/arrayOfSubtypeIsAssignableToReadonlyArray.ts" Expect Syntax Error: "compiler/arrayReferenceWithoutTypeArgs.ts" Expect Syntax Error: "compiler/arrayToLocaleStringES5.ts" Expect Syntax Error: "compiler/arrowFunctionInConstructorArgument1.ts" -Expect Syntax Error: "compiler/asiAbstract.ts" Expect Syntax Error: "compiler/asiPublicPrivateProtected.ts" Expect Syntax Error: "compiler/assignLambdaToNominalSubtypeOfFunction.ts" Expect Syntax Error: "compiler/assignToEnum.ts" @@ -1964,7 +1962,6 @@ Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInheritance1.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInheritance2.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations1.ts" -Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractOverloads.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractOverrideWithAbstract.ts" @@ -1972,7 +1969,6 @@ Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractSingleLineDecl.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractSuperCalls.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractUsingAbstractMethod1.ts" -Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractUsingAbstractMethods2.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classAndInterfaceMergeConflictingMembers.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classExtendingClassLikeType.ts" Expect Syntax Error: "conformance/classes/classDeclarations/classExtendingNonConstructor.ts" @@ -4019,6 +4015,14 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts" ╰──── help: Try insert a semicolon here + × TS(1253): Abstract properties can only appear within an abstract class. + ╭─[compiler/abstractPropertyNegative.ts:15:14] + 14 │ readonly ro = "readonly please"; + 15 │ abstract notAllowed: string; + · ────────── + 16 │ get concreteWithNoBody(): string; + ╰──── + × Unexpected token ╭─[compiler/accessorBodyInTypeContext.ts:2:15] 1 │ type A = { @@ -4181,6 +4185,14 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts" 3 │ ╰──── + × TS(1244): Abstract methods can only appear within an abstract class. + ╭─[compiler/asiAbstract.ts:3:12] + 2 │ class NonAbstractClass { + 3 │ abstract s(); + · ─ + 4 │ } + ╰──── + × TS1108: A 'return' statement can only be used within a function body ╭─[compiler/asiReturn.ts:2:1] 1 │ // This should be an error for using a return outside a function, but ASI should work properly @@ -11131,6 +11143,14 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts" 9 │ } ╰──── + × TS(1244): Abstract methods can only appear within an abstract class. + ╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts:50:14] + 49 │ class H { // error -- not declared abstract + 50 │ abstract baz() : number; + · ─── + 51 │ } + ╰──── + × Unexpected token ╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractManyKeywords.ts:3:1] 2 │ export abstract class B {} @@ -11163,6 +11183,14 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts" 18 │ ╰──── + × TS(1244): Abstract methods can only appear within an abstract class. + ╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodInNonAbstractClass.ts:2:14] + 1 │ class A { + 2 │ abstract foo(); + · ─── + 3 │ } + ╰──── + × TS(1245): Method 'foo' cannot have an implementation because it is marked abstract. ╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodInNonAbstractClass.ts:6:14] 5 │ class B { @@ -11174,6 +11202,14 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts" × TS(1245): Method 'foo' cannot have an implementation because it is marked abstract. ╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodInNonAbstractClass.ts:6:14] 5 │ class B { + 6 │ abstract foo() {} + · ─── + 7 │ } + ╰──── + + × TS(1244): Abstract methods can only appear within an abstract class. + ╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodInNonAbstractClass.ts:6:14] + 5 │ class B { 6 │ abstract foo() {} · ─── 7 │ } @@ -11195,6 +11231,14 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts" 3 │ } ╰──── + × TS(1244): Abstract methods can only appear within an abstract class. + ╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractUsingAbstractMethods2.ts:2:14] + 1 │ class A { + 2 │ abstract foo(); + · ─── + 3 │ } + ╰──── + × 'abstract' modifier cannot be used here. ╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractWithInterface.ts:1:1] 1 │ abstract interface I {}