From 87db539ea687e59fc90ee27abcc53cc29733d91b Mon Sep 17 00:00:00 2001 From: Alan Pierce Date: Sun, 6 May 2018 14:57:30 -0700 Subject: [PATCH] Implement optional trailing commas Trailing commas are allowed in these situations in JS/TS: * Array literals * Argument lists * Parameter lists * Enums * Type parameter lists * Type argument lists (Technically filed as https://github.com/Microsoft/TypeScript/issues/21984 ) They're also allowed in these cases not (yet) supported by AssemblyScript: * Object literals * Array destructures * Object destructures All of these cases had similar-looking code, which needed to be tweaked to handle the possibility of a comma before the close-delimiter. Type arguments were a little different because they take a backtracking approach. I also fixed a missing case in the AST printer: `export function` wasn't being printed right. --- src/extra/ast.ts | 1 + src/parser.ts | 180 +++++++++++---------- tests/parser/trailing-commas.ts | 33 ++++ tests/parser/trailing-commas.ts.fixture.ts | 13 ++ 4 files changed, 144 insertions(+), 83 deletions(-) create mode 100644 tests/parser/trailing-commas.ts create mode 100644 tests/parser/trailing-commas.ts.fixture.ts diff --git a/src/extra/ast.ts b/src/extra/ast.ts index 97cdc0bccc..a41a08f8bd 100644 --- a/src/extra/ast.ts +++ b/src/extra/ast.ts @@ -967,6 +967,7 @@ export class ASTBuilder { this.serializeDecorator(decorators[i]); } } + this.serializeExternalModifiers(node); this.serializeAccessModifiers(node); if (node.name.text.length) { sb.push("function "); diff --git a/src/parser.ts b/src/parser.ts index b833cb682b..a3c58c25bd 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -819,18 +819,20 @@ export class Parser extends DiagnosticEmitter { return null; } var members = new Array(); - if (!tn.skip(Token.CLOSEBRACE)) { - do { - let member = this.parseEnumValue(tn, CommonFlags.NONE); - if (!member) return null; - members.push(member); - } while (tn.skip(Token.COMMA)); - if (!tn.skip(Token.CLOSEBRACE)) { - this.error( - DiagnosticCode._0_expected, - tn.range(), "}" - ); - return null; + while (!tn.skip(Token.CLOSEBRACE)) { + let member = this.parseEnumValue(tn, CommonFlags.NONE); + if (!member) return null; + members.push(member); + if (!tn.skip(Token.COMMA)) { + if (tn.skip(Token.CLOSEBRACE)) { + break; + } else { + this.error( + DiagnosticCode._0_expected, + tn.range(), "}" + ); + return null; + } } } var ret = Node.createEnumDeclaration( @@ -900,17 +902,21 @@ export class Parser extends DiagnosticEmitter { var typeParameters = new Array(); if (!tn.skip(Token.GREATERTHAN)) { - do { + while (!tn.skip(Token.GREATERTHAN)) { let typeParameter = this.parseTypeParameter(tn); if (!typeParameter) return null; typeParameters.push(typeParameter); - } while (tn.skip(Token.COMMA)); - if (!tn.skip(Token.GREATERTHAN)) { - this.error( - DiagnosticCode._0_expected, - tn.range(), ">" - ); - return null; + if (!tn.skip(Token.COMMA)) { + if (tn.skip(Token.GREATERTHAN)) { + break; + } else { + this.error( + DiagnosticCode._0_expected, + tn.range(), ">" + ); + return null; + } + } } } else { this.error( @@ -971,45 +977,47 @@ export class Parser extends DiagnosticEmitter { var seenOptional = false; var reportedRest = false; - if (tn.peek() != Token.CLOSEPAREN) { - do { - let param = this.parseParameter(tn, isConstructor); - if (!param) return null; - if (seenRest && !reportedRest) { + while (!tn.skip(Token.CLOSEPAREN)) { + let param = this.parseParameter(tn, isConstructor); + if (!param) return null; + if (seenRest && !reportedRest) { + this.error( + DiagnosticCode.A_rest_parameter_must_be_last_in_a_parameter_list, + seenRest.name.range + ); + reportedRest = true; + } + switch (param.parameterKind) { + default: { + if (seenOptional) { + this.error( + DiagnosticCode.A_required_parameter_cannot_follow_an_optional_parameter, + param.name.range + ); + } + break; + } + case ParameterKind.OPTIONAL: { + seenOptional = true; + break; + } + case ParameterKind.REST: { + seenRest = param; + break; + } + } + parameters.push(param); + if (!tn.skip(Token.COMMA)) { + if (tn.skip(Token.CLOSEPAREN)) { + break; + } else { this.error( - DiagnosticCode.A_rest_parameter_must_be_last_in_a_parameter_list, - seenRest.name.range + DiagnosticCode._0_expected, + tn.range(), ")" ); - reportedRest = true; - } - switch (param.parameterKind) { - default: { - if (seenOptional) { - this.error( - DiagnosticCode.A_required_parameter_cannot_follow_an_optional_parameter, - param.name.range - ); - } - break; - } - case ParameterKind.OPTIONAL: { - seenOptional = true; - break; - } - case ParameterKind.REST: { - seenRest = param; - break; - } + return null; } - parameters.push(param); - } while (tn.skip(Token.COMMA)); - } - if (!tn.skip(Token.CLOSEPAREN)) { - this.error( - DiagnosticCode._0_expected, - tn.range(), ")" - ); - return null; + } } return parameters; } @@ -2877,23 +2885,24 @@ export class Parser extends DiagnosticEmitter { // ArrayLiteralExpression case Token.OPENBRACKET: { let elementExpressions = new Array(); - if (!tn.skip(Token.CLOSEBRACKET)) { - do { - if (tn.peek() == Token.COMMA) { - expr = null; // omitted + while (!tn.skip(Token.CLOSEBRACKET)) { + if (tn.peek() == Token.COMMA) { + expr = null; // omitted + } else { + expr = this.parseExpression(tn, Precedence.COMMA + 1); + if (!expr) return null; + } + elementExpressions.push(expr); + if (!tn.skip(Token.COMMA)) { + if (tn.skip(Token.CLOSEBRACKET)) { + break; } else { - expr = this.parseExpression(tn, Precedence.COMMA + 1); - if (!expr) return null; + this.error( + DiagnosticCode._0_expected, + tn.range(), "]" + ); + return null; } - elementExpressions.push(expr); - if (tn.peek() == Token.CLOSEBRACKET) break; - } while (tn.skip(Token.COMMA)); - if (!tn.skip(Token.CLOSEBRACKET)) { - this.error( - DiagnosticCode._0_expected, - tn.range(), "]" - ); - return null; } } return Node.createArrayLiteralExpression(elementExpressions, tn.range(startPos, tn.pos)); @@ -2979,6 +2988,9 @@ export class Parser extends DiagnosticEmitter { if (!tn.skip(Token.LESSTHAN)) return null; var typeArguments = new Array(); do { + if (tn.peek() === Token.GREATERTHAN) { + break; + } let type = this.parseType(tn, true, true); if (!type) { tn.reset(state); @@ -3000,18 +3012,20 @@ export class Parser extends DiagnosticEmitter { // at '(': (Expression (',' Expression)*)? ')' var args = new Array(); - if (!tn.skip(Token.CLOSEPAREN)) { - do { - let expr = this.parseExpression(tn, Precedence.COMMA + 1); - if (!expr) return null; - args.push(expr); - } while (tn.skip(Token.COMMA)); - if (!tn.skip(Token.CLOSEPAREN)) { - this.error( - DiagnosticCode._0_expected, - tn.range(), ")" - ); - return null; + while (!tn.skip(Token.CLOSEPAREN)) { + let expr = this.parseExpression(tn, Precedence.COMMA + 1); + if (!expr) return null; + args.push(expr); + if (!tn.skip(Token.COMMA)) { + if (tn.skip(Token.CLOSEPAREN)) { + break; + } else { + this.error( + DiagnosticCode._0_expected, + tn.range(), ")" + ); + return null; + } } } return args; diff --git a/tests/parser/trailing-commas.ts b/tests/parser/trailing-commas.ts new file mode 100644 index 0000000000..5260b2dc17 --- /dev/null +++ b/tests/parser/trailing-commas.ts @@ -0,0 +1,33 @@ +enum Foo { + A, + B, +} + +function add( + x: i32, + y: i32, +): i32 { + return x + y; +} + +function parameterized< + A, + B, + >(a: A, b: B): void { +} + +export function compute(): i32 { + const arr: Array = [ + 1, + 2, + ]; + parameterized< + i8, + // @ts-ignore: Waiting on https://github.com/Microsoft/TypeScript/issues/21984 + i32, + >(0, 0); + return add( + 1, + 2, + ); +} diff --git a/tests/parser/trailing-commas.ts.fixture.ts b/tests/parser/trailing-commas.ts.fixture.ts new file mode 100644 index 0000000000..9c3ce3c4a5 --- /dev/null +++ b/tests/parser/trailing-commas.ts.fixture.ts @@ -0,0 +1,13 @@ +enum Foo { + A, + B +} +function add(x: i32, y: i32): i32 { + return x + y; +} +function parameterized(a: A, b: B): void {} +export function compute(): i32 { + const arr: Array = [1, 2]; + parameterized(0, 0); + return add(1, 2); +}