Skip to content

Commit 83da050

Browse files
committed
fix(transformer/typescript): parameter property assignments before conditional super()
When `super()` is inside a top-level control flow statement (if/else, switch, try, labeled), parameter property assignments were inserted at the start of the constructor body — before `super()`. Fixes #20527 Amp-Thread-ID: https://ampcode.com/threads/T-019f9fdf-7db3-700c-a022-472ecfe7119d
1 parent 07a189c commit 83da050

4 files changed

Lines changed: 272 additions & 18 deletions

File tree

  • crates/oxc_transformer/src/typescript
  • tasks/transform_conformance

crates/oxc_transformer/src/typescript/class.rs

Lines changed: 123 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use rustc_hash::FxHashSet;
33
use oxc_allocator::{ArenaVec, ReplaceWith, TakeIn};
44
use oxc_ast::{ast::*, builder::NONE};
55
use oxc_semantic::{ScopeFlags, ScopeId};
6-
use oxc_span::SPAN;
6+
use oxc_span::{GetSpan, SPAN};
77
use oxc_str::Ident;
88
use oxc_syntax::operator::AssignmentOperator;
99
use oxc_traverse::BoundIdentifier;
@@ -411,13 +411,28 @@ impl<'a> TypeScript<'a> {
411411
return;
412412
}
413413

414-
let params = &constructor.value.params.items;
415-
let assignments = Self::convert_constructor_params(params, ctx).collect::<Vec<_>>();
414+
let constructor_scope_id = constructor.value.scope_id();
415+
let Function { params, body, .. } = &mut *constructor.value;
416+
let params = &params.items;
417+
if !params
418+
.iter()
419+
.any(|param| param.has_modifier() && param.pattern.get_binding_identifier().is_some())
420+
{
421+
return;
422+
}
416423

417-
let constructor_body_statements = &mut constructor.value.body.as_mut().unwrap().statements;
424+
let constructor_body_statements = &mut body.as_mut().unwrap().statements;
418425
let super_call_position = Self::get_super_call_position(constructor_body_statements);
426+
if super_call_position > 0
427+
&& let Statement::IfStatement(stmt) =
428+
&mut constructor_body_statements[super_call_position - 1]
429+
&& Self::can_insert_constructor_params_in_if_branches(stmt)
430+
{
431+
Self::insert_constructor_params_in_if_branches(stmt, params, constructor_scope_id, ctx);
432+
return;
433+
}
419434

420-
// Insert the assignments after the `super()` call
435+
let assignments = Self::convert_constructor_params(params, ctx);
421436
constructor_body_statements.splice(super_call_position..super_call_position, assignments);
422437
}
423438

@@ -479,19 +494,111 @@ impl<'a> TypeScript<'a> {
479494
Self::create_assignment(target, value, ctx)
480495
}
481496

482-
/// Find the position of the `super()` call in the constructor body, otherwise return 0.
497+
/// Find the position after the `super()` call in the constructor body, otherwise return 0.
483498
///
484-
/// Don't need to handle nested `super()` call because `TypeScript` doesn't allow it.
499+
/// If `super()` is nested inside a top-level control flow statement, return the position after
500+
/// the containing statement.
485501
pub fn get_super_call_position(statements: &[Statement<'a>]) -> usize {
486-
// Find the position of the `super()` call in the constructor body.
487-
// Don't need to handle nested `super()` call because `TypeScript` doesn't allow it.
488-
statements
489-
.iter()
490-
.position(|stmt| {
491-
matches!(stmt, Statement::ExpressionStatement(stmt)
492-
if stmt.expression.is_super_call_expression())
493-
})
494-
.map_or(0, |pos| pos + 1)
502+
statements.iter().position(Self::statement_contains_super_call).map_or(0, |pos| pos + 1)
503+
}
504+
505+
fn statement_contains_super_call(stmt: &Statement<'a>) -> bool {
506+
if Self::statement_contains_direct_super_call(stmt) {
507+
return true;
508+
}
509+
510+
match stmt {
511+
Statement::BlockStatement(stmt) => {
512+
stmt.body.iter().any(Self::statement_contains_super_call)
513+
}
514+
Statement::IfStatement(stmt) => {
515+
Self::statement_contains_super_call(&stmt.consequent)
516+
|| stmt.alternate.as_ref().is_some_and(Self::statement_contains_super_call)
517+
}
518+
Statement::SwitchStatement(stmt) => stmt
519+
.cases
520+
.iter()
521+
.any(|case| case.consequent.iter().any(Self::statement_contains_super_call)),
522+
Statement::TryStatement(stmt) => {
523+
stmt.block.body.iter().any(Self::statement_contains_super_call)
524+
|| stmt.handler.as_ref().is_some_and(|handler| {
525+
handler.body.body.iter().any(Self::statement_contains_super_call)
526+
})
527+
|| stmt.finalizer.as_ref().is_some_and(|block| {
528+
block.body.iter().any(Self::statement_contains_super_call)
529+
})
530+
}
531+
Statement::LabeledStatement(stmt) => Self::statement_contains_super_call(&stmt.body),
532+
_ => false,
533+
}
534+
}
535+
536+
fn can_insert_constructor_params_in_if_branches(stmt: &IfStatement<'a>) -> bool {
537+
Self::can_insert_constructor_params_in_if_branch(&stmt.consequent)
538+
&& stmt.alternate.as_ref().is_some_and(Self::can_insert_constructor_params_in_if_branch)
539+
}
540+
541+
fn can_insert_constructor_params_in_if_branch(stmt: &Statement<'a>) -> bool {
542+
Self::statement_contains_direct_super_call(stmt)
543+
|| matches!(stmt, Statement::BlockStatement(block)
544+
if block.body.iter().any(Self::statement_contains_direct_super_call))
545+
}
546+
547+
fn insert_constructor_params_in_if_branches(
548+
stmt: &mut IfStatement<'a>,
549+
params: &ArenaVec<'a, FormalParameter<'a>>,
550+
constructor_scope_id: ScopeId,
551+
ctx: &mut TraverseCtx<'a>,
552+
) {
553+
Self::insert_constructor_params_in_if_branch(
554+
&mut stmt.consequent,
555+
params,
556+
constructor_scope_id,
557+
ctx,
558+
);
559+
Self::insert_constructor_params_in_if_branch(
560+
stmt.alternate.as_mut().unwrap(),
561+
params,
562+
constructor_scope_id,
563+
ctx,
564+
);
565+
}
566+
567+
fn insert_constructor_params_in_if_branch(
568+
stmt: &mut Statement<'a>,
569+
params: &ArenaVec<'a, FormalParameter<'a>>,
570+
constructor_scope_id: ScopeId,
571+
ctx: &mut TraverseCtx<'a>,
572+
) {
573+
match stmt {
574+
Statement::BlockStatement(stmt) => {
575+
let position =
576+
stmt.body.iter().position(Self::statement_contains_direct_super_call).unwrap()
577+
+ 1;
578+
stmt.body.splice(position..position, Self::convert_constructor_params(params, ctx));
579+
}
580+
_ if Self::statement_contains_direct_super_call(stmt) => {
581+
let scope_id = ctx.insert_scope_below_statement_from_scope_id(
582+
stmt,
583+
constructor_scope_id,
584+
ScopeFlags::empty(),
585+
);
586+
let span = stmt.span();
587+
let mut body = ArenaVec::from_array_in([stmt.take_in(ctx)], ctx);
588+
body.extend(Self::convert_constructor_params(params, ctx));
589+
*stmt = Statement::new_block_statement_with_scope_id(span, body, scope_id, ctx);
590+
}
591+
_ => {}
592+
}
593+
}
594+
595+
fn statement_contains_direct_super_call(stmt: &Statement<'a>) -> bool {
596+
matches!(stmt, Statement::ExpressionStatement(stmt) if match &stmt.expression {
597+
Expression::SequenceExpression(seq) => {
598+
seq.expressions.iter().any(Expression::is_super_call_expression)
599+
}
600+
expr => expr.is_super_call_expression(),
601+
})
495602
}
496603

497604
/// Convert computed key to sequence expression if there are assignments.

tasks/transform_conformance/snapshots/oxc.snap.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
commit: c86e9e4b
22

3-
Passed: 270/398
3+
Passed: 271/399
44

55
# All Passed:
66
* babel-plugin-transform-class-static-block
@@ -48,7 +48,7 @@ x Output mismatch
4848
x Output mismatch
4949

5050

51-
# babel-plugin-transform-typescript (41/60)
51+
# babel-plugin-transform-typescript (42/61)
5252
* allow-declare-fields-false/input.ts
5353
Unresolved references mismatch:
5454
after transform: ["dce"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
class MyError extends Error {
2+
constructor(
3+
public code: string,
4+
public description?: string,
5+
) {
6+
if (description) {
7+
super(code + ': ' + description);
8+
} else {
9+
super(code);
10+
}
11+
this.name = 'MyError';
12+
}
13+
}
14+
15+
class MyError2 extends Error {
16+
constructor(
17+
public code: string,
18+
) {
19+
switch (code) {
20+
case 'A':
21+
super('Error A');
22+
break;
23+
default:
24+
super(code);
25+
}
26+
}
27+
}
28+
29+
class MyError3 extends Error {
30+
constructor(
31+
public code: string,
32+
) {
33+
super(code), init(this);
34+
}
35+
}
36+
37+
class Unbraced extends Error {
38+
constructor(public code: string, useCode: boolean) {
39+
if (useCode) super(code), init(this);
40+
else super();
41+
}
42+
}
43+
44+
class NestedConditional extends Error {
45+
constructor(public code: string, first: boolean, second: boolean) {
46+
if (first) {
47+
if (second) super(code);
48+
return {};
49+
} else {
50+
super(code);
51+
}
52+
}
53+
}
54+
55+
class OneSuperBranch extends Error {
56+
constructor(public code: string, skip: boolean) {
57+
if (skip) return {};
58+
else super(code);
59+
}
60+
}
61+
62+
class MissingElse extends Error {
63+
constructor(public code: string, initialize: boolean) {
64+
if (initialize) super(code);
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
class MyError extends Error {
2+
code;
3+
description;
4+
constructor(code, description) {
5+
if (description) {
6+
super(code + ': ' + description);
7+
this.code = code;
8+
this.description = description;
9+
} else {
10+
super(code);
11+
this.code = code;
12+
this.description = description;
13+
}
14+
this.name = 'MyError';
15+
}
16+
}
17+
18+
class MyError2 extends Error {
19+
code;
20+
constructor(code) {
21+
switch (code) {
22+
case 'A':
23+
super('Error A');
24+
break;
25+
default:
26+
super(code);
27+
}
28+
this.code = code;
29+
}
30+
}
31+
32+
class MyError3 extends Error {
33+
code;
34+
constructor(code) {
35+
super(code), init(this);
36+
this.code = code;
37+
}
38+
}
39+
40+
class Unbraced extends Error {
41+
code;
42+
constructor(code, useCode) {
43+
if (useCode) {
44+
super(code), init(this);
45+
this.code = code;
46+
} else {
47+
super();
48+
this.code = code;
49+
}
50+
}
51+
}
52+
53+
class NestedConditional extends Error {
54+
code;
55+
constructor(code, first, second) {
56+
if (first) {
57+
if (second) super(code);
58+
return {};
59+
} else {
60+
super(code);
61+
}
62+
this.code = code;
63+
}
64+
}
65+
66+
class OneSuperBranch extends Error {
67+
code;
68+
constructor(code, skip) {
69+
if (skip) return {};
70+
else super(code);
71+
this.code = code;
72+
}
73+
}
74+
75+
class MissingElse extends Error {
76+
code;
77+
constructor(code, initialize) {
78+
if (initialize) super(code);
79+
this.code = code;
80+
}
81+
}

0 commit comments

Comments
 (0)