diff --git a/src/parser/expression.js b/src/parser/expression.js index df41e064f5..c6626b1338 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -433,12 +433,7 @@ export default class ExpressionParser extends LValParser { noCalls, ); } else if (this.match(tt.questionDot)) { - if (!this.hasPlugin("optionalChaining")) { - this.raise( - startPos, - "You can only use optional-chaining when the 'optionalChaining' plugin is enabled.", - ); - } + this.expectPlugin("optionalChaining"); if (noCalls && this.lookahead().type == tt.parenL) { state.stop = true; @@ -660,11 +655,11 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, "Super"); case tt._import: - if (this.hasPlugin("importMeta") && this.lookahead().type === tt.dot) { + if (this.lookahead().type === tt.dot) { return this.parseImportMetaProperty(); } - if (!this.hasPlugin("dynamicImport")) this.unexpected(); + this.expectPlugin("dynamicImport"); node = this.startNode(); this.next(); @@ -712,7 +707,9 @@ export default class ExpressionParser extends LValParser { return id; case tt._do: - if (this.hasPlugin("doExpressions")) { + // TODO + if (true) { + this.expectPlugin("doExpressions"); const node = this.startNode(); this.next(); const oldInFunction = this.state.inFunction; @@ -825,11 +822,7 @@ export default class ExpressionParser extends LValParser { parseFunctionExpression(): N.FunctionExpression | N.MetaProperty { const node = this.startNode(); const meta = this.parseIdentifier(true); - if ( - this.state.inGenerator && - this.hasPlugin("functionSent") && - this.eat(tt.dot) - ) { + if (this.state.inGenerator && this.eat(tt.dot)) { return this.parseMetaProperty(node, meta, "sent"); } return this.parseFunction(node, false); @@ -841,6 +834,16 @@ export default class ExpressionParser extends LValParser { propertyName: string, ): N.MetaProperty { node.meta = meta; + + if (meta.name === "function" && propertyName === "sent") { + if (this.isContextual(propertyName)) { + this.expectPlugin("functionSent"); + } else if (!this.hasPlugin("functionSent")) { + // They didn't actually say `function.sent`, just `function.`, so a simple error would be less confusing. + this.unexpected(); + } + } + node.property = this.parseIdentifier(true); if (node.property.name !== propertyName) { @@ -857,6 +860,18 @@ export default class ExpressionParser extends LValParser { const node = this.startNode(); const id = this.parseIdentifier(true); this.expect(tt.dot); + + if (id.name === "import") { + if (this.isContextual("meta")) { + this.expectPlugin("importMeta"); + } else if (!this.hasPlugin("importMeta")) { + this.raise( + null, + `Dynamic imports require a parameter: import('a.js').then`, + ); + } + } + if (!this.inModule) { this.raise( id.start, @@ -1141,7 +1156,8 @@ export default class ExpressionParser extends LValParser { decorators = []; } - if (this.hasPlugin("objectRestSpread") && this.match(tt.ellipsis)) { + if (this.match(tt.ellipsis)) { + this.expectPlugin("objectRestSpread"); prop = this.parseSpread(isPattern ? { start: 0 } : undefined); if (isPattern) { this.toAssignable(prop, true, "object pattern"); @@ -1199,8 +1215,11 @@ export default class ExpressionParser extends LValParser { prop.computed = false; } else { isAsync = true; - if (this.hasPlugin("asyncGenerators")) - isGenerator = this.eat(tt.star); + if (this.match(tt.star)) { + this.expectPlugin("asyncGenerators"); + this.next(); + isGenerator = true; + } this.parsePropertyName(prop); } } else { diff --git a/src/parser/location.js b/src/parser/location.js index c506fd0928..a037632672 100644 --- a/src/parser/location.js +++ b/src/parser/location.js @@ -10,7 +10,11 @@ import CommentsParser from "./comments"; // message. export default class LocationParser extends CommentsParser { - raise(pos: number, message: string, missingPluginName: string): empty { + raise( + pos: number, + message: string, + missingPluginNames?: Array, + ): empty { const loc = getLineInfo(this.input, pos); message += ` (${loc.line}:${loc.column})`; // $FlowIgnore @@ -19,8 +23,8 @@ export default class LocationParser extends CommentsParser { ); err.pos = pos; err.loc = loc; - if (missingPluginName) { - err.missingPlugin = missingPluginName; + if (missingPluginNames) { + err.missingPlugin = missingPluginNames; } throw err; } diff --git a/src/parser/statement.js b/src/parser/statement.js index d2bce9dce9..610095105a 100644 --- a/src/parser/statement.js +++ b/src/parser/statement.js @@ -237,9 +237,7 @@ export default class StatementParser extends ExpressionParser { } parseDecorator(): N.Decorator { - if (!(this.hasPlugin("decorators") || this.hasPlugin("decorators2"))) { - this.unexpected(); - } + this.expectOnePlugin(["decorators", "decorators2"]); const node = this.startNode(); this.next(); @@ -340,11 +338,8 @@ export default class StatementParser extends ExpressionParser { this.state.labels.push(loopLabel); let forAwait = false; - if ( - this.hasPlugin("asyncGenerators") && - this.state.inAsync && - this.isContextual("await") - ) { + if (this.state.inAsync && this.isContextual("await")) { + this.expectPlugin("asyncGenerators"); forAwait = true; this.next(); } @@ -489,12 +484,13 @@ export default class StatementParser extends ExpressionParser { if (this.match(tt._catch)) { const clause = this.startNode(); this.next(); - if (this.match(tt.parenL) || !this.hasPlugin("optionalCatchBinding")) { + if (this.match(tt.parenL)) { this.expect(tt.parenL); clause.param = this.parseBindingAtom(); this.checkLVal(clause.param, true, Object.create(null), "catch clause"); this.expect(tt.parenR); } else { + this.expectPlugin("optionalCatchBinding"); clause.param = null; } clause.body = this.parseBlock(); @@ -782,12 +778,11 @@ export default class StatementParser extends ExpressionParser { this.initFunction(node, isAsync); if (this.match(tt.star)) { - if (node.async && !this.hasPlugin("asyncGenerators")) { - this.unexpected(); - } else { - node.generator = true; - this.next(); + if (node.async) { + this.expectPlugin("asyncGenerators"); } + node.generator = true; + this.next(); } if ( @@ -960,8 +955,9 @@ export default class StatementParser extends ExpressionParser { isStatic = true; } - if (this.hasPlugin("classPrivateProperties") && this.match(tt.hash)) { + if (this.match(tt.hash)) { // Private property + this.expectPlugin("classPrivateProperties"); this.next(); const privateProp: N.ClassPrivateProperty = memberAny; privateProp.key = this.parseIdentifier(true); @@ -1047,8 +1043,12 @@ export default class StatementParser extends ExpressionParser { this.pushClassProperty(classBody, prop); } else if (isSimple && key.name === "async" && !this.isLineTerminator()) { // an async method - const isGenerator = - this.hasPlugin("asyncGenerators") && this.eat(tt.star); + let isGenerator = false; + if (this.match(tt.star)) { + this.expectPlugin("asyncGenerators"); + this.next(); + isGenerator = true; + } method.kind = "method"; this.parsePropertyName(method); if (this.isNonstaticConstructor(method)) { diff --git a/src/parser/util.js b/src/parser/util.js index 1300637276..c2fb78674a 100644 --- a/src/parser/util.js +++ b/src/parser/util.js @@ -114,8 +114,20 @@ export default class UtilParser extends Tokenizer { if (!this.hasPlugin(name)) { throw this.raise( this.state.start, - `This experimental syntax requires enabling the parser plugin: ${name}`, - name, + `This experimental syntax requires enabling the parser plugin: '${name}'`, + [name], + ); + } + } + + expectOnePlugin(names: Array): void { + if (!names.some(n => this.hasPlugin(n))) { + throw this.raise( + this.state.start, + `This experimental syntax requires enabling the parser plugin(s): '${names.join( + ", ", + )}'`, + names, ); } } diff --git a/test/fixtures/experimental/dynamic-import/direct-calls-only/options.json b/test/fixtures/experimental/dynamic-import/direct-calls-only/options.json index 400eeea626..4bb0327514 100644 --- a/test/fixtures/experimental/dynamic-import/direct-calls-only/options.json +++ b/test/fixtures/experimental/dynamic-import/direct-calls-only/options.json @@ -1,3 +1,3 @@ { - "throws": "Unexpected token, expected ( (2:15)" + "throws": "Dynamic imports require a parameter: import('a.js').then (1:0)" } diff --git a/test/fixtures/experimental/function-sent/disabled-function-keyword-declaration/options.json b/test/fixtures/experimental/function-sent/disabled-function-keyword-declaration/options.json index 9a9a59331e..991da279e5 100644 --- a/test/fixtures/experimental/function-sent/disabled-function-keyword-declaration/options.json +++ b/test/fixtures/experimental/function-sent/disabled-function-keyword-declaration/options.json @@ -1,3 +1,3 @@ { - "throws": "Unexpected token, expected ( (2:10)" + "throws": "This experimental syntax requires enabling the parser plugin: 'functionSent' (2:11)" } diff --git a/test/fixtures/experimental/function-sent/disabled-function-keyword-expression/options.json b/test/fixtures/experimental/function-sent/disabled-function-keyword-expression/options.json index 36c4be02d9..eee5abbf1f 100644 --- a/test/fixtures/experimental/function-sent/disabled-function-keyword-expression/options.json +++ b/test/fixtures/experimental/function-sent/disabled-function-keyword-expression/options.json @@ -1,3 +1,3 @@ { - "throws": "Unexpected token, expected ( (2:11)" + "throws": "This experimental syntax requires enabling the parser plugin: 'functionSent' (2:12)" } diff --git a/test/fixtures/experimental/function-sent/disabled-inside-generator/options.json b/test/fixtures/experimental/function-sent/disabled-inside-generator/options.json index 6127ef0bfb..9ce89a133d 100644 --- a/test/fixtures/experimental/function-sent/disabled-inside-generator/options.json +++ b/test/fixtures/experimental/function-sent/disabled-inside-generator/options.json @@ -1,3 +1,3 @@ { - "throws": "Unexpected token, expected ( (2:17)" + "throws": "This experimental syntax requires enabling the parser plugin: 'functionSent' (2:18)" } diff --git a/test/fixtures/experimental/no-async-generators/error-without-plugin/options.json b/test/fixtures/experimental/no-async-generators/error-without-plugin/options.json index 96a1980f24..4fbfd38e68 100644 --- a/test/fixtures/experimental/no-async-generators/error-without-plugin/options.json +++ b/test/fixtures/experimental/no-async-generators/error-without-plugin/options.json @@ -1,4 +1,4 @@ { - "throws": "Unexpected token (1:15)", + "throws": "This experimental syntax requires enabling the parser plugin: 'asyncGenerators' (1:15)", "plugins": [] -} \ No newline at end of file +} diff --git a/test/fixtures/experimental/no-decorators/error-without-plugin/actual.js b/test/fixtures/experimental/no-decorators/error-without-plugin/actual.js new file mode 100644 index 0000000000..9dea56e7ca --- /dev/null +++ b/test/fixtures/experimental/no-decorators/error-without-plugin/actual.js @@ -0,0 +1,2 @@ +@memoize +function() {} diff --git a/test/fixtures/experimental/no-decorators/error-without-plugin/options.json b/test/fixtures/experimental/no-decorators/error-without-plugin/options.json new file mode 100644 index 0000000000..33a94ed1eb --- /dev/null +++ b/test/fixtures/experimental/no-decorators/error-without-plugin/options.json @@ -0,0 +1,4 @@ +{ + "throws": "This experimental syntax requires enabling the parser plugin(s): 'decorators, decorators2' (1:0)", + "plugins": [] +} diff --git a/test/fixtures/experimental/no-do-expressions/error-without-plugin/actual.js b/test/fixtures/experimental/no-do-expressions/error-without-plugin/actual.js new file mode 100644 index 0000000000..a7c0c31b31 --- /dev/null +++ b/test/fixtures/experimental/no-do-expressions/error-without-plugin/actual.js @@ -0,0 +1 @@ +(do {x}) diff --git a/test/fixtures/experimental/no-do-expressions/error-without-plugin/options.json b/test/fixtures/experimental/no-do-expressions/error-without-plugin/options.json new file mode 100644 index 0000000000..0aaae18db8 --- /dev/null +++ b/test/fixtures/experimental/no-do-expressions/error-without-plugin/options.json @@ -0,0 +1,4 @@ +{ + "throws": "This experimental syntax requires enabling the parser plugin: 'doExpressions' (1:1)", + "plugins": [] +} diff --git a/test/fixtures/experimental/no-dynamic-import/error-without-plugin/actual.js b/test/fixtures/experimental/no-dynamic-import/error-without-plugin/actual.js new file mode 100644 index 0000000000..f0038a3c11 --- /dev/null +++ b/test/fixtures/experimental/no-dynamic-import/error-without-plugin/actual.js @@ -0,0 +1 @@ +var $ = import("jquery"); diff --git a/test/fixtures/experimental/no-dynamic-import/error-without-plugin/options.json b/test/fixtures/experimental/no-dynamic-import/error-without-plugin/options.json new file mode 100644 index 0000000000..90bbfd5623 --- /dev/null +++ b/test/fixtures/experimental/no-dynamic-import/error-without-plugin/options.json @@ -0,0 +1,4 @@ +{ + "throws": "This experimental syntax requires enabling the parser plugin: 'dynamicImport' (1:8)", + "plugins": [] +} diff --git a/test/fixtures/experimental/no-import-meta/error-without-plugin/actual.js b/test/fixtures/experimental/no-import-meta/error-without-plugin/actual.js new file mode 100644 index 0000000000..e1cfc5468e --- /dev/null +++ b/test/fixtures/experimental/no-import-meta/error-without-plugin/actual.js @@ -0,0 +1 @@ +const x = import.meta; diff --git a/test/fixtures/experimental/no-import-meta/error-without-plugin/options.json b/test/fixtures/experimental/no-import-meta/error-without-plugin/options.json new file mode 100644 index 0000000000..48ce55a15d --- /dev/null +++ b/test/fixtures/experimental/no-import-meta/error-without-plugin/options.json @@ -0,0 +1,5 @@ +{ + "throws": "This experimental syntax requires enabling the parser plugin: 'importMeta' (1:17)", + "sourceType": "module", + "plugins": [] +} diff --git a/test/fixtures/experimental/no-object-rest-spread/error-without-plugin/actual.js b/test/fixtures/experimental/no-object-rest-spread/error-without-plugin/actual.js new file mode 100644 index 0000000000..93e64fb4ca --- /dev/null +++ b/test/fixtures/experimental/no-object-rest-spread/error-without-plugin/actual.js @@ -0,0 +1 @@ +({...x}) diff --git a/test/fixtures/experimental/no-object-rest-spread/error-without-plugin/options.json b/test/fixtures/experimental/no-object-rest-spread/error-without-plugin/options.json new file mode 100644 index 0000000000..98dd2ee11d --- /dev/null +++ b/test/fixtures/experimental/no-object-rest-spread/error-without-plugin/options.json @@ -0,0 +1,4 @@ +{ + "throws": "This experimental syntax requires enabling the parser plugin: 'objectRestSpread' (1:2)", + "plugins": [] +} diff --git a/test/fixtures/experimental/optional-catch-binding/no-plugin-no-binding-finally/options.json b/test/fixtures/experimental/optional-catch-binding/no-plugin-no-binding-finally/options.json index 04c3ad7d05..0b022f9ea1 100644 --- a/test/fixtures/experimental/optional-catch-binding/no-plugin-no-binding-finally/options.json +++ b/test/fixtures/experimental/optional-catch-binding/no-plugin-no-binding-finally/options.json @@ -1,3 +1,3 @@ { - "throws": "Unexpected token, expected ( (4:6)" + "throws": "This experimental syntax requires enabling the parser plugin: 'optionalCatchBinding' (4:6)" } diff --git a/test/fixtures/experimental/optional-catch-binding/no-plugin-no-binding/options.json b/test/fixtures/experimental/optional-catch-binding/no-plugin-no-binding/options.json index 04c3ad7d05..0b022f9ea1 100644 --- a/test/fixtures/experimental/optional-catch-binding/no-plugin-no-binding/options.json +++ b/test/fixtures/experimental/optional-catch-binding/no-plugin-no-binding/options.json @@ -1,3 +1,3 @@ { - "throws": "Unexpected token, expected ( (4:6)" + "throws": "This experimental syntax requires enabling the parser plugin: 'optionalCatchBinding' (4:6)" } diff --git a/test/fixtures/experimental/optional-chaining/missing-plugin/options.json b/test/fixtures/experimental/optional-chaining/missing-plugin/options.json index 9aa9db14c9..b8e170c94a 100644 --- a/test/fixtures/experimental/optional-chaining/missing-plugin/options.json +++ b/test/fixtures/experimental/optional-chaining/missing-plugin/options.json @@ -1,3 +1,3 @@ { - "throws": "You can only use optional-chaining when the 'optionalChaining' plugin is enabled. (1:0)" + "throws": "This experimental syntax requires enabling the parser plugin: 'optionalChaining' (1:1)" }