Skip to content

Commit 2173a84

Browse files
committed
Parse and preserve parameter decorators
Extend parameter AST nodes to retain decorators, including explicit-this decorators on function signatures, without forcing a broad constructor API churn. Teach both parameter parsers to accept stacked decorators on regular, rest, explicit-this, constructor-property, function-type, and parenthesized-arrow parameters. Update the AST builder to serialize parameter decorators inline so parser fixtures can round-trip the new syntax faithfully. Add a focused parser fixture covering the preserved syntax surface before deferred validation is introduced.
1 parent be8e56d commit 2173a84

5 files changed

Lines changed: 156 additions & 40 deletions

File tree

src/ast.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,12 @@ export abstract class Node {
181181
name: IdentifierExpression,
182182
type: TypeNode,
183183
initializer: Expression | null,
184-
range: Range
184+
range: Range,
185+
decorators: DecoratorNode[] | null = null
185186
): ParameterNode {
186-
return new ParameterNode(parameterKind, name, type, initializer, range);
187+
let parameter = new ParameterNode(parameterKind, name, type, initializer, range);
188+
parameter.decorators = decorators;
189+
return parameter;
187190
}
188191

189192
// special
@@ -926,6 +929,9 @@ export class FunctionTypeNode extends TypeNode {
926929
) {
927930
super(NodeKind.FunctionType, isNullable, range);
928931
}
932+
933+
/** Decorators on an explicit `this` parameter, if any. */
934+
explicitThisDecorators: DecoratorNode[] | null = null;
929935
}
930936

931937
/** Represents a type parameter. */
@@ -971,6 +977,8 @@ export class ParameterNode extends Node {
971977
super(NodeKind.Parameter, range);
972978
}
973979

980+
/** Decorators, if any. */
981+
decorators: DecoratorNode[] | null = null;
974982
/** Implicit field declaration, if applicable. */
975983
implicitFieldDeclaration: FieldDeclaration | null = null;
976984
/** Common flags indicating specific traits. */

src/extra/ast.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ export class ASTBuilder {
427427
sb.push(isNullable ? "((" : "(");
428428
let explicitThisType = node.explicitThisType;
429429
if (explicitThisType) {
430+
this.serializeParameterDecorators(node.explicitThisDecorators);
430431
sb.push("this: ");
431432
this.visitTypeNode(explicitThisType);
432433
}
@@ -1153,6 +1154,7 @@ export class ASTBuilder {
11531154
let numParameters = parameters.length;
11541155
let explicitThisType = signature.explicitThisType;
11551156
if (explicitThisType) {
1157+
this.serializeParameterDecorators(signature.explicitThisDecorators);
11561158
sb.push("this: ");
11571159
this.visitTypeNode(explicitThisType);
11581160
}
@@ -1563,9 +1565,38 @@ export class ASTBuilder {
15631565
indent(sb, this.indentLevel);
15641566
}
15651567

1568+
serializeParameterDecorators(decorators: DecoratorNode[] | null): void {
1569+
if (decorators) {
1570+
for (let i = 0, k = decorators.length; i < k; ++i) {
1571+
this.serializeParameterDecorator(decorators[i]);
1572+
}
1573+
}
1574+
}
1575+
1576+
private serializeParameterDecorator(node: DecoratorNode): void {
1577+
let sb = this.sb;
1578+
sb.push("@");
1579+
this.visitNode(node.name);
1580+
let args = node.args;
1581+
if (args) {
1582+
sb.push("(");
1583+
let numArgs = args.length;
1584+
if (numArgs) {
1585+
this.visitNode(args[0]);
1586+
for (let i = 1; i < numArgs; ++i) {
1587+
sb.push(", ");
1588+
this.visitNode(args[i]);
1589+
}
1590+
}
1591+
sb.push(")");
1592+
}
1593+
sb.push(" ");
1594+
}
1595+
15661596
serializeParameter(node: ParameterNode): void {
15671597
let sb = this.sb;
15681598
let kind = node.parameterKind;
1599+
this.serializeParameterDecorators(node.decorators);
15691600
let implicitFieldDeclaration = node.implicitFieldDeclaration;
15701601
if (implicitFieldDeclaration) {
15711602
this.serializeAccessModifiers(implicitFieldDeclaration);

src/parser.ts

Lines changed: 93 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,7 @@ export class Parser extends DiagnosticEmitter {
709709
let startPos = tn.tokenPos;
710710
let parameters: ParameterNode[] | null = null;
711711
let thisType: NamedTypeNode | null = null;
712+
let thisDecorators: DecoratorNode[] | null = null;
712713
let isSignature: bool = false;
713714
let firstParamNameNoType: IdentifierExpression | null = null;
714715
let firstParamKind: ParameterKind = ParameterKind.Default;
@@ -723,6 +724,12 @@ export class Parser extends DiagnosticEmitter {
723724
do {
724725
let paramStart = -1;
725726
let kind = ParameterKind.Default;
727+
let decorators = this.parseParameterDecorators(tn);
728+
if (decorators) {
729+
paramStart = decorators[0].range.start;
730+
isSignature = true;
731+
tn.discard(state);
732+
}
726733
if (tn.skip(Token.Dot_Dot_Dot)) {
727734
paramStart = tn.tokenPos;
728735
isSignature = true;
@@ -745,6 +752,7 @@ export class Parser extends DiagnosticEmitter {
745752
return null;
746753
}
747754
thisType = <NamedTypeNode>type;
755+
thisDecorators = decorators;
748756
} else {
749757
tn.reset(state);
750758
this.tryParseSignatureIsSignature = false;
@@ -773,7 +781,7 @@ export class Parser extends DiagnosticEmitter {
773781
this.tryParseSignatureIsSignature = isSignature;
774782
return null;
775783
}
776-
let param = Node.createParameter(kind, name, type, null, tn.range(paramStart, tn.pos));
784+
let param = Node.createParameter(kind, name, type, null, tn.range(paramStart, tn.pos), decorators);
777785
if (!parameters) parameters = [ param ];
778786
else parameters.push(param);
779787
} else {
@@ -784,7 +792,7 @@ export class Parser extends DiagnosticEmitter {
784792
}
785793
}
786794
if (isSignature) {
787-
let param = Node.createParameter(kind, name, Node.createOmittedType(tn.range(tn.pos)), null, tn.range(paramStart, tn.pos));
795+
let param = Node.createParameter(kind, name, Node.createOmittedType(tn.range(tn.pos)), null, tn.range(paramStart, tn.pos), decorators);
788796
if (!parameters) parameters = [ param ];
789797
else parameters.push(param);
790798
this.error(
@@ -869,13 +877,15 @@ export class Parser extends DiagnosticEmitter {
869877

870878
if (!parameters) parameters = [];
871879

872-
return Node.createFunctionType(
880+
let functionType = Node.createFunctionType(
873881
parameters,
874882
returnType,
875883
thisType,
876884
false,
877885
tn.range(startPos, tn.pos)
878886
);
887+
functionType.explicitThisDecorators = thisDecorators;
888+
return functionType;
879889
}
880890

881891
// statements
@@ -924,6 +934,19 @@ export class Parser extends DiagnosticEmitter {
924934
return null;
925935
}
926936

937+
private parseParameterDecorators(
938+
tn: Tokenizer
939+
): DecoratorNode[] | null {
940+
let decorators: DecoratorNode[] | null = null;
941+
while (tn.skip(Token.At)) {
942+
let decorator = this.parseDecorator(tn);
943+
if (!decorator) break;
944+
if (!decorators) decorators = [decorator];
945+
else decorators.push(decorator);
946+
}
947+
return decorators;
948+
}
949+
927950
parseVariable(
928951
tn: Tokenizer,
929952
flags: CommonFlags,
@@ -1227,6 +1250,7 @@ export class Parser extends DiagnosticEmitter {
12271250
}
12281251

12291252
private parseParametersThis: NamedTypeNode | null = null;
1253+
private parseParametersThisDecorators: DecoratorNode[] | null = null;
12301254

12311255
parseParameters(
12321256
tn: Tokenizer,
@@ -1243,40 +1267,50 @@ export class Parser extends DiagnosticEmitter {
12431267

12441268
// check if there is a leading `this` parameter
12451269
this.parseParametersThis = null;
1246-
if (tn.skip(Token.This)) {
1247-
if (tn.skip(Token.Colon)) {
1248-
thisType = this.parseType(tn); // reports
1249-
if (!thisType) return null;
1250-
if (thisType.kind == NodeKind.NamedType) {
1251-
this.parseParametersThis = <NamedTypeNode>thisType;
1252-
} else {
1253-
this.error(
1254-
DiagnosticCode.Identifier_expected,
1255-
thisType.range
1256-
);
1257-
}
1258-
} else {
1259-
this.error(
1260-
DiagnosticCode._0_expected,
1261-
tn.range(), ":"
1262-
);
1263-
return null;
1264-
}
1265-
if (!tn.skip(Token.Comma)) {
1266-
if (tn.skip(Token.CloseParen)) {
1267-
return parameters;
1270+
this.parseParametersThisDecorators = null;
1271+
1272+
let first = true;
1273+
while (true) {
1274+
if (tn.skip(Token.CloseParen)) break;
1275+
1276+
let paramDecorators = this.parseParameterDecorators(tn);
1277+
1278+
if (first && tn.skip(Token.This)) {
1279+
if (tn.skip(Token.Colon)) {
1280+
thisType = this.parseType(tn); // reports
1281+
if (!thisType) return null;
1282+
if (thisType.kind == NodeKind.NamedType) {
1283+
this.parseParametersThis = <NamedTypeNode>thisType;
1284+
this.parseParametersThisDecorators = paramDecorators;
1285+
} else {
1286+
this.error(
1287+
DiagnosticCode.Identifier_expected,
1288+
thisType.range
1289+
);
1290+
}
12681291
} else {
12691292
this.error(
12701293
DiagnosticCode._0_expected,
1271-
tn.range(), ")"
1294+
tn.range(), ":"
12721295
);
12731296
return null;
12741297
}
1298+
first = false;
1299+
if (!tn.skip(Token.Comma)) {
1300+
if (tn.skip(Token.CloseParen)) {
1301+
break;
1302+
} else {
1303+
this.error(
1304+
DiagnosticCode._0_expected,
1305+
tn.range(), ")"
1306+
);
1307+
return null;
1308+
}
1309+
}
1310+
continue;
12751311
}
1276-
}
12771312

1278-
while (!tn.skip(Token.CloseParen)) {
1279-
let param = this.parseParameter(tn, isConstructor); // reports
1313+
let param = this.parseParameter(tn, isConstructor, paramDecorators); // reports
12801314
if (!param) return null;
12811315
if (seenRest && !reportedRest) {
12821316
this.error(
@@ -1305,6 +1339,7 @@ export class Parser extends DiagnosticEmitter {
13051339
}
13061340
}
13071341
parameters.push(param);
1342+
first = false;
13081343
if (!tn.skip(Token.Comma)) {
13091344
if (tn.skip(Token.CloseParen)) {
13101345
break;
@@ -1322,24 +1357,27 @@ export class Parser extends DiagnosticEmitter {
13221357

13231358
parseParameter(
13241359
tn: Tokenizer,
1325-
isConstructor: bool = false
1360+
isConstructor: bool = false,
1361+
decorators: DecoratorNode[] | null = null
13261362
): ParameterNode | null {
13271363

13281364
// before: ('public' | 'private' | 'protected' | '...')? Identifier '?'? (':' Type)? ('=' Expression)?
13291365

13301366
let isRest = false;
13311367
let isOptional = false;
1332-
let startRange: Range | null = null;
1368+
let startRange: Range | null = decorators
1369+
? decorators[0].range
1370+
: null;
13331371
let accessFlags: CommonFlags = CommonFlags.None;
13341372
if (isConstructor) {
13351373
if (tn.skip(Token.Public)) {
1336-
startRange = tn.range();
1374+
if (!startRange) startRange = tn.range();
13371375
accessFlags |= CommonFlags.Public;
13381376
} else if (tn.skip(Token.Protected)) {
1339-
startRange = tn.range();
1377+
if (!startRange) startRange = tn.range();
13401378
accessFlags |= CommonFlags.Protected;
13411379
} else if (tn.skip(Token.Private)) {
1342-
startRange = tn.range();
1380+
if (!startRange) startRange = tn.range();
13431381
accessFlags |= CommonFlags.Private;
13441382
}
13451383
if (tn.peek() == Token.Readonly) {
@@ -1361,12 +1399,12 @@ export class Parser extends DiagnosticEmitter {
13611399
tn.range()
13621400
);
13631401
} else {
1364-
startRange = tn.range();
1402+
if (!startRange) startRange = tn.range();
13651403
}
13661404
isRest = true;
13671405
}
13681406
if (tn.skipIdentifier()) {
1369-
if (!isRest) startRange = tn.range();
1407+
if (!isRest && !startRange) startRange = tn.range();
13701408
let identifier = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
13711409
let type: TypeNode | null = null;
13721410
if (isOptional = tn.skip(Token.Question)) {
@@ -1411,7 +1449,8 @@ export class Parser extends DiagnosticEmitter {
14111449
identifier,
14121450
type,
14131451
initializer,
1414-
Range.join(assert(startRange), tn.range())
1452+
Range.join(assert(startRange), tn.range()),
1453+
decorators
14151454
);
14161455
param.flags |= accessFlags;
14171456
return param;
@@ -1523,6 +1562,7 @@ export class Parser extends DiagnosticEmitter {
15231562
false,
15241563
tn.range(signatureStart, tn.pos)
15251564
);
1565+
signature.explicitThisDecorators = this.parseParametersThisDecorators;
15261566

15271567
let body: Statement | null = null;
15281568
if (tn.skip(Token.OpenBrace)) {
@@ -1597,14 +1637,24 @@ export class Parser extends DiagnosticEmitter {
15971637
let parameters = this.parseParameters(tn);
15981638
if (!parameters) return null;
15991639

1600-
return this.parseFunctionExpressionCommon(tn, name, parameters, this.parseParametersThis, arrowKind, startPos, signatureStart);
1640+
return this.parseFunctionExpressionCommon(
1641+
tn,
1642+
name,
1643+
parameters,
1644+
this.parseParametersThis,
1645+
this.parseParametersThisDecorators,
1646+
arrowKind,
1647+
startPos,
1648+
signatureStart
1649+
);
16011650
}
16021651

16031652
private parseFunctionExpressionCommon(
16041653
tn: Tokenizer,
16051654
name: IdentifierExpression,
16061655
parameters: ParameterNode[],
16071656
explicitThis: NamedTypeNode | null,
1657+
explicitThisDecorators: DecoratorNode[] | null,
16081658
arrowKind: ArrowKind,
16091659
startPos: i32 = -1,
16101660
signatureStart: i32 = -1
@@ -1637,6 +1687,7 @@ export class Parser extends DiagnosticEmitter {
16371687
false,
16381688
tn.range(signatureStart, tn.pos)
16391689
);
1690+
signature.explicitThisDecorators = explicitThisDecorators;
16401691

16411692
let body: Statement | null = null;
16421693
if (arrowKind) {
@@ -2282,6 +2333,7 @@ export class Parser extends DiagnosticEmitter {
22822333
false,
22832334
tn.range(signatureStart, tn.pos)
22842335
);
2336+
signature.explicitThisDecorators = this.parseParametersThisDecorators;
22852337

22862338
let body: Statement | null = null;
22872339
if (tn.skip(Token.OpenBrace)) {
@@ -3729,6 +3781,7 @@ export class Parser extends DiagnosticEmitter {
37293781
Node.createEmptyIdentifierExpression(tn.range(startPos)),
37303782
[],
37313783
null,
3784+
null,
37323785
ArrowKind.Parenthesized
37333786
);
37343787
}
@@ -3738,6 +3791,7 @@ export class Parser extends DiagnosticEmitter {
37383791
switch (tn.next(IdentifierHandling.Prefer)) {
37393792

37403793
// function expression
3794+
case Token.At:
37413795
case Token.Dot_Dot_Dot: {
37423796
tn.reset(state);
37433797
return this.parseFunctionExpression(tn);
@@ -3930,6 +3984,7 @@ export class Parser extends DiagnosticEmitter {
39303984
)
39313985
],
39323986
null,
3987+
null,
39333988
ArrowKind.Single,
39343989
startPos
39353990
);

0 commit comments

Comments
 (0)