From e45d5c3b6524a8842ac44f60f86b2d204c74e2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Tue, 15 May 2018 18:39:54 +0200 Subject: [PATCH] Add an option to Babylon to have decorators before export (#7869) * Add support for plugin options in Babylon They work similarly to how they work in Babel. e.g. babylon.parse({ options: [ "plugin1", ["plugin2", { option: true }] ] }); The inernal api to get an option is this.getPluginOption("pluginName", "option") If the plugin isn't defined, it returns undefined. * Add Babylon option decorators.secoratorsBeforeExport * Nit --- packages/babylon/src/index.js | 40 +++--- packages/babylon/src/parser/base.js | 6 +- packages/babylon/src/parser/index.js | 7 +- packages/babylon/src/parser/statement.js | 25 +++- .../input.mjs | 1 + .../options.json | 6 + .../output.json | 136 ++++++++++++++++++ .../input.mjs | 1 + .../options.json | 7 + .../input.mjs | 2 + .../options.json | 6 + .../output.json | 132 +++++++++++++++++ .../decoratorsBeforeExport-export/input.mjs | 2 + .../options.json | 6 + .../decoratorsBeforeExport-export/output.json | 134 +++++++++++++++++ .../options.json | 2 +- .../decorators-2/plugin-conflict/options.json | 2 +- 17 files changed, 490 insertions(+), 25 deletions(-) create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/input.mjs create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/options.json create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/output.json create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-without-parens/input.mjs create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-without-parens/options.json create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/input.mjs create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/options.json create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/output.json create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/input.mjs create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/options.json create mode 100644 packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/output.json diff --git a/packages/babylon/src/index.js b/packages/babylon/src/index.js index 3c7aa419bb..23bf9cf9de 100755 --- a/packages/babylon/src/index.js +++ b/packages/babylon/src/index.js @@ -67,39 +67,39 @@ function getParserClass( pluginsFromOptions: $ReadOnlyArray, ): Class { if ( - pluginsFromOptions.indexOf("decorators-legacy") >= 0 && - pluginsFromOptions.indexOf("decorators") >= 0 + hasPlugin(pluginsFromOptions, "decorators") && + hasPlugin(pluginsFromOptions, "decorators-legacy") ) { - throw new Error("Cannot use decorators and decorators2 plugin together"); + throw new Error( + "Cannot use the decorators and decorators-legacy plugin together", + ); } // Filter out just the plugins that have an actual mixin associated with them. - let pluginList = pluginsFromOptions.filter( - p => p === "estree" || p === "flow" || p === "jsx" || p === "typescript", - ); + let pluginList = pluginsFromOptions.filter(plugin => { + const p = getPluginName(plugin); + return p === "estree" || p === "flow" || p === "jsx" || p === "typescript"; + }); - if (pluginList.indexOf("flow") >= 0) { + if (hasPlugin(pluginList, "flow")) { // ensure flow plugin loads last - pluginList = pluginList.filter(plugin => plugin !== "flow"); + pluginList = pluginList.filter(p => getPluginName(p) !== "flow"); pluginList.push("flow"); } - if ( - pluginList.indexOf("flow") >= 0 && - pluginList.indexOf("typescript") >= 0 - ) { + if (hasPlugin(pluginList, "flow") && hasPlugin(pluginList, "typescript")) { throw new Error("Cannot combine flow and typescript plugins."); } - if (pluginList.indexOf("typescript") >= 0) { + if (hasPlugin(pluginList, "typescript")) { // ensure typescript plugin loads last - pluginList = pluginList.filter(plugin => plugin !== "typescript"); + pluginList = pluginList.filter(p => getPluginName(p) !== "typescript"); pluginList.push("typescript"); } - if (pluginList.indexOf("estree") >= 0) { + if (hasPlugin(pluginList, "estree")) { // ensure estree plugin loads first - pluginList = pluginList.filter(plugin => plugin !== "estree"); + pluginList = pluginList.filter(p => getPluginName(p) !== "estree"); pluginList.unshift("estree"); } @@ -114,3 +114,11 @@ function getParserClass( } return cls; } + +function getPluginName(plugin) { + return Array.isArray(plugin) ? plugin[0] : plugin; +} + +function hasPlugin(pluginsList, name) { + return pluginsList.some(plugin => getPluginName(plugin) === name); +} diff --git a/packages/babylon/src/parser/base.js b/packages/babylon/src/parser/base.js index 6e7ab90d65..e0dc90ddf8 100644 --- a/packages/babylon/src/parser/base.js +++ b/packages/babylon/src/parser/base.js @@ -26,6 +26,10 @@ export default class BaseParser { } hasPlugin(name: string): boolean { - return !!this.plugins[name]; + return Object.hasOwnProperty.call(this.plugins, name); + } + + getPluginOption(plugin: string, name: string) { + if (this.hasPlugin(plugin)) return this.plugins[plugin][name]; } } diff --git a/packages/babylon/src/parser/index.js b/packages/babylon/src/parser/index.js index 35525227c6..5ebcb07bcf 100644 --- a/packages/babylon/src/parser/index.js +++ b/packages/babylon/src/parser/index.js @@ -41,9 +41,10 @@ export default class Parser extends StatementParser { function pluginsMap( pluginList: $ReadOnlyArray, ): { [key: string]: boolean } { - const pluginMap = {}; - for (const name of pluginList) { - pluginMap[name] = true; + const pluginMap = Object.create(null); + for (const plugin of pluginList) { + const [name, options = {}] = Array.isArray(plugin) ? plugin : [plugin]; + pluginMap[name] = options; } return pluginMap; } diff --git a/packages/babylon/src/parser/statement.js b/packages/babylon/src/parser/statement.js index 969ea9639d..08c97fe1c9 100644 --- a/packages/babylon/src/parser/statement.js +++ b/packages/babylon/src/parser/statement.js @@ -230,7 +230,10 @@ export default class StatementParser extends ExpressionParser { } parseDecorators(allowExport?: boolean): void { - if (this.hasPlugin("decorators")) { + if ( + this.hasPlugin("decorators") && + !this.getPluginOption("decorators", "decoratorsBeforeExport") + ) { allowExport = false; } @@ -1422,6 +1425,12 @@ export default class StatementParser extends ExpressionParser { } else if (this.match(tt._class)) { return this.parseClass(expr, true, true); } else if (this.match(tt.at)) { + if ( + this.hasPlugin("decorators") && + this.getPluginOption("decorators", "decoratorsBeforeExport") + ) { + this.unexpected(); + } this.parseDecorators(false); return this.parseClass(expr, true, true); } else if ( @@ -1518,14 +1527,24 @@ export default class StatementParser extends ExpressionParser { } shouldParseExportDeclaration(): boolean { + if (this.match(tt.at)) { + this.expectOnePlugin(["decorators", "decorators-legacy"]); + if (this.hasPlugin("decorators")) { + if (this.getPluginOption("decorators", "decoratorsBeforeExport")) { + this.unexpected(); + } else { + return true; + } + } + } + return ( this.state.type.keyword === "var" || this.state.type.keyword === "const" || this.state.type.keyword === "let" || this.state.type.keyword === "function" || this.state.type.keyword === "class" || - this.isContextual("async") || - (this.match(tt.at) && this.expectPlugin("decorators")) + this.isContextual("async") ); } diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/input.mjs b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/input.mjs new file mode 100644 index 0000000000..964761176b --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/input.mjs @@ -0,0 +1 @@ +export default (@decorator class Foo {}) diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/options.json b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/options.json new file mode 100644 index 0000000000..8dcb89f00f --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/options.json @@ -0,0 +1,6 @@ +{ + "sourceType": "module", + "plugins": [ + ["decorators", { "decoratorsBeforeExport": true }] + ] +} diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/output.json b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/output.json new file mode 100644 index 0000000000..9744f0c47a --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-with-parens/output.json @@ -0,0 +1,136 @@ +{ + "type": "File", + "start": 0, + "end": 40, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 40 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 40, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 40 + } + }, + "sourceType": "module", + "body": [ + { + "type": "ExportDefaultDeclaration", + "start": 0, + "end": 40, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 40 + } + }, + "declaration": { + "type": "ClassExpression", + "start": 16, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "decorators": [ + { + "type": "Decorator", + "start": 16, + "end": 26, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 26 + } + }, + "callee": { + "type": "Identifier", + "start": 17, + "end": 26, + "loc": { + "start": { + "line": 1, + "column": 17 + }, + "end": { + "line": 1, + "column": 26 + }, + "identifierName": "decorator" + }, + "name": "decorator" + } + } + ], + "id": { + "type": "Identifier", + "start": 33, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 33 + }, + "end": { + "line": 1, + "column": 36 + }, + "identifierName": "Foo" + }, + "name": "Foo" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 37, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 37 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "body": [] + }, + "extra": { + "parenthesized": true, + "parenStart": 15 + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-without-parens/input.mjs b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-without-parens/input.mjs new file mode 100644 index 0000000000..fdbd3947bc --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-without-parens/input.mjs @@ -0,0 +1 @@ +export default @decorator class Foo {} diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-without-parens/options.json b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-without-parens/options.json new file mode 100644 index 0000000000..c9bed54c7f --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default-decorated-expression-without-parens/options.json @@ -0,0 +1,7 @@ +{ + "sourceType": "module", + "plugins": [ + ["decorators", { "decoratorsBeforeExport": true }] + ], + "throws": "Unexpected token (1:15)" +} diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/input.mjs b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/input.mjs new file mode 100644 index 0000000000..d1568b1d95 --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/input.mjs @@ -0,0 +1,2 @@ +@decorator +export default class Foo {} diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/options.json b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/options.json new file mode 100644 index 0000000000..8dcb89f00f --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/options.json @@ -0,0 +1,6 @@ +{ + "sourceType": "module", + "plugins": [ + ["decorators", { "decoratorsBeforeExport": true }] + ] +} diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/output.json b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/output.json new file mode 100644 index 0000000000..a4e825f478 --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export-default/output.json @@ -0,0 +1,132 @@ +{ + "type": "File", + "start": 0, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 27 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 27 + } + }, + "sourceType": "module", + "body": [ + { + "type": "ExportDefaultDeclaration", + "start": 11, + "end": 38, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 27 + } + }, + "declaration": { + "type": "ClassDeclaration", + "start": 0, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 27 + } + }, + "decorators": [ + { + "type": "Decorator", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "callee": { + "type": "Identifier", + "start": 1, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 10 + }, + "identifierName": "decorator" + }, + "name": "decorator" + } + } + ], + "id": { + "type": "Identifier", + "start": 32, + "end": 35, + "loc": { + "start": { + "line": 2, + "column": 21 + }, + "end": { + "line": 2, + "column": 24 + }, + "identifierName": "Foo" + }, + "name": "Foo" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 36, + "end": 38, + "loc": { + "start": { + "line": 2, + "column": 25 + }, + "end": { + "line": 2, + "column": 27 + } + }, + "body": [] + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/input.mjs b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/input.mjs new file mode 100644 index 0000000000..911e70cef0 --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/input.mjs @@ -0,0 +1,2 @@ +@decorator +export class Foo {} diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/options.json b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/options.json new file mode 100644 index 0000000000..8dcb89f00f --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/options.json @@ -0,0 +1,6 @@ +{ + "sourceType": "module", + "plugins": [ + ["decorators", { "decoratorsBeforeExport": true }] + ] +} diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/output.json b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/output.json new file mode 100644 index 0000000000..4fa56b4f02 --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/decorators-2/decoratorsBeforeExport-export/output.json @@ -0,0 +1,134 @@ +{ + "type": "File", + "start": 0, + "end": 30, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 30, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "sourceType": "module", + "body": [ + { + "type": "ExportNamedDeclaration", + "start": 11, + "end": 30, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "specifiers": [], + "source": null, + "declaration": { + "type": "ClassDeclaration", + "start": 0, + "end": 30, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "decorators": [ + { + "type": "Decorator", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "callee": { + "type": "Identifier", + "start": 1, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 10 + }, + "identifierName": "decorator" + }, + "name": "decorator" + } + } + ], + "id": { + "type": "Identifier", + "start": 24, + "end": 27, + "loc": { + "start": { + "line": 2, + "column": 13 + }, + "end": { + "line": 2, + "column": 16 + }, + "identifierName": "Foo" + }, + "name": "Foo" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 28, + "end": 30, + "loc": { + "start": { + "line": 2, + "column": 17 + }, + "end": { + "line": 2, + "column": 19 + } + }, + "body": [] + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/export-decorated-class-without-plugin/options.json b/packages/babylon/test/fixtures/experimental/decorators-2/export-decorated-class-without-plugin/options.json index ff3a0c88e5..5a8bd1e838 100644 --- a/packages/babylon/test/fixtures/experimental/decorators-2/export-decorated-class-without-plugin/options.json +++ b/packages/babylon/test/fixtures/experimental/decorators-2/export-decorated-class-without-plugin/options.json @@ -1,5 +1,5 @@ { "sourceType": "module", - "throws": "This experimental syntax requires enabling the parser plugin: 'decorators' (1:7)", + "throws": "This experimental syntax requires enabling one of the following parser plugin(s): 'decorators, decorators-legacy' (1:7)", "plugins": null } diff --git a/packages/babylon/test/fixtures/experimental/decorators-2/plugin-conflict/options.json b/packages/babylon/test/fixtures/experimental/decorators-2/plugin-conflict/options.json index 1e7b20958f..4d6ea76c3c 100644 --- a/packages/babylon/test/fixtures/experimental/decorators-2/plugin-conflict/options.json +++ b/packages/babylon/test/fixtures/experimental/decorators-2/plugin-conflict/options.json @@ -1,4 +1,4 @@ { "plugins": ["decorators-legacy", "decorators"], - "throws": "Cannot use decorators and decorators2 plugin together" + "throws": "Cannot use the decorators and decorators-legacy plugin together" }