Skip to content

Commit

Permalink
fix: support decorator before and after "export" keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
3cp committed Oct 19, 2020
1 parent e27b9d6 commit f3898ff
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 5 deletions.
3 changes: 2 additions & 1 deletion src/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Token, KeywordDescTable } from './token';
import { Errors, report } from './errors';
import { Node, Comment } from './estree';
import { Node, Comment, Decorator } from './estree';
import { nextToken } from './lexer/scan';

/**
Expand Down Expand Up @@ -227,6 +227,7 @@ export interface ParserState {
currentChar: number;
exportedNames: any;
exportedBindings: any;
leadingDecorators: Decorator[] | void;
}

/**
Expand Down
6 changes: 4 additions & 2 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ export const enum Errors {
OptionalChainingNoTemplate,
OptionalChainingNoSuper,
OptionalChainingNoNew,
ImportMetaOutsideModule
ImportMetaOutsideModule,
InvalidLeadingDecorator
}

export const errorMessages: {
Expand Down Expand Up @@ -342,7 +343,8 @@ export const errorMessages: {
[Errors.OptionalChainingNoTemplate]: 'Invalid tagged template on optional chain',
[Errors.OptionalChainingNoSuper]: 'Invalid optional chain from super property',
[Errors.OptionalChainingNoNew]: 'Invalid optional chain from new expression',
[Errors.ImportMetaOutsideModule]: 'Cannot use "import.meta" outside a module'
[Errors.ImportMetaOutsideModule]: 'Cannot use "import.meta" outside a module',
[Errors.InvalidLeadingDecorator]: 'Leading decorators must be attached to a class declaration'
};

export class ParseError extends SyntaxError {
Expand Down
35 changes: 34 additions & 1 deletion src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,12 @@ export function create(
/**
* Holds either a function or array used on every token
*/
onToken
onToken,

/**
* Holds leading decorators before "export" or "class" keywords
*/
leadingDecorators: void 0
};
}

Expand Down Expand Up @@ -415,6 +420,16 @@ export function parseModuleItem(
line: number,
column: number
): any {
if (context & Context.OptionsNext) {
const decorators = parseDecorators(parser, context);
if (decorators.length) {
parser.leadingDecorators = decorators;
if (parser.token !== Token.ExportKeyword && parser.token !== Token.ClassKeyword) {
report(parser, Errors.InvalidLeadingDecorator);
}
}
}

// ecma262/#prod-ModuleItem
// ModuleItem :
// ImportDeclaration
Expand Down Expand Up @@ -2880,6 +2895,11 @@ function parseExportDeclaration(
// See: https://www.ecma-international.org/ecma-262/9.0/index.html#sec-exports-static-semantics-exportednames
if (scope) declareUnboundVariable(parser, 'default');

if (parser.leadingDecorators) {
// leadingDecorators should be consumed by parseClassDeclaration
report(parser, Errors.InvalidLeadingDecorator);
}

return finishNode(parser, context, start, line, column, {
type: 'ExportDefaultDeclaration',
declaration
Expand All @@ -2893,6 +2913,10 @@ function parseExportDeclaration(
//
// See: https://github.com/tc39/ecma262/pull/1174

if (parser.leadingDecorators) {
report(parser, Errors.InvalidLeadingDecorator);
}

nextToken(parser, context); // Skips: '*'

let exported: ESTree.Identifier | null = null;
Expand Down Expand Up @@ -3088,6 +3112,10 @@ function parseExportDeclaration(
report(parser, Errors.UnexpectedToken, KeywordDescTable[parser.token & Token.Type]);
}

if (parser.leadingDecorators) {
report(parser, Errors.InvalidLeadingDecorator);
}

return finishNode(parser, context, start, line, column, {
type: 'ExportNamedDeclaration',
declaration,
Expand Down Expand Up @@ -7904,6 +7932,11 @@ export function parseClassDeclaration(

const decorators: ESTree.Decorator[] = context & Context.OptionsNext ? parseDecorators(parser, context) : [];

if (parser.leadingDecorators) {
decorators.unshift(...parser.leadingDecorators);
parser.leadingDecorators = void 0;
}

nextToken(parser, context);
let id: ESTree.Expression | null = null;
let superClass: ESTree.Expression | null = null;
Expand Down
218 changes: 217 additions & 1 deletion test/parser/next/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,17 @@ describe('Next - Decorators', () => {
['class Foo { @a; m(){}}', Context.OptionsNext],
['class Foo { @abc constructor(){} }', Context.OptionsNext],
['class A { @foo && bar method() {} }', Context.OptionsNext],
['class A { @foo && bar; method() {} }', Context.OptionsNext]
['class A { @foo && bar; method() {} }', Context.OptionsNext],
['@bar export const foo = 1;', Context.OptionsNext | Context.Module],
['@bar export {Foo};', Context.OptionsNext | Context.Module],
['@bar export * from "./foo";', Context.OptionsNext | Context.Module],
['@bar export default function foo() {}', Context.OptionsNext | Context.Module],
['@bar const foo = 1;', Context.OptionsNext],
['@bar function foo() {}', Context.OptionsNext],
['(@bar const foo = 1);', Context.OptionsNext],
['(@bar function foo() {})', Context.OptionsNext],
['@bar;', Context.OptionsNext],
['@bar();', Context.OptionsNext]
]);

pass('Next - Decorators (pass)', [
Expand Down Expand Up @@ -355,6 +365,41 @@ describe('Next - Decorators', () => {
type: 'Program'
}
],
[
`@bar export default
class Foo { }`,
Context.OptionsNext | Context.Module | Context.Strict,
{
body: [
{
declaration: {
body: {
body: [],
type: 'ClassBody'
},
decorators: [
{
expression: {
name: 'bar',
type: 'Identifier'
},
type: 'Decorator'
}
],
id: {
name: 'Foo',
type: 'Identifier'
},
superClass: null,
type: 'ClassDeclaration'
},
type: 'ExportDefaultDeclaration'
}
],
sourceType: 'module',
type: 'Program'
}
],
[
`export default
@bar class Foo { }`,
Expand Down Expand Up @@ -425,6 +470,48 @@ describe('Next - Decorators', () => {
type: 'Program'
}
],
[
`@lo export default @bar
class Foo { }`,
Context.OptionsNext | Context.Module | Context.Strict,
{
body: [
{
declaration: {
body: {
body: [],
type: 'ClassBody'
},
decorators: [
{
expression: {
name: 'lo',
type: 'Identifier'
},
type: 'Decorator'
},
{
expression: {
name: 'bar',
type: 'Identifier'
},
type: 'Decorator'
}
],
id: {
name: 'Foo',
type: 'Identifier'
},
superClass: null,
type: 'ClassDeclaration'
},
type: 'ExportDefaultDeclaration'
}
],
sourceType: 'module',
type: 'Program'
}
],
[
`@pushElement({
kind: "initializer",
Expand Down Expand Up @@ -969,6 +1056,135 @@ describe('Next - Decorators', () => {
]
}
],
[
`@foo('bar')
class Foo {}`,
Context.OptionsNext | Context.Module,
{
type: 'Program',
sourceType: 'module',
body: [
{
type: 'ClassDeclaration',
id: {
type: 'Identifier',
name: 'Foo'
},
superClass: null,
body: {
type: 'ClassBody',
body: []
},
decorators: [
{
type: 'Decorator',
expression: {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'foo'
},
arguments: [
{
type: 'Literal',
value: 'bar'
}
]
}
}
]
}
]
}
],
[
`(@foo('bar')
class Foo {})`,
Context.OptionsNext,
{
type: 'Program',
sourceType: 'script',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'ClassExpression',
id: {
type: 'Identifier',
name: 'Foo'
},
superClass: null,
decorators: [
{
type: 'Decorator',
expression: {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'foo'
},
arguments: [
{
type: 'Literal',
value: 'bar'
}
]
}
}
],
body: {
type: 'ClassBody',
body: []
}
}
}
]
}
],
[
`(@foo('bar')
class Foo {})`,
Context.OptionsNext | Context.Module,
{
type: 'Program',
sourceType: 'module',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'ClassExpression',
id: {
type: 'Identifier',
name: 'Foo'
},
superClass: null,
decorators: [
{
type: 'Decorator',
expression: {
type: 'CallExpression',
callee: {
type: 'Identifier',
name: 'foo'
},
arguments: [
{
type: 'Literal',
value: 'bar'
}
]
}
}
],
body: {
type: 'ClassBody',
body: []
}
}
}
]
}
],
[
`class Foo {
@dec
Expand Down

0 comments on commit f3898ff

Please sign in to comment.