Skip to content

Commit

Permalink
Implement optional trailing commas
Browse files Browse the repository at this point in the history
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 microsoft/TypeScript#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.
  • Loading branch information
alangpierce committed May 6, 2018
1 parent 25a1f62 commit 87db539
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 83 deletions.
1 change: 1 addition & 0 deletions src/extra/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ");
Expand Down
180 changes: 97 additions & 83 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,18 +819,20 @@ export class Parser extends DiagnosticEmitter {
return null;
}
var members = new Array<EnumValueDeclaration>();
if (!tn.skip(Token.CLOSEBRACE)) {
do {
let member = this.parseEnumValue(tn, CommonFlags.NONE);
if (!member) return null;
members.push(<EnumValueDeclaration>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(<EnumValueDeclaration>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(
Expand Down Expand Up @@ -900,17 +902,21 @@ export class Parser extends DiagnosticEmitter {

var typeParameters = new Array<TypeParameterNode>();
if (!tn.skip(Token.GREATERTHAN)) {
do {
while (!tn.skip(Token.GREATERTHAN)) {
let typeParameter = this.parseTypeParameter(tn);
if (!typeParameter) return null;
typeParameters.push(<TypeParameterNode>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(
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -2877,23 +2885,24 @@ export class Parser extends DiagnosticEmitter {
// ArrayLiteralExpression
case Token.OPENBRACKET: {
let elementExpressions = new Array<Expression | null>();
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));
Expand Down Expand Up @@ -2979,6 +2988,9 @@ export class Parser extends DiagnosticEmitter {
if (!tn.skip(Token.LESSTHAN)) return null;
var typeArguments = new Array<CommonTypeNode>();
do {
if (tn.peek() === Token.GREATERTHAN) {
break;
}
let type = this.parseType(tn, true, true);
if (!type) {
tn.reset(state);
Expand All @@ -3000,18 +3012,20 @@ export class Parser extends DiagnosticEmitter {
// at '(': (Expression (',' Expression)*)? ')'

var args = new Array<Expression>();
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;
Expand Down
33 changes: 33 additions & 0 deletions tests/parser/trailing-commas.ts
Original file line number Diff line number Diff line change
@@ -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<i8> = [
1,
2,
];
parameterized<
i8,
// @ts-ignore: Waiting on https://github.com/Microsoft/TypeScript/issues/21984
i32,
>(0, 0);
return add(
1,
2,
);
}
13 changes: 13 additions & 0 deletions tests/parser/trailing-commas.ts.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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<i8> = [1, 2];
parameterized<i8, i32>(0, 0);
return add(1, 2);
}

0 comments on commit 87db539

Please sign in to comment.