Skip to content

Commit

Permalink
Fix crash when access modifiers are used in constructor param defaults (
Browse files Browse the repository at this point in the history
#531)

Fixes #522

Previously, the logic to compile TS constructor param assignments walked through
all tokens in the constructor looking for access modifiers, but this can cause
problems if default values include access modifiers. Now, we mark commas
associated with the constructor and only emit assignments when we see an
open-paren or comma followed by an access modifier.
  • Loading branch information
alangpierce authored May 10, 2020
1 parent 9a67974 commit 0a8413a
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/parser/traverser/lval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export function parseBindingList(
isBlockScope: boolean,
allowEmpty: boolean = false,
allowModifiers: boolean = false,
contextId: number = 0,
): void {
let first = true;

Expand All @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion src/parser/traverser/statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
25 changes: 15 additions & 10 deletions src/util/getClassInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
30 changes: 30 additions & 0 deletions test/typescript-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;}
}
`,
);
});
});

0 comments on commit 0a8413a

Please sign in to comment.