diff --git a/src/parser/traverser/lval.ts b/src/parser/traverser/lval.ts index f7508803..22fdd54c 100644 --- a/src/parser/traverser/lval.ts +++ b/src/parser/traverser/lval.ts @@ -86,6 +86,7 @@ export function parseBindingList( isBlockScope: boolean, allowEmpty: boolean = false, allowModifiers: boolean = false, + contextId: number = 0, ): void { let first = true; @@ -97,6 +98,7 @@ export function parseBindingList( first = false; } else { expect(tt.comma); + state.tokens[state.tokens.length - 1].contextId = contextId; // After a "this" type in TypeScript, we need to set the following comma (if any) to also be // a type token so that it will be removed. if (!hasRemovedComma && state.tokens[firstItemTokenIndex].isType) { diff --git a/src/parser/traverser/statement.ts b/src/parser/traverser/statement.ts index 7496d8b5..1ebb9156 100644 --- a/src/parser/traverser/statement.ts +++ b/src/parser/traverser/statement.ts @@ -614,7 +614,13 @@ export function parseFunctionParams( if (funcContextId) { state.tokens[state.tokens.length - 1].contextId = funcContextId; } - parseBindingList(tt.parenR, false /* isBlockScope */, false /* allowEmpty */, allowModifiers); + parseBindingList( + tt.parenR, + false /* isBlockScope */, + false /* allowEmpty */, + allowModifiers, + funcContextId, + ); if (funcContextId) { state.tokens[state.tokens.length - 1].contextId = funcContextId; } diff --git a/src/util/getClassInfo.ts b/src/util/getClassInfo.ts index f361b012..7f59210e 100644 --- a/src/util/getClassInfo.ts +++ b/src/util/getClassInfo.ts @@ -197,22 +197,27 @@ function processConstructor( if (constructorContextId == null) { throw new Error("Expected context ID on open-paren starting constructor params."); } - tokens.nextToken(); // Advance through parameters looking for access modifiers. while (!tokens.matchesContextIdAndLabel(tt.parenR, constructorContextId)) { - if (isAccessModifier(tokens.currentToken())) { + if (tokens.currentToken().contextId === constructorContextId) { + // Current token is an open paren or comma just before a param, so check + // that param for access modifiers. tokens.nextToken(); - while (isAccessModifier(tokens.currentToken())) { + if (isAccessModifier(tokens.currentToken())) { tokens.nextToken(); + while (isAccessModifier(tokens.currentToken())) { + tokens.nextToken(); + } + const token = tokens.currentToken(); + if (token.type !== tt.name) { + throw new Error("Expected identifier after access modifiers in constructor arg."); + } + const name = tokens.identifierNameForToken(token); + constructorInitializerStatements.push(`this.${name} = ${name}`); } - const token = tokens.currentToken(); - if (token.type !== tt.name) { - throw new Error("Expected identifier after access modifiers in constructor arg."); - } - const name = tokens.identifierNameForToken(token); - constructorInitializerStatements.push(`this.${name} = ${name}`); + } else { + tokens.nextToken(); } - tokens.nextToken(); } // ) tokens.nextToken(); diff --git a/test/typescript-test.ts b/test/typescript-test.ts index f68befb7..85a8cde6 100644 --- a/test/typescript-test.ts +++ b/test/typescript-test.ts @@ -1974,4 +1974,34 @@ describe("typescript transform", () => { `, ); }); + + it("properly handles default args in constructors", () => { + assertTypeScriptResult( + ` + class Foo { + constructor(p = -1) {} + } + `, + `"use strict"; + class Foo { + constructor(p = -1) {} + } + `, + ); + }); + + it("properly emits assignments with multiple constructor initializers", () => { + assertTypeScriptResult( + ` + class Foo { + constructor(a: number, readonly b: number, private c: number) {} + } + `, + `"use strict"; + class Foo { + constructor(a, b, c) {;this.b = b;this.c = c;} + } + `, + ); + }); });