Skip to content

Commit

Permalink
Refactor decorators parsing, attach decorators after comments
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Oct 25, 2022
1 parent 2b1184c commit 0a7c85d
Show file tree
Hide file tree
Showing 17 changed files with 727 additions and 259 deletions.
61 changes: 0 additions & 61 deletions packages/babel-parser/src/parser/comments.ts
Expand Up @@ -289,65 +289,4 @@ export default class CommentsParser extends BaseParser {
}
}
}

/*
* Used to finalize the leading comments of a node, even if they could
* be attached as leading comments to a parent node.
*
* In case of comments followed by a decorator followed by `export`,
* we manually attach them to the decorator node rather than to the
* class declaration node. This is because the class location range
* overlaps with `export`, and this causes confusing comments behavior:
*
*/ // /* comment */ @dec export class Foo {}
/*
* See [takeLeadingCommentsAfterLastToken] for the hanling of comments
* after `export`.
*/
forceFinalizeLeadingComments(node: Node): void {
const { commentStack } = this.state;
for (let i = commentStack.length - 1; i >= 0; i--) {
const commentWS = commentStack[i];
if (commentWS.trailingNode === node) {
this.finalizeComment(commentWS);
commentStack.splice(i, 1);
} else if (commentWS.end < node.start) {
break;
}
}
}

/*
* Used to take the comments after the last token as leading
* comments of the given node, even if their start position
* is after the start position of the node.
*
* In case of decorators followed by `export`, we manually mark the
* comments between `export` and `class` as leading comments of the
* class declaration node.
* This is needed because the class location range overlaps with
* `export`, and thus they would be marked as inner comments of
* the class declaration:
*
*/ // @dec export /* comment */ class Foo {}
/*
* See [forceFinalizeLeadingComments] for the handling of comments
* before decorators.
*/
takeLeadingCommentsAfterLastToken(node: Undone<Node>): void {
const {
start,
lastTokEndLoc: { index: lastTokEnd },
} = this.state;

const { commentStack } = this.state;
for (let i = commentStack.length - 1; i >= 0; i--) {
const commentWS = commentStack[i];
if (commentWS.start >= lastTokEnd && commentWS.end <= start) {
commentWS.trailingNode = node as Node;
} else if (commentWS.end < lastTokEnd) {
break;
}
}
}
}
11 changes: 6 additions & 5 deletions packages/babel-parser/src/parser/expression.ts
Expand Up @@ -100,7 +100,6 @@ export default abstract class ExpressionParser extends LValParser {
node: N.Function,
allowModifiers?: boolean,
): void;
abstract takeDecorators(node: N.HasDecorators): void;
abstract parseBlockOrModuleBlockBody(
body: N.Statement[],
directives: N.Directive[] | null | undefined,
Expand Down Expand Up @@ -1102,6 +1101,7 @@ export default abstract class ExpressionParser extends LValParser {
refExpressionErrors?: ExpressionErrors | null,
): N.Expression {
let node;
let decorators: N.Decorator[] | null = null;

const { type } = this.state;
switch (type) {
Expand Down Expand Up @@ -1198,12 +1198,13 @@ export default abstract class ExpressionParser extends LValParser {
return this.parseFunctionOrFunctionSent();

case tt.at:
this.parseDecorators();
decorators = this.parseDecorators();
// fall through
case tt._class:
node = this.startNode<N.Class>();
this.takeDecorators(node);
return this.parseClass(node, false);
return this.maybeTakeDecorators(
decorators,
this.parseClass(this.startNode(), false),
);

case tt._new:
return this.parseNewOrNewTarget();
Expand Down
130 changes: 75 additions & 55 deletions packages/babel-parser/src/parser/statement.ts
Expand Up @@ -340,16 +340,19 @@ export default abstract class StatementParser extends ExpressionParser {
context?: string | null,
topLevel?: boolean,
): N.Statement {
let decorators: N.Decorator[] | null = null;

if (this.match(tt.at)) {
this.parseDecorators(true);
decorators = this.parseDecorators(true);
}
return this.parseStatementContent(context, topLevel);
return this.parseStatementContent(context, topLevel, decorators);
}

parseStatementContent(
this: Parser,
context?: string | null,
topLevel?: boolean | null,
decorators?: N.Decorator[] | null,
): N.Statement {
let starttype = this.state.type;
const node = this.startNode();
Expand Down Expand Up @@ -390,9 +393,13 @@ export default abstract class StatementParser extends ExpressionParser {
!context,
);

case tt._class:
case tt._class: {
if (context) this.unexpected();
return this.parseClass(node as Undone<N.ClassDeclaration>, true);
return this.maybeTakeDecorators(
decorators,
this.parseClass(node as Undone<N.ClassDeclaration>, true),
);
}

case tt._if:
return this.parseIfStatement(node as Undone<N.IfStatement>);
Expand Down Expand Up @@ -462,6 +469,7 @@ export default abstract class StatementParser extends ExpressionParser {
| N.ExportDefaultDeclaration
| N.ExportDefaultDeclaration
>,
decorators,
);

if (
Expand Down Expand Up @@ -521,6 +529,7 @@ export default abstract class StatementParser extends ExpressionParser {
return this.parseExpressionStatement(
node as Undone<N.ExpressionStatement>,
expr,
decorators,
);
}
}
Expand All @@ -539,38 +548,26 @@ export default abstract class StatementParser extends ExpressionParser {
);
}

takeDecorators(node: N.HasDecorators): void {
const decorators =
this.state.decoratorStack[this.state.decoratorStack.length - 1];
if (decorators.length) {
node.decorators = decorators;
this.resetStartLocationFromNode(node, decorators[0]);
this.state.decoratorStack[this.state.decoratorStack.length - 1] = [];

if (
this.decoratorsEnabledBeforeExport() &&
this.input.slice(
this.state.lastTokStart,
this.state.lastTokEndLoc.index,
) === "export"
) {
this.takeLeadingCommentsAfterLastToken(node);
}
maybeTakeDecorators<T extends N.Class>(
maybeDecorators: N.Decorator[] | null,
node: T,
): T {
if (maybeDecorators) {
node.decorators = maybeDecorators;
this.resetStartLocationFromNode(node, maybeDecorators[0]);
}
return node;
}

canHaveLeadingDecorator(): boolean {
return this.match(tt._class);
}

parseDecorators(this: Parser, allowExport?: boolean): void {
const { decoratorStack } = this.state;
const currentContextDecorators = decoratorStack[decoratorStack.length - 1];

while (this.match(tt.at)) {
const decorator = this.parseDecorator();
currentContextDecorators.push(decorator);
}
parseDecorators(this: Parser, allowExport?: boolean): N.Decorator[] {
const decorators = [];
do {
decorators.push(this.parseDecorator());
} while (this.match(tt.at));

if (this.match(tt._export)) {
if (!allowExport) {
Expand All @@ -580,14 +577,13 @@ export default abstract class StatementParser extends ExpressionParser {
if (!this.decoratorsEnabledBeforeExport()) {
this.raise(Errors.DecoratorExportClass, { at: this.state.startLoc });
}

const firstDecorator = decoratorStack[decoratorStack.length - 1][0];
this.forceFinalizeLeadingComments(firstDecorator);
} else if (!this.canHaveLeadingDecorator()) {
throw this.raise(Errors.UnexpectedLeadingDecorator, {
at: this.state.startLoc,
});
}

return decorators;
}

parseDecorator(this: Parser): N.Decorator {
Expand All @@ -597,10 +593,6 @@ export default abstract class StatementParser extends ExpressionParser {
this.next();

if (this.hasPlugin("decorators")) {
// Every time a decorator class expression is evaluated, a new empty array is pushed onto the stack
// So that the decorators of any nested class expressions will be dealt with separately
this.state.decoratorStack.push([]);

const startLoc = this.state.startLoc;
let expr: N.Expression;

Expand Down Expand Up @@ -643,8 +635,6 @@ export default abstract class StatementParser extends ExpressionParser {

node.expression = this.parseMaybeDecoratorArguments(expr);
}

this.state.decoratorStack.pop();
} else {
node.expression = this.parseExprSubscripts();
}
Expand Down Expand Up @@ -1121,6 +1111,8 @@ export default abstract class StatementParser extends ExpressionParser {
parseExpressionStatement(
node: Undone<N.ExpressionStatement>,
expr: N.Expression,
/* eslint-disable @typescript-eslint/no-unused-vars -- used in TypeScript parser */
decorators: N.Decorator[] | null,
) {
node.expression = expr;
this.semicolon();
Expand Down Expand Up @@ -1499,8 +1491,6 @@ export default abstract class StatementParser extends ExpressionParser {
isStatement: /* T === ClassDeclaration */ boolean,
optionalId?: boolean,
): T {
this.takeDecorators(node);

this.next(); // 'class'

// A class definition is always strict mode code.
Expand Down Expand Up @@ -2130,6 +2120,7 @@ export default abstract class StatementParser extends ExpressionParser {
| N.ExportAllDeclaration
| N.ExportNamedDeclaration
>,
decorators: N.Decorator[] | null,
): N.AnyExport {
const hasDefault = this.maybeParseExportDefaultSpecifier(
// @ts-expect-error todo(flow->ts)
Expand All @@ -2154,6 +2145,9 @@ export default abstract class StatementParser extends ExpressionParser {

if (hasStar && !hasNamespace) {
if (hasDefault) this.unexpected();
if (decorators) {
throw this.raise(Errors.UnsupportedDecoratorExport, { at: node });
}
this.parseExportFrom(node as Undone<N.ExportNamedDeclaration>, true);

return this.finishNode(node, "ExportAllDeclaration");
Expand All @@ -2174,6 +2168,9 @@ export default abstract class StatementParser extends ExpressionParser {
let hasDeclaration;
if (isFromRequired || hasSpecifiers) {
hasDeclaration = false;
if (decorators) {
throw this.raise(Errors.UnsupportedDecoratorExport, { at: node });
}
this.parseExportFrom(
node as Undone<N.ExportNamedDeclaration>,
isFromRequired,
Expand All @@ -2191,16 +2188,37 @@ export default abstract class StatementParser extends ExpressionParser {
false,
!!(node as Undone<N.ExportNamedDeclaration>).source,
);
return this.finishNode(node, "ExportNamedDeclaration");
const finished = this.finishNode(
node as Undone<N.ExportNamedDeclaration>,
"ExportNamedDeclaration",
);
if (finished.declaration?.type === "ClassDeclaration") {
this.maybeTakeDecorators(decorators, finished.declaration);
} else if (decorators) {
throw this.raise(Errors.UnsupportedDecoratorExport, { at: node });
}
return finished;
}

if (this.eat(tt._default)) {
// export default ...
(node as Undone<N.ExportDefaultDeclaration>).declaration =
this.parseExportDefaultExpression();
this.checkExport(node as Undone<N.ExportDefaultDeclaration>, true, true);
const decl = this.parseExportDefaultExpression();
(node as Undone<N.ExportDefaultDeclaration>).declaration = decl;

const finished = this.finishNode(
node as Undone<N.ExportDefaultDeclaration>,
"ExportDefaultDeclaration",
);

if (decl.type === "ClassDeclaration") {
this.maybeTakeDecorators(decorators, decl as N.ClassDeclaration);
} else if (decorators) {
throw this.raise(Errors.UnsupportedDecoratorExport, { at: node });
}

this.checkExport(finished, true, true);

return this.finishNode(node, "ExportDefaultDeclaration");
return finished;
}

throw this.unexpected(null, tt.braceL);
Expand Down Expand Up @@ -2311,8 +2329,10 @@ export default abstract class StatementParser extends ExpressionParser {
) {
this.raise(Errors.DecoratorBeforeExport, { at: this.state.startLoc });
}
this.parseDecorators(false);
return this.parseClass(expr as Undone<N.ClassExpression>, true, true);
return this.maybeTakeDecorators(
this.parseDecorators(false),
this.parseClass(this.startNode<N.ClassDeclaration>(), true, true),
);
}

if (this.match(tt._const) || this.match(tt._var) || this.isLet()) {
Expand All @@ -2331,6 +2351,14 @@ export default abstract class StatementParser extends ExpressionParser {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
node: Undone<N.ExportNamedDeclaration>,
): N.Declaration | undefined | null {
if (this.match(tt._class)) {
const node = this.parseClass(
this.startNode<N.ClassDeclaration>(),
true,
false,
);
return node;
}
return this.parseStatement(null) as N.Declaration;
}

Expand Down Expand Up @@ -2494,14 +2522,6 @@ export default abstract class StatementParser extends ExpressionParser {
}
}
}

const currentContextDecorators =
this.state.decoratorStack[this.state.decoratorStack.length - 1];
// If node.declaration is a class, it will take all decorators in the current context.
// Thus we should throw if we see non-empty decorators here.
if (currentContextDecorators.length) {
throw this.raise(Errors.UnsupportedDecoratorExport, { at: node });
}
}

checkDeclaration(node: N.Pattern | N.ObjectProperty): void {
Expand Down
7 changes: 5 additions & 2 deletions packages/babel-parser/src/plugins/estree.ts
Expand Up @@ -451,8 +451,11 @@ export default (superClass: typeof Parser) =>
super.toReferencedArguments(node);
}

parseExport(unfinished: Undone<N.AnyExport>) {
const node = super.parseExport(unfinished);
parseExport(
unfinished: Undone<N.AnyExport>,
decorators: N.Decorator[] | null,
) {
const node = super.parseExport(unfinished, decorators);

switch (node.type) {
case "ExportAllDeclaration":
Expand Down

0 comments on commit 0a7c85d

Please sign in to comment.