diff --git a/internal/ast/ast.go b/internal/ast/ast.go index c65424c215..df7c25d760 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -2496,6 +2496,10 @@ func (node *TryStatement) Clone(f *NodeFactory) *Node { return cloneNode(f.NewTryStatement(node.TryBlock, node.CatchClause, node.FinallyBlock), node.AsNode(), f.hooks) } +func IsTryStatement(node *Node) bool { + return node.Kind == KindTryStatement +} + // CatchClause type CatchClause struct { diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 3841fdf098..92c6eb0f0f 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1495,6 +1495,50 @@ func GetContainingClass(node *Node) *Node { return FindAncestor(node.Parent, IsClassLike) } +func GetExtendsHeritageClauseElement(node *Node) *ExpressionWithTypeArgumentsNode { + return core.FirstOrNil(GetExtendsHeritageClauseElements(node)) +} + +func GetExtendsHeritageClauseElements(node *Node) []*ExpressionWithTypeArgumentsNode { + return getHeritageElements(node, KindExtendsKeyword) +} + +func GetImplementsHeritageClauseElements(node *Node) []*ExpressionWithTypeArgumentsNode { + return getHeritageElements(node, KindImplementsKeyword) +} + +func getHeritageElements(node *Node, kind Kind) []*Node { + clause := getHeritageClause(node, kind) + if clause != nil { + return clause.AsHeritageClause().Types.Nodes + } + return nil +} + +func getHeritageClause(node *Node, kind Kind) *Node { + clauses := getHeritageClauses(node) + if clauses != nil { + for _, clause := range clauses.Nodes { + if clause.AsHeritageClause().Token == kind { + return clause + } + } + } + return nil +} + +func getHeritageClauses(node *Node) *NodeList { + switch node.Kind { + case KindClassDeclaration: + return node.AsClassDeclaration().HeritageClauses + case KindClassExpression: + return node.AsClassExpression().HeritageClauses + case KindInterfaceDeclaration: + return node.AsInterfaceDeclaration().HeritageClauses + } + return nil +} + func IsPartOfTypeQuery(node *Node) bool { for node.Kind == KindQualifiedName || node.Kind == KindIdentifier { node = node.Parent @@ -1844,6 +1888,10 @@ func isJSXTagName(node *Node) bool { return false } +func IsSuperCall(node *Node) bool { + return IsCallExpression(node) && node.AsCallExpression().Expression.Kind == KindSuperKeyword +} + func IsImportCall(node *Node) bool { return IsCallExpression(node) && node.AsCallExpression().Expression.Kind == KindImportKeyword } diff --git a/internal/checker/checker.go b/internal/checker/checker.go index aa527d149d..5c4de12cdb 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -2526,7 +2526,7 @@ func (c *Checker) checkConstructorDeclaration(node *ast.Node) { // Constructors of classes with no extends clause may not contain super calls, whereas // constructors of derived classes must contain at least one super call somewhere in their function body. containingClassDecl := node.Parent - if getExtendsTypeNode(containingClassDecl) == nil { + if ast.GetExtendsHeritageClauseElement(containingClassDecl) == nil { return } classExtendsNull := c.classDeclarationExtendsNull(containingClassDecl) @@ -3937,7 +3937,7 @@ func (c *Checker) checkClassLikeDeclaration(node *ast.Node) { c.checkTypeParameterListsIdentical(symbol) c.checkFunctionOrConstructorSymbol(symbol) c.checkObjectTypeForDuplicateDeclarations(node, true /*checkPrivateNames*/) - baseTypeNode := getExtendsTypeNode(node) + baseTypeNode := ast.GetExtendsHeritageClauseElement(node) if baseTypeNode != nil { c.checkSourceElements(baseTypeNode.TypeArguments()) baseTypes := c.getBaseTypes(classType) @@ -3988,7 +3988,7 @@ func (c *Checker) checkClassLikeDeclaration(node *ast.Node) { } } c.checkMembersForOverrideModifier(node, classType, typeWithThis, staticType) - implementedTypeNodes := getImplementsTypeNodes(node) + implementedTypeNodes := ast.GetImplementsHeritageClauseElements(node) for _, typeRefNode := range implementedTypeNodes { expr := typeRefNode.Expression() if !ast.IsEntityNameExpression(expr) || ast.IsOptionalChain(expr) { @@ -4308,7 +4308,7 @@ func (c *Checker) isPropertyAbstractOrInterface(declaration *ast.Node, baseDecla func (c *Checker) checkMembersForOverrideModifier(node *ast.Node, t *Type, typeWithThis *Type, staticType *Type) { var baseWithThis *Type - baseTypeNode := getExtendsTypeNode(node) + baseTypeNode := ast.GetExtendsHeritageClauseElement(node) if baseTypeNode != nil { baseTypes := c.getBaseTypes(t) if len(baseTypes) > 0 { @@ -4611,7 +4611,7 @@ func (c *Checker) checkInterfaceDeclaration(node *ast.Node) { } } c.checkObjectTypeForDuplicateDeclarations(node, false /*checkPrivateNames*/) - for _, heritageElement := range getExtendsTypeNodes(node) { + for _, heritageElement := range ast.GetExtendsHeritageClauseElements(node) { expr := heritageElement.Expression() if !ast.IsEntityNameExpression(expr) || ast.IsOptionalChain(expr) { c.error(expr, diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments) @@ -7336,7 +7336,7 @@ func (c *Checker) checkSuperExpression(node *ast.Node) *Type { } // at this point the only legal case for parent is ClassLikeDeclaration classLikeDeclaration := container.Parent - if getExtendsTypeNode(classLikeDeclaration) == nil { + if ast.GetExtendsHeritageClauseElement(classLikeDeclaration) == nil { c.error(node, diagnostics.X_super_can_only_be_referenced_in_a_derived_class) return c.errorType } @@ -7871,7 +7871,7 @@ func (c *Checker) resolveCallExpression(node *ast.Node, candidatesOutArray *[]*S if !c.isErrorType(superType) { // In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated // with the type arguments specified in the extends clause. - baseTypeNode := getExtendsTypeNode(ast.GetContainingClass(node)) + baseTypeNode := ast.GetExtendsHeritageClauseElement(ast.GetContainingClass(node)) if baseTypeNode != nil { baseConstructors := c.getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.TypeArguments(), baseTypeNode) return c.resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlagsNone, nil) @@ -11422,7 +11422,7 @@ func (c *Checker) checkThisInStaticClassFieldInitializerInDecoratedClass(thisExp func (c *Checker) checkThisBeforeSuper(node *ast.Node, container *ast.Node, diagnosticMessage *diagnostics.Message) { containingClassDecl := container.Parent - baseTypeNode := getExtendsTypeNode(containingClassDecl) + baseTypeNode := ast.GetExtendsHeritageClauseElement(containingClassDecl) // If a containing class does not have extends clause or the class extends null // skip checking whether super statement is called before "this" accessing. if baseTypeNode != nil && !c.classDeclarationExtendsNull(containingClassDecl) { @@ -15740,7 +15740,7 @@ func (c *Checker) isThislessInterface(symbol *ast.Symbol) bool { if declaration.Flags&ast.NodeFlagsContainsThis != 0 { return false } - baseTypeNodes := getExtendsTypeNodes(declaration) + baseTypeNodes := ast.GetExtendsHeritageClauseElements(declaration) for _, node := range baseTypeNodes { if ast.IsEntityNameExpression(node.Expression()) { baseSymbol := c.resolveEntityName(node.Expression(), ast.SymbolFlagsType, true /*ignoreErrors*/, false, nil) @@ -17437,7 +17437,7 @@ func (c *Checker) resolveBaseTypesOfClass(t *Type) { func getBaseTypeNodeOfClass(t *Type) *ast.Node { decl := getClassLikeDeclarationOfSymbol(t.symbol) if decl != nil { - return getExtendsTypeNode(decl) + return ast.GetExtendsHeritageClauseElement(decl) } return nil } @@ -17671,7 +17671,7 @@ func (c *Checker) resolveBaseTypesOfInterface(t *Type) { data := t.AsInterfaceType() for _, declaration := range t.symbol.Declarations { if ast.IsInterfaceDeclaration(declaration) { - for _, node := range getExtendsTypeNodes(declaration) { + for _, node := range ast.GetExtendsHeritageClauseElements(declaration) { baseType := c.getReducedType(c.getTypeFromTypeNode(node)) if !c.isErrorType(baseType) { if c.isValidBaseType(baseType) { diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index 25312389e0..4d939af194 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -1273,50 +1273,6 @@ func parameterIsThisKeyword(parameter *ast.Node) bool { return ast.IsThisParameter(parameter) } -func getExtendsTypeNode(node *ast.Node) *ast.Node { - return core.FirstOrNil(getExtendsTypeNodes(node)) -} - -func getExtendsTypeNodes(node *ast.Node) []*ast.Node { - return getHeritageTypeNodes(node, ast.KindExtendsKeyword) -} - -func getImplementsTypeNodes(node *ast.Node) []*ast.Node { - return getHeritageTypeNodes(node, ast.KindImplementsKeyword) -} - -func getHeritageTypeNodes(node *ast.Node, kind ast.Kind) []*ast.Node { - clause := getHeritageClause(node, kind) - if clause != nil { - return clause.AsHeritageClause().Types.Nodes - } - return nil -} - -func getHeritageClause(node *ast.Node, kind ast.Kind) *ast.Node { - clauses := getHeritageClauses(node) - if clauses != nil { - for _, clause := range clauses.Nodes { - if clause.AsHeritageClause().Token == kind { - return clause - } - } - } - return nil -} - -func getHeritageClauses(node *ast.Node) *ast.NodeList { - switch node.Kind { - case ast.KindClassDeclaration: - return node.AsClassDeclaration().HeritageClauses - case ast.KindClassExpression: - return node.AsClassExpression().HeritageClauses - case ast.KindInterfaceDeclaration: - return node.AsInterfaceDeclaration().HeritageClauses - } - return nil -} - func isObjectOrArrayLiteralType(t *Type) bool { return t.objectFlags&(ObjectFlagsObjectLiteral|ObjectFlagsArrayLiteral) != 0 } diff --git a/internal/transformers/runtimesyntax.go b/internal/transformers/runtimesyntax.go index 657a83fb53..ee9bd1f7cd 100644 --- a/internal/transformers/runtimesyntax.go +++ b/internal/transformers/runtimesyntax.go @@ -2,10 +2,11 @@ package transformers // !!! Unqualified enum member references across merged enum declarations are not currently supported (e.g `enum E {A}; enum E {B=A}`) // !!! Unqualified namespace member references across merged namespace declarations are not currently supported (e.g `namespace N { export var x = 1; }; namespace N { x; }`). -// !!! Parameter Property Initializers are not yet implemented. // !!! SourceMaps and Comments need to be validated import ( + "slices" + "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/binder" "github.com/microsoft/typescript-go/internal/core" @@ -35,15 +36,23 @@ func NewRuntimeSyntaxTransformer(emitContext *printer.EmitContext, compilerOptio return tx.newTransformer(tx.visit, emitContext) } -// Visits each node in the AST -func (tx *RuntimeSyntaxTransformer) visit(node *ast.Node) *ast.Node { - savedCurrentScope := tx.currentScope - savedCurrentScopeFirstDeclarationsOfName := tx.currentScopeFirstDeclarationsOfName - savedParentNode := tx.parentNode - +// Pushes a new child node onto the ancestor tracking stack, returning the grandparent node to be restored later via `popNode`. +func (tx *RuntimeSyntaxTransformer) pushNode(node *ast.Node) (grandparentNode *ast.Node) { + grandparentNode = tx.parentNode tx.parentNode = tx.currentNode tx.currentNode = node + return +} +// Pops the last child node off the ancestor tracking stack, restoring the grandparent node. +func (tx *RuntimeSyntaxTransformer) popNode(grandparentNode *ast.Node) { + tx.currentNode = tx.parentNode + tx.parentNode = grandparentNode +} + +func (tx *RuntimeSyntaxTransformer) pushScope(node *ast.Node) (savedCurrentScope *ast.Node, savedCurrentScopeFirstDeclarationsOfName map[string]*ast.Node) { + savedCurrentScope = tx.currentScope + savedCurrentScopeFirstDeclarationsOfName = tx.currentScopeFirstDeclarationsOfName switch node.Kind { case ast.KindSourceFile: tx.currentScope = node @@ -55,14 +64,41 @@ func (tx *RuntimeSyntaxTransformer) visit(node *ast.Node) *ast.Node { case ast.KindFunctionDeclaration, ast.KindClassDeclaration, ast.KindEnumDeclaration, ast.KindModuleDeclaration, ast.KindVariableDeclaration: tx.recordDeclarationInScope(node) } + return savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName +} + +func (tx *RuntimeSyntaxTransformer) popScope(savedCurrentScope *ast.Node, savedCurrentScopeFirstDeclarationsOfName map[string]*ast.Node) { + if tx.currentScope != savedCurrentScope { + // only reset the first declaration for a name if we are exiting the scope in which it was declared + tx.currentScopeFirstDeclarationsOfName = savedCurrentScopeFirstDeclarationsOfName + } + + tx.currentScope = savedCurrentScope +} + +// Visits each node in the AST +func (tx *RuntimeSyntaxTransformer) visit(node *ast.Node) *ast.Node { + savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName := tx.pushScope(node) + grandparentNode := tx.pushNode(node) switch node.Kind { + // TypeScript parameter property modifiers are elided + case ast.KindPublicKeyword, + ast.KindPrivateKeyword, + ast.KindProtectedKeyword, + ast.KindReadonlyKeyword, + ast.KindOverrideKeyword: + return nil case ast.KindEnumDeclaration: node = tx.visitEnumDeclaration(node.AsEnumDeclaration()) case ast.KindModuleDeclaration: node = tx.visitModuleDeclaration(node.AsModuleDeclaration()) case ast.KindClassDeclaration: node = tx.visitClassDeclaration(node.AsClassDeclaration()) + case ast.KindClassExpression: + node = tx.visitClassExpression(node.AsClassExpression()) + case ast.KindConstructor: + node = tx.visitConstructorDeclaration(node.AsConstructorDeclaration()) case ast.KindFunctionDeclaration: node = tx.visitFunctionDeclaration(node.AsFunctionDeclaration()) case ast.KindVariableStatement: @@ -77,14 +113,8 @@ func (tx *RuntimeSyntaxTransformer) visit(node *ast.Node) *ast.Node { node = tx.visitor.VisitEachChild(node) } - if tx.currentScope != savedCurrentScope { - // only reset the first declaration for a name if we are exiting the scope in which it was declared - tx.currentScopeFirstDeclarationsOfName = savedCurrentScopeFirstDeclarationsOfName - } - - tx.currentScope = savedCurrentScope - tx.currentNode = tx.parentNode - tx.parentNode = savedParentNode + tx.popNode(grandparentNode) + tx.popScope(savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName) return node } @@ -673,20 +703,258 @@ func (tx *RuntimeSyntaxTransformer) visitFunctionDeclaration(node *ast.FunctionD return tx.visitor.VisitEachChild(node.AsNode()) } +func (tx *RuntimeSyntaxTransformer) getParameterProperties(constructor *ast.Node) []*ast.ParameterDeclaration { + var parameterProperties []*ast.ParameterDeclaration + if constructor != nil { + for _, parameter := range constructor.Parameters() { + if ast.IsParameterPropertyDeclaration(parameter, constructor) { + parameterProperties = append(parameterProperties, parameter.AsParameterDeclaration()) + } + } + } + return parameterProperties +} + func (tx *RuntimeSyntaxTransformer) visitClassDeclaration(node *ast.ClassDeclaration) *ast.Node { - if tx.isExportOfNamespace(node.AsNode()) { - updated := tx.factory.UpdateClassDeclaration( - node, - tx.visitor.VisitModifiers(extractModifiers(tx.emitContext, node.Modifiers(), ^ast.ModifierFlagsExportDefault)), - tx.visitor.VisitNode(node.Name()), - nil, /*typeParameters*/ - tx.visitor.VisitNodes(node.HeritageClauses), - tx.visitor.VisitNodes(node.Members), - ) + exported := tx.isExportOfNamespace(node.AsNode()) + var modifiers *ast.ModifierList + if exported { + modifiers = tx.visitor.VisitModifiers(extractModifiers(tx.emitContext, node.Modifiers(), ^ast.ModifierFlagsExportDefault)) + } else { + modifiers = tx.visitor.VisitModifiers(node.Modifiers()) + } + + name := tx.visitor.VisitNode(node.Name()) + heritageClauses := tx.visitor.VisitNodes(node.HeritageClauses) + members := tx.visitor.VisitNodes(node.Members) + parameterProperties := tx.getParameterProperties(core.Find(node.Members.Nodes, ast.IsConstructorDeclaration)) + + if len(parameterProperties) > 0 { + var newMembers []*ast.ClassElement + for _, parameter := range parameterProperties { + if ast.IsIdentifier(parameter.Name()) { + parameterProperty := tx.factory.NewPropertyDeclaration( + nil, /*modifiers*/ + parameter.Name().Clone(tx.factory), + nil, /*questionOrExclamationToken*/ + nil, /*type*/ + nil, /*initializer*/ + ) + tx.emitContext.SetOriginal(parameterProperty, parameter.AsNode()) + newMembers = append(newMembers, parameterProperty) + } + } + if len(newMembers) > 0 { + newMembers = append(newMembers, members.Nodes...) + members = tx.factory.NewNodeList(newMembers) + members.Loc = node.Members.Loc + } + } + + updated := tx.factory.UpdateClassDeclaration(node, modifiers, name, nil /*typeParameters*/, heritageClauses, members) + if exported { export := tx.createExportStatementForDeclaration(node.AsNode()) return tx.factory.NewSyntaxList([]*ast.Node{updated, export}) } - return tx.visitor.VisitEachChild(node.AsNode()) + return updated +} + +func (tx *RuntimeSyntaxTransformer) visitClassExpression(node *ast.ClassExpression) *ast.Node { + modifiers := tx.visitor.VisitModifiers(extractModifiers(tx.emitContext, node.Modifiers(), ^ast.ModifierFlagsExportDefault)) + name := tx.visitor.VisitNode(node.Name()) + heritageClauses := tx.visitor.VisitNodes(node.HeritageClauses) + members := tx.visitor.VisitNodes(node.Members) + parameterProperties := tx.getParameterProperties(core.Find(node.Members.Nodes, ast.IsConstructorDeclaration)) + + if len(parameterProperties) > 0 { + var newMembers []*ast.ClassElement + for _, parameter := range parameterProperties { + if ast.IsIdentifier(parameter.Name()) { + parameterProperty := tx.factory.NewPropertyDeclaration( + nil, /*modifiers*/ + parameter.Name().Clone(tx.factory), + nil, /*questionOrExclamationToken*/ + nil, /*type*/ + nil, /*initializer*/ + ) + tx.emitContext.SetOriginal(parameterProperty, parameter.AsNode()) + newMembers = append(newMembers, parameterProperty) + } + } + if len(newMembers) > 0 { + newMembers = append(newMembers, members.Nodes...) + members = tx.factory.NewNodeList(newMembers) + members.Loc = node.Members.Loc + } + } + + return tx.factory.UpdateClassExpression(node, modifiers, name, nil /*typeParameters*/, heritageClauses, members) +} + +func (tx *RuntimeSyntaxTransformer) visitConstructorDeclaration(node *ast.ConstructorDeclaration) *ast.Node { + modifiers := tx.visitor.VisitModifiers(node.Modifiers()) + parameters := tx.emitContext.VisitParameters(node.ParameterList(), tx.visitor) + body := tx.visitConstructorBody(node.Body.AsBlock(), node.AsNode()) + return tx.factory.UpdateConstructorDeclaration(node, modifiers, nil /*typeParameters*/, parameters, nil /*returnType*/, body) +} + +func (tx *RuntimeSyntaxTransformer) visitConstructorBody(body *ast.Block, constructor *ast.Node) *ast.Node { + parameterProperties := tx.getParameterProperties(constructor) + if len(parameterProperties) == 0 { + return tx.emitContext.VisitFunctionBody(body.AsNode(), tx.visitor) + } + + grandparentOfConstructor := tx.pushNode(constructor) + grandparentOfBody := tx.pushNode(body.AsNode()) + savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName := tx.pushScope(body.AsNode()) + + tx.emitContext.StartVariableEnvironment() + prologue, rest := tx.emitContext.SplitStandardPrologue(body.Statements.Nodes) + statements := slices.Clone(prologue) + + // Transform parameters into property assignments. Transforms this: + // + // constructor (public x, public y) { + // } + // + // Into this: + // + // constructor (x, y) { + // this.x = x; + // this.y = y; + // } + // + + var parameterPropertyAssignments []*ast.Statement + for _, parameter := range parameterProperties { + if ast.IsIdentifier(parameter.Name()) { + propertyName := parameter.Name().Clone(tx.factory) + tx.emitContext.AddEmitFlags(propertyName, printer.EFNoComments|printer.EFNoSourceMap) + + localName := parameter.Name().Clone(tx.factory) + tx.emitContext.AddEmitFlags(localName, printer.EFNoComments) + + parameterProperty := tx.factory.NewExpressionStatement( + tx.factory.NewBinaryExpression( + tx.factory.NewPropertyAccessExpression( + tx.factory.NewKeywordExpression(ast.KindThisKeyword), + nil, /*questionDotToken*/ + propertyName, + ast.NodeFlagsNone, + ), + tx.factory.NewToken(ast.KindEqualsToken), + localName, + ), + ) + tx.emitContext.SetOriginal(parameterProperty, parameter.AsNode()) + tx.emitContext.AddEmitFlags(parameterProperty, printer.EFStartOnNewLine) + parameterPropertyAssignments = append(parameterPropertyAssignments, parameterProperty) + } + } + + var superPath []int + if ast.IsClassLike(grandparentOfBody) && ast.GetExtendsHeritageClauseElement(grandparentOfBody) != nil { + superPath = findSuperStatementIndexPath(rest, 0) + } + + if len(superPath) > 0 { + statements = append(statements, tx.transformConstructorBodyWorker(rest, superPath, parameterPropertyAssignments)...) + } else { + statements = append(statements, parameterPropertyAssignments...) + statements = append(statements, core.FirstResult(tx.visitor.VisitSlice(rest))...) + } + + statements = tx.emitContext.EndAndMergeVariableEnvironment(statements) + statementList := tx.factory.NewNodeList(statements) + statementList.Loc = body.Statements.Loc + + tx.popScope(savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName) + tx.popNode(grandparentOfBody) + tx.popNode(grandparentOfConstructor) + updated := tx.factory.NewBlock(statementList /*multiline*/, true) + tx.emitContext.SetOriginal(updated, body.AsNode()) + updated.Loc = body.Loc + return updated +} + +// finds a path to a statement containing a `super` call, descending through `try` blocks +func findSuperStatementIndexPath(statements []*ast.Statement, start int) []int { + for i := start; i < len(statements); i++ { + statement := statements[i] + if getSuperCallFromStatement(statement) != nil { + indices := make([]int, 1, 2) + indices[0] = i + return indices + } else if ast.IsTryStatement(statement) { + return slices.Insert(findSuperStatementIndexPath(statement.AsTryStatement().TryBlock.AsBlock().Statements.Nodes, 0), 0, i) + } + } + return nil +} + +func getSuperCallFromStatement(statement *ast.Statement) *ast.Node { + if !ast.IsExpressionStatement(statement) { + return nil + } + + expression := ast.SkipParentheses(statement.Expression()) + if ast.IsSuperCall(expression) { + return expression + } + return nil +} + +func (tx *RuntimeSyntaxTransformer) transformConstructorBodyWorker(statementsIn []*ast.Statement, superPath []int, initializerStatements []*ast.Statement) []*ast.Statement { + var statementsOut []*ast.Statement + superStatementIndex := superPath[0] + superStatement := statementsIn[superStatementIndex] + + // visit up to the statement containing `super` + statementsOut = append(statementsOut, core.FirstResult(tx.visitor.VisitSlice(statementsIn[:superStatementIndex]))...) + + // if the statement containing `super` is a `try` statement, transform the body of the `try` block + if ast.IsTryStatement(superStatement) { + tryStatement := superStatement.AsTryStatement() + tryBlock := tryStatement.TryBlock.AsBlock() + + // keep track of hierarchy as we descend + grandparentOfTryStatement := tx.pushNode(tryStatement.AsNode()) + grandparentOfTryBlock := tx.pushNode(tryBlock.AsNode()) + savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName := tx.pushScope(tryBlock.AsNode()) + + // visit the `try` block + tryBlockStatements := tx.transformConstructorBodyWorker( + tryBlock.Statements.Nodes, + superPath[1:], + initializerStatements, + ) + + // restore hierarchy as we ascend to the `try` statement + tx.popScope(savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName) + tx.popNode(grandparentOfTryBlock) + + tryBlockStatementList := tx.factory.NewNodeList(tryBlockStatements) + tryBlockStatementList.Loc = tryBlock.Statements.Loc + statementsOut = append(statementsOut, tx.factory.UpdateTryStatement( + tryStatement, + tx.factory.UpdateBlock(tryBlock, tryBlockStatementList), + tx.visitor.VisitNode(tryStatement.CatchClause), + tx.visitor.VisitNode(tryStatement.FinallyBlock), + )) + + // restore hierarchy as we ascend to the parent of the `try` statement + tx.popNode(grandparentOfTryStatement) + } else { + // visit the statement containing `super` + statementsOut = append(statementsOut, core.FirstResult(tx.visitor.VisitSlice(statementsIn[superStatementIndex:superStatementIndex+1]))...) + + // insert the initializer statements + statementsOut = append(statementsOut, initializerStatements...) + } + + // visit the statements after `super` + statementsOut = append(statementsOut, core.FirstResult(tx.visitor.VisitSlice(statementsIn[superStatementIndex+1:]))...) + return statementsOut } func (tx *RuntimeSyntaxTransformer) visitShorthandPropertyAssignment(node *ast.ShorthandPropertyAssignment) *ast.Node { diff --git a/internal/transformers/runtimesyntax_test.go b/internal/transformers/runtimesyntax_test.go index 997f3946b5..488aeeafb8 100644 --- a/internal/transformers/runtimesyntax_test.go +++ b/internal/transformers/runtimesyntax_test.go @@ -398,3 +398,34 @@ func TestNamespaceTransformer(t *testing.T) { }) } } + +func TestParameterPropertyTransformer(t *testing.T) { + t.Parallel() + data := []struct { + title string + input string + output string + }{ + {title: "parameter properties", input: "class C { constructor(public x) { } }", output: `class C { + x; + constructor(x) { + this.x = x; + } +}`}, + } + + for _, rec := range data { + t.Run(rec.title, func(t *testing.T) { + t.Parallel() + options := &core.CompilerOptions{} + file := parsetestutil.ParseTypeScript(rec.input, false /*jsx*/) + parsetestutil.CheckDiagnostics(t, file) + binder.BindSourceFile(file, options) + emitContext := printer.NewEmitContext() + resolver := binder.NewReferenceResolver(binder.ReferenceResolverHooks{}) + file = NewTypeEraserTransformer(emitContext, options).TransformSourceFile(file) + file = NewRuntimeSyntaxTransformer(emitContext, options, resolver).TransformSourceFile(file) + emittestutil.CheckEmit(t, emitContext, file, rec.output) + }) + } +} diff --git a/internal/transformers/typeeraser.go b/internal/transformers/typeeraser.go index 5fd6f62a82..001366fc1e 100644 --- a/internal/transformers/typeeraser.go +++ b/internal/transformers/typeeraser.go @@ -9,6 +9,8 @@ import ( type TypeEraserTransformer struct { Transformer compilerOptions *core.CompilerOptions + parentNode *ast.Node + currentNode *ast.Node } func NewTypeEraserTransformer(emitContext *printer.EmitContext, compilerOptions *core.CompilerOptions) *Transformer { @@ -16,6 +18,20 @@ func NewTypeEraserTransformer(emitContext *printer.EmitContext, compilerOptions return tx.newTransformer(tx.visit, emitContext) } +// Pushes a new child node onto the ancestor tracking stack, returning the grandparent node to be restored later via `popNode`. +func (tx *TypeEraserTransformer) pushNode(node *ast.Node) (grandparentNode *ast.Node) { + grandparentNode = tx.parentNode + tx.parentNode = tx.currentNode + tx.currentNode = node + return +} + +// Pops the last child node off the ancestor tracking stack, restoring the grandparent node. +func (tx *TypeEraserTransformer) popNode(grandparentNode *ast.Node) { + tx.currentNode = tx.parentNode + tx.parentNode = grandparentNode +} + func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node { // !!! TransformFlags were traditionally used here to skip over subtrees that contain no TypeScript syntax if ast.IsStatement(node) && ast.HasSyntacticModifier(node, ast.ModifierFlagsAmbient) { @@ -23,6 +39,9 @@ func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node { return nil } + grandparentNode := tx.pushNode(node) + defer tx.popNode(grandparentNode) + switch node.Kind { case // TypeScript accessibility and readonly modifiers are elided @@ -169,7 +188,12 @@ func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node { return nil } n := node.AsParameterDeclaration() - return tx.factory.UpdateParameterDeclaration(n, nil, n.DotDotDotToken, tx.visitor.VisitNode(n.Name()), nil, nil, tx.visitor.VisitNode(n.Initializer)) + // preserve parameter property modifiers to be handled by the runtime transformer + var modifiers *ast.ModifierList + if ast.IsParameterPropertyDeclaration(node, tx.parentNode) { + modifiers = extractModifiers(tx.emitContext, n.Modifiers(), ast.ModifierFlagsParameterPropertyModifier) + } + return tx.factory.UpdateParameterDeclaration(n, modifiers, n.DotDotDotToken, tx.visitor.VisitNode(n.Name()), nil, nil, tx.visitor.VisitNode(n.Initializer)) case ast.KindCallExpression: n := node.AsCallExpression()