From b4a6e5207aa86986a126a8ee4f8273c73d49eb74 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Thu, 2 Apr 2020 21:51:07 +0900 Subject: [PATCH] Add support for `import.meta` --- acorn-loose/src/expression.js | 11 +++- acorn-loose/src/statement.js | 11 ++-- acorn/src/expression.js | 34 ++++++++++-- acorn/src/statement.js | 2 +- bin/run_test262.js | 1 - test/run.js | 1 + test/tests-dynamic-import.js | 2 +- test/tests-harmony.js | 6 +-- test/tests-import-meta.js | 97 +++++++++++++++++++++++++++++++++++ test/tests.js | 2 +- 10 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 test/tests-import-meta.js diff --git a/acorn-loose/src/expression.js b/acorn-loose/src/expression.js index dbc41bd60..d2507f435 100644 --- a/acorn-loose/src/expression.js +++ b/acorn-loose/src/expression.js @@ -310,10 +310,13 @@ lp.parseExprAtom = function() { lp.parseExprImport = function() { const node = this.startNode() - this.next() // skip `import` + const meta = this.parseIdent(true) switch (this.tok.type) { case tt.parenL: return this.parseDynamicImport(node) + case tt.dot: + node.meta = meta + return this.parseImportMeta(node) default: node.name = "import" return this.finishNode(node, "Identifier") @@ -325,6 +328,12 @@ lp.parseDynamicImport = function(node) { return this.finishNode(node, "ImportExpression") } +lp.parseImportMeta = function(node) { + this.next() // skip '.' + node.property = this.parseIdent(true) + return this.finishNode(node, "MetaProperty") +} + lp.parseNew = function() { let node = this.startNode(), startIndent = this.curIndent, line = this.curLineStart let meta = this.parseIdent(true) diff --git a/acorn-loose/src/statement.js b/acorn-loose/src/statement.js index 40230d2ee..355d278f2 100644 --- a/acorn-loose/src/statement.js +++ b/acorn-loose/src/statement.js @@ -176,10 +176,13 @@ lp.parseStatement = function() { return this.parseClass(true) case tt._import: - if (this.options.ecmaVersion > 10 && this.lookAhead(1).type === tt.parenL) { - node.expression = this.parseExpression() - this.semicolon() - return this.finishNode(node, "ExpressionStatement") + if (this.options.ecmaVersion > 10) { + const nextType = this.lookAhead(1).type + if (nextType === tt.parenL || nextType === tt.dot) { + node.expression = this.parseExpression() + this.semicolon() + return this.finishNode(node, "ExpressionStatement") + } } return this.parseImport() diff --git a/acorn/src/expression.js b/acorn/src/expression.js index a50ea7531..006aa3bed 100644 --- a/acorn/src/expression.js +++ b/acorn/src/expression.js @@ -427,10 +427,18 @@ pp.parseExprAtom = function(refDestructuringErrors) { pp.parseExprImport = function() { const node = this.startNode() - this.next() // skip `import` + + // Consume `import` as an identifier for `import.meta`. + // Because `this.parseIdent(true)` doesn't check escape sequences, it needs the check of `this.containsEsc`. + if (this.containsEsc) this.raiseRecoverable(this.start, "Escape sequence in keyword import") + const meta = this.parseIdent(true) + switch (this.type) { case tt.parenL: return this.parseDynamicImport(node) + case tt.dot: + node.meta = meta + return this.parseImportMeta(node) default: this.unexpected() } @@ -455,6 +463,22 @@ pp.parseDynamicImport = function(node) { return this.finishNode(node, "ImportExpression") } +pp.parseImportMeta = function(node) { + this.next() // skip `.` + + const containsEsc = this.containsEsc + node.property = this.parseIdent(true) + + if (node.property.name !== "meta") + this.raiseRecoverable(node.property.start, "The only valid meta property for import is 'import.meta'") + if (containsEsc) + this.raiseRecoverable(node.start, "'import.meta' must not contain escaped characters") + if (this.options.sourceType !== "module") + this.raiseRecoverable(node.start, "Cannot use 'import.meta' outside a module") + + return this.finishNode(node, "MetaProperty") +} + pp.parseLiteral = function(value) { let node = this.startNode() node.value = value @@ -557,10 +581,12 @@ pp.parseNew = function() { node.meta = meta let containsEsc = this.containsEsc node.property = this.parseIdent(true) - if (node.property.name !== "target" || containsEsc) - this.raiseRecoverable(node.property.start, "The only valid meta property for new is new.target") + if (node.property.name !== "target") + this.raiseRecoverable(node.property.start, "The only valid meta property for new is 'new.target'") + if (containsEsc) + this.raiseRecoverable(node.start, "'new.target' must not contain escaped characters") if (!this.inNonArrowFunction()) - this.raiseRecoverable(node.start, "new.target can only be used in functions") + this.raiseRecoverable(node.start, "'new.target' can only be used in functions") return this.finishNode(node, "MetaProperty") } let startPos = this.start, startLoc = this.startLoc, isImport = this.type === tt._import diff --git a/acorn/src/statement.js b/acorn/src/statement.js index 8c7e171a9..4afea7503 100644 --- a/acorn/src/statement.js +++ b/acorn/src/statement.js @@ -122,7 +122,7 @@ pp.parseStatement = function(context, topLevel, exports) { skipWhiteSpace.lastIndex = this.pos let skip = skipWhiteSpace.exec(this.input) let next = this.pos + skip[0].length, nextCh = this.input.charCodeAt(next) - if (nextCh === 40) // '(' + if (nextCh === 40 || nextCh === 46) // '(' or '.' return this.parseExpressionStatement(node, this.parseExpression()) } diff --git a/bin/run_test262.js b/bin/run_test262.js index 4e0bc6b31..1521bbe15 100644 --- a/bin/run_test262.js +++ b/bin/run_test262.js @@ -11,7 +11,6 @@ const unsupportedFeatures = [ "class-static-fields-public", "class-static-methods-private", "coalesce-expression", - "import.meta", "numeric-separator-literal", "optional-chaining", "top-level-await" diff --git a/test/run.js b/test/run.js index 929360099..f0e89ac97 100644 --- a/test/run.js +++ b/test/run.js @@ -18,6 +18,7 @@ require("./tests-bigint.js"); require("./tests-dynamic-import.js"); require("./tests-export-all-as-ns-from-source.js"); + require("./tests-import-meta.js"); var acorn = require("../acorn") var acorn_loose = require("../acorn-loose") diff --git a/test/tests-dynamic-import.js b/test/tests-dynamic-import.js index 675a45365..92e52586f 100644 --- a/test/tests-dynamic-import.js +++ b/test/tests-dynamic-import.js @@ -204,7 +204,7 @@ test( { ecmaVersion: 11 } ); -testFail('function failsParse() { return import.then(); }', 'Unexpected token (1:37)', { +testFail('function failsParse() { return import.then(); }', 'The only valid meta property for import is \'import.meta\' (1:38)', { ecmaVersion: 11, loose: false }); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index e270abefa..e20bce8a6 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -14781,10 +14781,10 @@ test("function foo() { new.target; }", { sourceType: "script" }, {ecmaVersion: 6}); -testFail("new.prop", "The only valid meta property for new is new.target (1:4)", {ecmaVersion: 6}); -testFail("new.target", "new.target can only be used in functions (1:0)", {ecmaVersion: 6}); +testFail("new.prop", "The only valid meta property for new is 'new.target' (1:4)", {ecmaVersion: 6}); +testFail("new.target", "'new.target' can only be used in functions (1:0)", {ecmaVersion: 6}); test("function x() { return () => new.target }", {}, {ecmaVersion: 6}); -testFail("let y = () => new.target", "new.target can only be used in functions (1:14)", {ecmaVersion: 6}); +testFail("let y = () => new.target", "'new.target' can only be used in functions (1:14)", {ecmaVersion: 6}); test("export default function foo() {} false", { body: [ diff --git a/test/tests-import-meta.js b/test/tests-import-meta.js new file mode 100644 index 000000000..7f93d7a3a --- /dev/null +++ b/test/tests-import-meta.js @@ -0,0 +1,97 @@ +// Tests for ECMAScript 2020 `import.meta` + +if (typeof exports != 'undefined') { + var test = require('./driver.js').test; + var testFail = require('./driver.js').testFail; +} + +test( + "import.meta", + { + "type": "Program", + "start": 0, + "end": 11, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 11, + "expression": { + "type": "MetaProperty", + "start": 0, + "end": 11, + "meta": { + "type": "Identifier", + "start": 0, + "end": 6, + "name": "import" + }, + "property": { + "type": "Identifier", + "start": 7, + "end": 11, + "name": "meta" + } + } + } + ], + "sourceType": "module" + }, + { ecmaVersion: 11, sourceType: "module" } +); + +test( + "import.meta.url", + { + "type": "Program", + "start": 0, + "end": 15, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 15, + "expression": { + "type": "MemberExpression", + "start": 0, + "end": 15, + "object": { + "type": "MetaProperty", + "start": 0, + "end": 11, + "meta": { + "type": "Identifier", + "start": 0, + "end": 6, + "name": "import" + }, + "property": { + "type": "Identifier", + "start": 7, + "end": 11, + "name": "meta" + } + }, + "property": { + "type": "Identifier", + "start": 12, + "end": 15, + "name": "url" + }, + "computed": false + } + } + ], + "sourceType": "module" + }, + { ecmaVersion: 11, sourceType: "module" } +); + +testFail("import.meta", "Unexpected token (1:6)", { ecmaVersion: 10, sourceType: "module" }); +testFail("import.meta", "Cannot use 'import.meta' outside a module (1:0)", { ecmaVersion: 11, sourceType: "script" }); +testFail("import['meta']", "Unexpected token (1:6)", { ecmaVersion: 11, sourceType: "module" }); +testFail("a = import['meta']", "Unexpected token (1:10)", { ecmaVersion: 11, sourceType: "module" }); +testFail("import.target", "The only valid meta property for import is 'import.meta' (1:7)", { ecmaVersion: 11, sourceType: "module" }); +testFail("new.meta", "The only valid meta property for new is 'new.target' (1:4)", { ecmaVersion: 11, sourceType: "module" }); +testFail("im\\u0070ort.meta", "Escape sequence in keyword import (1:0)", { ecmaVersion: 11, sourceType: "module" }); +testFail("import.\\u006d\\u0065\\u0074\\u0061", "'import.meta' must not contain escaped characters (1:0)", { ecmaVersion: 11, sourceType: "module" }); diff --git a/test/tests.js b/test/tests.js index aec1739ac..0a0031484 100644 --- a/test/tests.js +++ b/test/tests.js @@ -29428,7 +29428,7 @@ test("(\\u0061sync ())", { }, {ecmaVersion: 8}) testFail("({ \\u0061sync x() { await x } })", "Unexpected token (1:14)", {ecmaVersion: 8}) testFail("for (x \\u006ff y) {}", "Unexpected token (1:7)", {ecmaVersion: 6}) -testFail("function x () { new.ta\\u0072get }", "The only valid meta property for new is new.target (1:20)", {ecmaVersion: 6}) +testFail("function x () { new.ta\\u0072get }", "'new.target' must not contain escaped characters (1:16)", {ecmaVersion: 6}) testFail("class X { st\\u0061tic y() {} }", "Unexpected token (1:22)", {ecmaVersion: 6}) testFail("(x=1)=2", "Parenthesized pattern (1:0)")