Skip to content

Commit c4ee9f8

Browse files
authored
feat(semantic): check for abstract initializations and implementations (#4125)
1 parent 365d9ba commit c4ee9f8

File tree

5 files changed

+264
-13
lines changed

5 files changed

+264
-13
lines changed

crates/oxc_ast/src/ast_impl/js.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,12 @@ impl<'a> ClassElement<'a> {
12711271
}
12721272
}
12731273

1274+
impl PropertyDefinitionType {
1275+
pub fn is_abstract(&self) -> bool {
1276+
matches!(self, Self::TSAbstractPropertyDefinition)
1277+
}
1278+
}
1279+
12741280
impl MethodDefinitionKind {
12751281
pub fn is_constructor(&self) -> bool {
12761282
matches!(self, Self::Constructor)

crates/oxc_semantic/src/checker/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ pub fn check<'a>(node: &AstNode<'a>, ctx: &SemanticBuilder<'a>) {
7171
AstKind::Class(class) => {
7272
js::check_class(class, node, ctx);
7373
}
74-
AstKind::MethodDefinition(method) => js::check_method_definition(method, ctx),
74+
AstKind::MethodDefinition(method) => {
75+
js::check_method_definition(method, ctx);
76+
ts::check_method_definition(method, ctx);
77+
}
78+
AstKind::PropertyDefinition(prop) => ts::check_property_definition(prop, ctx),
7579
AstKind::ObjectProperty(prop) => js::check_object_property(prop, ctx),
7680
AstKind::Super(sup) => js::check_super(sup, node, ctx),
7781

crates/oxc_semantic/src/checker/typescript.rs

+83-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use oxc_ast::syntax_directed_operations::BoundNames;
1+
use oxc_ast::syntax_directed_operations::{BoundNames, PropName};
22
#[allow(clippy::wildcard_imports)]
33
use oxc_ast::{ast::*, AstKind};
44
use oxc_diagnostics::OxcDiagnostic;
@@ -184,3 +184,85 @@ pub fn check_ts_import_equals_declaration<'a>(
184184
ctx.error(import_alias_cannot_use_import_type(decl.span));
185185
}
186186
}
187+
188+
fn abstract_element_cannot_have_initializer(
189+
code: u32,
190+
elem_name: &str,
191+
prop_name: &str,
192+
span: Span,
193+
init_or_impl: &str,
194+
) -> OxcDiagnostic {
195+
OxcDiagnostic::error(
196+
format!("TS({code}): {elem_name} '{prop_name}' cannot have an {init_or_impl} because it is marked abstract."),
197+
)
198+
.with_label(span)
199+
}
200+
201+
/// TS(1245): Method 'foo' cannot have an implementation because it is marked abstract.
202+
fn abstract_method_cannot_have_implementation(method_name: &str, span: Span) -> OxcDiagnostic {
203+
abstract_element_cannot_have_initializer(1245, "Method", method_name, span, "implementation")
204+
}
205+
206+
/// TS(1267): Property 'foo' cannot have an initializer because it is marked abstract.
207+
fn abstract_property_cannot_have_initializer(prop_name: &str, span: Span) -> OxcDiagnostic {
208+
abstract_element_cannot_have_initializer(1267, "Property", prop_name, span, "initializer")
209+
}
210+
211+
/// TS(1318): Accessor 'foo' cannot have an implementation because it is marked abstract.
212+
///
213+
/// Applies to getters/setters
214+
///
215+
/// > TS's original message, `An abstract accessor cannot have an
216+
/// > implementation.`, is less helpful than the one provided here.
217+
fn abstract_accessor_cannot_have_implementation(accessor_name: &str, span: Span) -> OxcDiagnostic {
218+
abstract_element_cannot_have_initializer(
219+
1318,
220+
"Accessor",
221+
accessor_name,
222+
span,
223+
"implementation",
224+
)
225+
}
226+
227+
/// 'abstract' modifier can only appear on a class, method, or property declaration. (1242)
228+
fn illegal_abstract_modifier(span: Span) -> OxcDiagnostic {
229+
OxcDiagnostic::error("TS(1242): 'abstract' modifier can only appear on a class, method, or property declaration.")
230+
.with_label(span)
231+
}
232+
233+
pub fn check_method_definition<'a>(method: &MethodDefinition<'a>, ctx: &SemanticBuilder<'a>) {
234+
if method.r#type.is_abstract() {
235+
// constructors cannot be abstract, no matter what
236+
if method.kind.is_constructor() {
237+
ctx.error(illegal_abstract_modifier(method.key.span()));
238+
} else if method.value.body.is_some() {
239+
// abstract class elements cannot have bodies or initializers
240+
let (method_name, span) = method.key.prop_name().unwrap_or_else(|| {
241+
let key_span = method.key.span();
242+
(&ctx.source_text[key_span], key_span)
243+
});
244+
match method.kind {
245+
MethodDefinitionKind::Method => {
246+
ctx.error(abstract_method_cannot_have_implementation(method_name, span));
247+
}
248+
MethodDefinitionKind::Get | MethodDefinitionKind::Set => {
249+
ctx.error(abstract_accessor_cannot_have_implementation(method_name, span));
250+
}
251+
// abstract classes can have concrete methods. Constructors cannot
252+
// have abstract modifiers, but this gets checked during parsing
253+
MethodDefinitionKind::Constructor => {}
254+
}
255+
ctx.error(abstract_method_cannot_have_implementation(method_name, span));
256+
}
257+
}
258+
}
259+
260+
pub fn check_property_definition<'a>(prop: &PropertyDefinition<'a>, ctx: &SemanticBuilder<'a>) {
261+
if prop.r#type.is_abstract() && prop.value.is_some() {
262+
let (prop_name, span) = prop.key.prop_name().unwrap_or_else(|| {
263+
let key_span = prop.key.span();
264+
(&ctx.source_text[key_span], key_span)
265+
});
266+
ctx.error(abstract_property_cannot_have_initializer(prop_name, span));
267+
}
268+
}

tasks/coverage/parser_babel.snap

+89-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ commit: 12619ffe
33
parser_babel Summary:
44
AST Parsed : 2093/2101 (99.62%)
55
Positive Passed: 2083/2101 (99.14%)
6-
Negative Passed: 1373/1501 (91.47%)
6+
Negative Passed: 1377/1501 (91.74%)
77
Expect Syntax Error: "annex-b/disabled/1.1-html-comments-close/input.js"
88
Expect Syntax Error: "annex-b/disabled/3.1-sloppy-labeled-functions/input.js"
99
Expect Syntax Error: "annex-b/disabled/3.1-sloppy-labeled-functions-if-body/input.js"
@@ -45,9 +45,6 @@ Expect Syntax Error: "typescript/cast/unparenthesized-type-assertion-and-assign/
4545
Expect Syntax Error: "typescript/class/abstract-method-in-non-abstract-class-1/input.ts"
4646
Expect Syntax Error: "typescript/class/abstract-method-in-non-abstract-class-2/input.ts"
4747
Expect Syntax Error: "typescript/class/abstract-method-in-non-abstract-class-3/input.ts"
48-
Expect Syntax Error: "typescript/class/abstract-method-with-body/input.ts"
49-
Expect Syntax Error: "typescript/class/abstract-method-with-body-computed/input.ts"
50-
Expect Syntax Error: "typescript/class/abstract-property-initializer/input.ts"
5148
Expect Syntax Error: "typescript/class/constructor-with-invalid-order-modifiers-1/input.ts"
5249
Expect Syntax Error: "typescript/class/constructor-with-invalid-order-modifiers-2/input.ts"
5350
Expect Syntax Error: "typescript/class/constructor-with-invalid-order-modifiers-3/input.ts"
@@ -58,7 +55,6 @@ Expect Syntax Error: "typescript/class/declare-field-initializer/input.ts"
5855
Expect Syntax Error: "typescript/class/declare-initializer/input.ts"
5956
Expect Syntax Error: "typescript/class/declare-method/input.ts"
6057
Expect Syntax Error: "typescript/class/declare-readonly-field-initializer-w-annotation/input.ts"
61-
Expect Syntax Error: "typescript/class/generator-method-with-modifiers/input.ts"
6258
Expect Syntax Error: "typescript/class/index-signature-errors/input.ts"
6359
Expect Syntax Error: "typescript/class/invalid-modifiers-order/input.ts"
6460
Expect Syntax Error: "typescript/class/method-readonly/input.ts"
@@ -9975,6 +9971,78 @@ Expect to Parse: "typescript/types/const-type-parameters-babel-7/input.ts"
99759971
2 │ func<T>(a: T);
99769972
╰────
99779973

9974+
× TS(1245): Method 'method' cannot have an implementation because it is marked abstract.
9975+
╭─[typescript/class/abstract-method-with-body/input.ts:2:12]
9976+
1 │ abstract class Foo {
9977+
2 │ abstract method() {}
9978+
· ──────
9979+
3 │ }
9980+
╰────
9981+
9982+
× TS(1245): Method 'method' cannot have an implementation because it is marked abstract.
9983+
╭─[typescript/class/abstract-method-with-body/input.ts:2:12]
9984+
1 │ abstract class Foo {
9985+
2 │ abstract method() {}
9986+
· ──────
9987+
3 │ }
9988+
╰────
9989+
9990+
× TS(1245): Method 'foo()' cannot have an implementation because it is marked abstract.
9991+
╭─[typescript/class/abstract-method-with-body-computed/input.ts:2:13]
9992+
1 │ abstract class Foo {
9993+
2 │ abstract [foo()]() {}
9994+
· ─────
9995+
3 │ }
9996+
╰────
9997+
9998+
× TS(1245): Method 'foo()' cannot have an implementation because it is marked abstract.
9999+
╭─[typescript/class/abstract-method-with-body-computed/input.ts:2:13]
10000+
1 │ abstract class Foo {
10001+
2 │ abstract [foo()]() {}
10002+
· ─────
10003+
3 │ }
10004+
╰────
10005+
10006+
× TS(1267): Property 'prop' cannot have an initializer because it is marked abstract.
10007+
╭─[typescript/class/abstract-property-initializer/input.ts:2:12]
10008+
1 │ abstract class Foo {
10009+
2 │ abstract prop = 1
10010+
· ────
10011+
3 │ abstract [Bar.foo] = 2
10012+
╰────
10013+
10014+
× TS(1267): Property 'Bar.foo' cannot have an initializer because it is marked abstract.
10015+
╭─[typescript/class/abstract-property-initializer/input.ts:3:13]
10016+
2 │ abstract prop = 1
10017+
3 │ abstract [Bar.foo] = 2
10018+
· ───────
10019+
4 │ abstract [Bar] = 3
10020+
╰────
10021+
10022+
× TS(1267): Property 'Bar' cannot have an initializer because it is marked abstract.
10023+
╭─[typescript/class/abstract-property-initializer/input.ts:4:13]
10024+
3 │ abstract [Bar.foo] = 2
10025+
4 │ abstract [Bar] = 3
10026+
· ───
10027+
5 │ abstract 2 = 4
10028+
╰────
10029+
10030+
× TS(1267): Property '2' cannot have an initializer because it is marked abstract.
10031+
╭─[typescript/class/abstract-property-initializer/input.ts:5:12]
10032+
4 │ abstract [Bar] = 3
10033+
5 │ abstract 2 = 4
10034+
· ─
10035+
6 │ abstract "c" = 5
10036+
╰────
10037+
10038+
× TS(1267): Property 'c' cannot have an initializer because it is marked abstract.
10039+
╭─[typescript/class/abstract-property-initializer/input.ts:6:12]
10040+
5 │ abstract 2 = 4
10041+
6 │ abstract "c" = 5
10042+
· ───
10043+
7 │ }
10044+
╰────
10045+
997810046
× An accessibility modifier cannot be used with a private identifier.
997910047
╭─[typescript/class/accessor-invalid/input.ts:3:3]
998010048
2 │ declare accessor prop7: number;
@@ -10036,6 +10104,22 @@ Expect to Parse: "typescript/types/const-type-parameters-babel-7/input.ts"
1003610104
2 │ }
1003710105
╰────
1003810106

10107+
× TS(1245): Method 'd' cannot have an implementation because it is marked abstract.
10108+
╭─[typescript/class/generator-method-with-modifiers/input.ts:5:13]
10109+
4 │ static *c() {}
10110+
5 │ abstract *d() {}
10111+
· ─
10112+
6 │ readonly *e() {}
10113+
╰────
10114+
10115+
× TS(1245): Method 'd' cannot have an implementation because it is marked abstract.
10116+
╭─[typescript/class/generator-method-with-modifiers/input.ts:5:13]
10117+
4 │ static *c() {}
10118+
5 │ abstract *d() {}
10119+
· ─
10120+
6 │ readonly *e() {}
10121+
╰────
10122+
1003910123
× Unexpected token
1004010124
╭─[typescript/class/implements-empty/input.ts:1:22]
1004110125
1 │ class Foo implements {

tasks/coverage/parser_typescript.snap

+81-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ commit: d8086f14
33
parser_typescript Summary:
44
AST Parsed : 5279/5283 (99.92%)
55
Positive Passed: 5272/5283 (99.79%)
6-
Negative Passed: 1085/4875 (22.26%)
6+
Negative Passed: 1090/4875 (22.36%)
77
Expect Syntax Error: "compiler/ClassDeclaration10.ts"
88
Expect Syntax Error: "compiler/ClassDeclaration11.ts"
99
Expect Syntax Error: "compiler/ClassDeclaration13.ts"
@@ -1952,10 +1952,8 @@ Expect Syntax Error: "conformance/async/es6/functionDeclarations/asyncFunctionDe
19521952
Expect Syntax Error: "conformance/async/es6/functionDeclarations/asyncFunctionDeclaration8_es6.ts"
19531953
Expect Syntax Error: "conformance/asyncGenerators/asyncGeneratorParameterEvaluation.ts"
19541954
Expect Syntax Error: "conformance/classes/awaitAndYieldInProperty.ts"
1955-
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAccessor.ts"
19561955
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAssignabilityConstructorFunction.ts"
19571956
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractClinterfaceAssignability.ts"
1958-
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractConstructor.ts"
19591957
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractConstructorAssignability.ts"
19601958
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractDeclarations.d.ts"
19611959
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractExtends.ts"
@@ -1967,8 +1965,6 @@ Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword
19671965
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInheritance2.ts"
19681966
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations1.ts"
19691967
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts"
1970-
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodInNonAbstractClass.ts"
1971-
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodWithImplementation.ts"
19721968
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts"
19731969
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractOverloads.ts"
19741970
Expect Syntax Error: "conformance/classes/classDeclarations/classAbstractKeyword/classAbstractOverrideWithAbstract.ts"
@@ -2118,7 +2114,6 @@ Expect Syntax Error: "conformance/classes/propertyMemberDeclarations/accessorsOv
21182114
Expect Syntax Error: "conformance/classes/propertyMemberDeclarations/accessorsOverrideProperty3.ts"
21192115
Expect Syntax Error: "conformance/classes/propertyMemberDeclarations/accessorsOverrideProperty4.ts"
21202116
Expect Syntax Error: "conformance/classes/propertyMemberDeclarations/accessorsOverrideProperty6.ts"
2121-
Expect Syntax Error: "conformance/classes/propertyMemberDeclarations/accessorsOverrideProperty7.ts"
21222117
Expect Syntax Error: "conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationES2022.ts"
21232118
Expect Syntax Error: "conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts"
21242119
Expect Syntax Error: "conformance/classes/propertyMemberDeclarations/autoAccessor1.ts"
@@ -11074,6 +11069,46 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts"
1107411069
47 │ }
1107511070
╰────
1107611071

11072+
× TS(1318): Accessor 'aa' cannot have an implementation because it is marked abstract.
11073+
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAccessor.ts:3:17]
11074+
2 │ abstract get a();
11075+
3 │ abstract get aa() { return 1; } // error
11076+
· ──
11077+
4 │ abstract set b(x: string);
11078+
╰────
11079+
11080+
× TS(1245): Method 'aa' cannot have an implementation because it is marked abstract.
11081+
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAccessor.ts:3:17]
11082+
2 │ abstract get a();
11083+
3 │ abstract get aa() { return 1; } // error
11084+
· ──
11085+
4 │ abstract set b(x: string);
11086+
╰────
11087+
11088+
× TS(1318): Accessor 'bb' cannot have an implementation because it is marked abstract.
11089+
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAccessor.ts:5:17]
11090+
4 │ abstract set b(x: string);
11091+
5 │ abstract set bb(x: string) {} // error
11092+
· ──
11093+
6 │ }
11094+
╰────
11095+
11096+
× TS(1245): Method 'bb' cannot have an implementation because it is marked abstract.
11097+
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractAccessor.ts:5:17]
11098+
4 │ abstract set b(x: string);
11099+
5 │ abstract set bb(x: string) {} // error
11100+
· ──
11101+
6 │ }
11102+
╰────
11103+
11104+
× TS(1242): 'abstract' modifier can only appear on a class, method, or property declaration.
11105+
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractConstructor.ts:2:14]
11106+
1 │ abstract class A {
11107+
2 │ abstract constructor() {}
11108+
· ───────────
11109+
3 │ }
11110+
╰────
11111+
1107711112
× Unexpected token
1107811113
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractCrashedOnce.ts:8:5]
1107911114
7 │ this.
@@ -11114,6 +11149,38 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts"
1111411149
18 │
1111511150
╰────
1111611151

11152+
× TS(1245): Method 'foo' cannot have an implementation because it is marked abstract.
11153+
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodInNonAbstractClass.ts:6:14]
11154+
5 │ class B {
11155+
6 │ abstract foo() {}
11156+
· ───
11157+
7 │ }
11158+
╰────
11159+
11160+
× TS(1245): Method 'foo' cannot have an implementation because it is marked abstract.
11161+
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodInNonAbstractClass.ts:6:14]
11162+
5 │ class B {
11163+
6 │ abstract foo() {}
11164+
· ───
11165+
7 │ }
11166+
╰────
11167+
11168+
× TS(1245): Method 'foo' cannot have an implementation because it is marked abstract.
11169+
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodWithImplementation.ts:2:14]
11170+
1 │ abstract class A {
11171+
2 │ abstract foo() {}
11172+
· ───
11173+
3 │ }
11174+
╰────
11175+
11176+
× TS(1245): Method 'foo' cannot have an implementation because it is marked abstract.
11177+
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMethodWithImplementation.ts:2:14]
11178+
1 │ abstract class A {
11179+
2 │ abstract foo() {}
11180+
· ───
11181+
3 │ }
11182+
╰────
11183+
1111711184
× 'abstract' modifier cannot be used here.
1111811185
╭─[conformance/classes/classDeclarations/classAbstractKeyword/classAbstractWithInterface.ts:1:1]
1111911186
1 │ abstract interface I {}
@@ -12361,6 +12428,14 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts"
1236112428
╰────
1236212429
help: Remove the duplicate modifier.
1236312430

12431+
× TS(1267): Property 'p' cannot have an initializer because it is marked abstract.
12432+
╭─[conformance/classes/propertyMemberDeclarations/accessorsOverrideProperty7.ts:2:14]
12433+
1 │ abstract class A {
12434+
2 │ abstract p = 'yep'
12435+
· ─
12436+
3 │ }
12437+
╰────
12438+
1236412439
× Identifier `accessor` has already been declared
1236512440
╭─[conformance/classes/propertyMemberDeclarations/autoAccessor11.ts:5:12]
1236612441
4 │

0 commit comments

Comments
 (0)