Skip to content

Commit 3ee552c

Browse files
committed
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 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.
1 parent 25a1f62 commit 3ee552c

File tree

4 files changed

+144
-83
lines changed

4 files changed

+144
-83
lines changed

src/extra/ast.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ export class ASTBuilder {
967967
this.serializeDecorator(decorators[i]);
968968
}
969969
}
970+
this.serializeExternalModifiers(node);
970971
this.serializeAccessModifiers(node);
971972
if (node.name.text.length) {
972973
sb.push("function ");

src/parser.ts

Lines changed: 97 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -819,18 +819,20 @@ export class Parser extends DiagnosticEmitter {
819819
return null;
820820
}
821821
var members = new Array<EnumValueDeclaration>();
822-
if (!tn.skip(Token.CLOSEBRACE)) {
823-
do {
824-
let member = this.parseEnumValue(tn, CommonFlags.NONE);
825-
if (!member) return null;
826-
members.push(<EnumValueDeclaration>member);
827-
} while (tn.skip(Token.COMMA));
828-
if (!tn.skip(Token.CLOSEBRACE)) {
829-
this.error(
830-
DiagnosticCode._0_expected,
831-
tn.range(), "}"
832-
);
833-
return null;
822+
while (!tn.skip(Token.CLOSEBRACE)) {
823+
let member = this.parseEnumValue(tn, CommonFlags.NONE);
824+
if (!member) return null;
825+
members.push(<EnumValueDeclaration>member);
826+
if (!tn.skip(Token.COMMA)) {
827+
if (tn.skip(Token.CLOSEBRACE)) {
828+
break;
829+
} else {
830+
this.error(
831+
DiagnosticCode._0_expected,
832+
tn.range(), "}"
833+
);
834+
return null;
835+
}
834836
}
835837
}
836838
var ret = Node.createEnumDeclaration(
@@ -900,17 +902,21 @@ export class Parser extends DiagnosticEmitter {
900902

901903
var typeParameters = new Array<TypeParameterNode>();
902904
if (!tn.skip(Token.GREATERTHAN)) {
903-
do {
905+
while (!tn.skip(Token.GREATERTHAN)) {
904906
let typeParameter = this.parseTypeParameter(tn);
905907
if (!typeParameter) return null;
906908
typeParameters.push(<TypeParameterNode>typeParameter);
907-
} while (tn.skip(Token.COMMA));
908-
if (!tn.skip(Token.GREATERTHAN)) {
909-
this.error(
910-
DiagnosticCode._0_expected,
911-
tn.range(), ">"
912-
);
913-
return null;
909+
if (!tn.skip(Token.COMMA)) {
910+
if (tn.skip(Token.GREATERTHAN)) {
911+
break;
912+
} else {
913+
this.error(
914+
DiagnosticCode._0_expected,
915+
tn.range(), ">"
916+
);
917+
return null;
918+
}
919+
}
914920
}
915921
} else {
916922
this.error(
@@ -971,45 +977,47 @@ export class Parser extends DiagnosticEmitter {
971977
var seenOptional = false;
972978
var reportedRest = false;
973979

974-
if (tn.peek() != Token.CLOSEPAREN) {
975-
do {
976-
let param = this.parseParameter(tn, isConstructor);
977-
if (!param) return null;
978-
if (seenRest && !reportedRest) {
980+
while (!tn.skip(Token.CLOSEPAREN)) {
981+
let param = this.parseParameter(tn, isConstructor);
982+
if (!param) return null;
983+
if (seenRest && !reportedRest) {
984+
this.error(
985+
DiagnosticCode.A_rest_parameter_must_be_last_in_a_parameter_list,
986+
seenRest.name.range
987+
);
988+
reportedRest = true;
989+
}
990+
switch (param.parameterKind) {
991+
default: {
992+
if (seenOptional) {
993+
this.error(
994+
DiagnosticCode.A_required_parameter_cannot_follow_an_optional_parameter,
995+
param.name.range
996+
);
997+
}
998+
break;
999+
}
1000+
case ParameterKind.OPTIONAL: {
1001+
seenOptional = true;
1002+
break;
1003+
}
1004+
case ParameterKind.REST: {
1005+
seenRest = param;
1006+
break;
1007+
}
1008+
}
1009+
parameters.push(param);
1010+
if (!tn.skip(Token.COMMA)) {
1011+
if (tn.skip(Token.CLOSEPAREN)) {
1012+
break;
1013+
} else {
9791014
this.error(
980-
DiagnosticCode.A_rest_parameter_must_be_last_in_a_parameter_list,
981-
seenRest.name.range
1015+
DiagnosticCode._0_expected,
1016+
tn.range(), ")"
9821017
);
983-
reportedRest = true;
984-
}
985-
switch (param.parameterKind) {
986-
default: {
987-
if (seenOptional) {
988-
this.error(
989-
DiagnosticCode.A_required_parameter_cannot_follow_an_optional_parameter,
990-
param.name.range
991-
);
992-
}
993-
break;
994-
}
995-
case ParameterKind.OPTIONAL: {
996-
seenOptional = true;
997-
break;
998-
}
999-
case ParameterKind.REST: {
1000-
seenRest = param;
1001-
break;
1002-
}
1018+
return null;
10031019
}
1004-
parameters.push(param);
1005-
} while (tn.skip(Token.COMMA));
1006-
}
1007-
if (!tn.skip(Token.CLOSEPAREN)) {
1008-
this.error(
1009-
DiagnosticCode._0_expected,
1010-
tn.range(), ")"
1011-
);
1012-
return null;
1020+
}
10131021
}
10141022
return parameters;
10151023
}
@@ -2877,23 +2885,24 @@ export class Parser extends DiagnosticEmitter {
28772885
// ArrayLiteralExpression
28782886
case Token.OPENBRACKET: {
28792887
let elementExpressions = new Array<Expression | null>();
2880-
if (!tn.skip(Token.CLOSEBRACKET)) {
2881-
do {
2882-
if (tn.peek() == Token.COMMA) {
2883-
expr = null; // omitted
2888+
while (!tn.skip(Token.CLOSEBRACKET)) {
2889+
if (tn.peek() == Token.COMMA) {
2890+
expr = null; // omitted
2891+
} else {
2892+
expr = this.parseExpression(tn, Precedence.COMMA + 1);
2893+
if (!expr) return null;
2894+
}
2895+
elementExpressions.push(expr);
2896+
if (!tn.skip(Token.COMMA)) {
2897+
if (tn.skip(Token.CLOSEBRACKET)) {
2898+
break;
28842899
} else {
2885-
expr = this.parseExpression(tn, Precedence.COMMA + 1);
2886-
if (!expr) return null;
2900+
this.error(
2901+
DiagnosticCode._0_expected,
2902+
tn.range(), "]"
2903+
);
2904+
return null;
28872905
}
2888-
elementExpressions.push(expr);
2889-
if (tn.peek() == Token.CLOSEBRACKET) break;
2890-
} while (tn.skip(Token.COMMA));
2891-
if (!tn.skip(Token.CLOSEBRACKET)) {
2892-
this.error(
2893-
DiagnosticCode._0_expected,
2894-
tn.range(), "]"
2895-
);
2896-
return null;
28972906
}
28982907
}
28992908
return Node.createArrayLiteralExpression(elementExpressions, tn.range(startPos, tn.pos));
@@ -2979,6 +2988,9 @@ export class Parser extends DiagnosticEmitter {
29792988
if (!tn.skip(Token.LESSTHAN)) return null;
29802989
var typeArguments = new Array<CommonTypeNode>();
29812990
do {
2991+
if (tn.peek() === Token.GREATERTHAN) {
2992+
break;
2993+
}
29822994
let type = this.parseType(tn, true, true);
29832995
if (!type) {
29842996
tn.reset(state);
@@ -3000,18 +3012,20 @@ export class Parser extends DiagnosticEmitter {
30003012
// at '(': (Expression (',' Expression)*)? ')'
30013013

30023014
var args = new Array<Expression>();
3003-
if (!tn.skip(Token.CLOSEPAREN)) {
3004-
do {
3005-
let expr = this.parseExpression(tn, Precedence.COMMA + 1);
3006-
if (!expr) return null;
3007-
args.push(expr);
3008-
} while (tn.skip(Token.COMMA));
3009-
if (!tn.skip(Token.CLOSEPAREN)) {
3010-
this.error(
3011-
DiagnosticCode._0_expected,
3012-
tn.range(), ")"
3013-
);
3014-
return null;
3015+
while (!tn.skip(Token.CLOSEPAREN)) {
3016+
let expr = this.parseExpression(tn, Precedence.COMMA + 1);
3017+
if (!expr) return null;
3018+
args.push(expr);
3019+
if (!tn.skip(Token.COMMA)) {
3020+
if (tn.skip(Token.CLOSEPAREN)) {
3021+
break;
3022+
} else {
3023+
this.error(
3024+
DiagnosticCode._0_expected,
3025+
tn.range(), ")"
3026+
);
3027+
return null;
3028+
}
30153029
}
30163030
}
30173031
return args;

tests/parser/trailing-commas.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
enum Foo {
2+
A,
3+
B,
4+
}
5+
6+
function add(
7+
x: i32,
8+
y: i32,
9+
): i32 {
10+
return x + y;
11+
}
12+
13+
function parameterized<
14+
A,
15+
B,
16+
>(a: A, b: B): void {
17+
}
18+
19+
export function compute(): i32 {
20+
const arr: Array<i8> = [
21+
1,
22+
2,
23+
];
24+
parameterized<
25+
i8,
26+
// @ts-ignore: Waiting on https://github.com/Microsoft/TypeScript/issues/21984
27+
i32,
28+
>(0, 0);
29+
return add(
30+
1,
31+
2,
32+
);
33+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
enum Foo {
2+
A,
3+
B
4+
}
5+
function add(x: i32, y: i32): i32 {
6+
return x + y;
7+
}
8+
function parameterized<A, B>(a: A, b: B): void {}
9+
export function compute(): i32 {
10+
const arr: Array<i8> = [1, 2];
11+
parameterized<i8, i32>(0, 0);
12+
return add(1, 2);
13+
}

0 commit comments

Comments
 (0)