diff --git a/acorn.js b/acorn.js index 68b94cf405..77432428a9 100644 --- a/acorn.js +++ b/acorn.js @@ -243,6 +243,7 @@ this.label = label; this.keyword = conf.keyword; this.beforeExpr = !!conf.beforeExpr; + this.startsExpr = !!conf.startsExpr; this.rightAssociative = !!conf.rightAssociative; this.isLoop = !!conf.isLoop; this.isAssign = !!conf.isAssign; @@ -255,21 +256,21 @@ function binop(name, prec) { return new TokenType(name, {beforeExpr: true, binop: prec}); } - var beforeExpr = {beforeExpr: true}; + var beforeExpr = {beforeExpr: true}, startsExpr = {startsExpr: true};; var tt = exports.tokTypes = { - num: new TokenType("num"), - regexp: new TokenType("regexp"), - string: new TokenType("string"), - name: new TokenType("name"), + num: new TokenType("num", startsExpr), + regexp: new TokenType("regexp", startsExpr), + string: new TokenType("string", startsExpr), + name: new TokenType("name", startsExpr), eof: new TokenType("eof"), // Punctuation token types. - bracketL: new TokenType("[", beforeExpr), + bracketL: new TokenType("[", {beforeExpr: true, startsExpr: true}), bracketR: new TokenType("]"), - braceL: new TokenType("{", beforeExpr), + braceL: new TokenType("{", {beforeExpr: true, startsExpr: true}), braceR: new TokenType("}"), - parenL: new TokenType("(", beforeExpr), + parenL: new TokenType("(", {beforeExpr: true, startsExpr: true}), parenR: new TokenType(")"), comma: new TokenType(",", beforeExpr), semi: new TokenType(";", beforeExpr), @@ -279,8 +280,8 @@ arrow: new TokenType("=>", beforeExpr), template: new TokenType("template"), ellipsis: new TokenType("...", beforeExpr), - backQuote: new TokenType("`"), - dollarBraceL: new TokenType("${", beforeExpr), + backQuote: new TokenType("`", startsExpr), + dollarBraceL: new TokenType("${", {beforeExpr: true, startsExpr: true}), // Operators. These carry several kinds of properties to help the // parser use them properly (the presence of these properties is @@ -298,8 +299,8 @@ eq: new TokenType("=", {beforeExpr: true, isAssign: true}), assign: new TokenType("_=", {beforeExpr: true, isAssign: true}), - incDec: new TokenType("++/--", {prefix: true, postfix: true}), - prefix: new TokenType("prefix", {beforeExpr: true, prefix: true}), + incDec: new TokenType("++/--", {prefix: true, postfix: true, startsExpr: true}), + prefix: new TokenType("prefix", {beforeExpr: true, prefix: true, startsExpr: true}), logicalOR: binop("||", 1), logicalAND: binop("&&", 2), bitwiseOR: binop("|", 3), @@ -308,7 +309,7 @@ equality: binop("==/!=", 6), relational: binop("", 7), bitShift: binop("<>", 8), - plusMin: new TokenType("+/-", {beforeExpr: true, binop: 9, prefix: true}), + plusMin: new TokenType("+/-", {beforeExpr: true, binop: 9, prefix: true, startsExpr: true}), modulo: binop("%", 10), star: binop("*", 10), slash: binop("/", 10), @@ -347,22 +348,22 @@ kw("const"); kw("while", {isLoop: true}); kw("with"); - kw("new", beforeExpr); - kw("this"); - kw("super"); + kw("new", {beforeExpr: true, startsExpr: true}); + kw("this", startsExpr); + kw("super", startsExpr); kw("class"); kw("extends", beforeExpr); kw("export"); kw("import"); - kw("yield", beforeExpr); - kw("null"); - kw("true"); - kw("false"); + kw("yield", {beforeExpr: true, startsExpr: true}); + kw("null", startsExpr); + kw("true", startsExpr); + kw("false", startsExpr); kw("in", {beforeExpr: true, binop: 7}); kw("instanceof", {beforeExpr: true, binop: 7}); - kw("typeof", {beforeExpr: true, prefix: true}); - kw("void", {beforeExpr: true, prefix: true}); - kw("delete", {beforeExpr: true, prefix: true}); + kw("typeof", {beforeExpr: true, prefix: true, startsExpr: true}); + kw("void", {beforeExpr: true, prefix: true, startsExpr: true}); + kw("delete", {beforeExpr: true, prefix: true, startsExpr: true}); // This is a trick taken from Esprima. It turns out that, on // non-Chrome browsers, to check whether a string is in a set, a @@ -1125,13 +1126,15 @@ if (this.options.ecmaVersion >= 6) validFlags = /^[gmsiyu]*$/; if (!validFlags.test(mods)) this.raise(start, "Invalid regular expression flag"); if (mods.indexOf('u') >= 0 && !regexpUnicodeSupport) { - // Replace each astral symbol and every Unicode code point - // escape sequence that represents such a symbol with a single - // ASCII symbol to avoid throwing on regular expressions that + // Replace each astral symbol and every Unicode escape sequence that + // possibly represents an astral symbol or a paired surrogate with a + // single ASCII symbol to avoid throwing on regular expressions that // are only valid in combination with the `/u` flag. - tmp = tmp - .replace(/\\u\{([0-9a-fA-F]+)\}/g, "x") - .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "x"); + // Note: replacing with the ASCII symbol `x` might cause false + // negatives in unlikely scenarios. For example, `[\u{61}-b]` is a + // perfectly valid pattern that is equivalent to `[a-b]`, but it would + // be replaced by `[x-b]` which throws an error. + tmp = tmp.replace(/\\u([a-fA-F0-9]{4})|\\u\{([0-9a-fA-F]+)\}|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "x") } } // Detect invalid regular expressions. @@ -1604,24 +1607,23 @@ // Convert list of expression atoms to binding list. pp.toAssignableList = function(exprList, isBinding) { - if (exprList.length) { - for (var i = 0; i < exprList.length - 1; i++) { - this.toAssignable(exprList[i], isBinding); - } - var last = exprList[exprList.length - 1]; - switch (last.type) { - case "RestElement": - break; - case "SpreadElement": - last.type = "RestElement"; - var arg = last.argument; - this.toAssignable(arg, isBinding); - if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") - this.unexpected(arg.start); - break; - default: - this.toAssignable(last, isBinding); - } + var end = exprList.length; + if (end) { + var last = exprList[end - 1]; + if (last && last.type == "RestElement") { + --end; + } else if (last && last.type == "SpreadElement") { + last.type = "RestElement"; + var arg = last.argument; + this.toAssignable(arg, isBinding); + if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") + this.unexpected(arg.start); + --end; + } + } + for (var i = 0; i < end; i++) { + var elt = exprList[i]; + if (elt) this.toAssignable(elt, isBinding); } return exprList; }; @@ -2193,6 +2195,8 @@ decl.init = this.parseMaybeAssign(noIn); } else if (kind === tt._const && !(this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of")))) { this.unexpected(); + } else if (decl.id.type != "Identifier") { + this.raise(this.lastTokEnd, "Complex binding patterns require an initialization value"); } else { decl.init = null; } @@ -2634,12 +2638,10 @@ if (this.options.ecmaVersion >= 6) { prop.method = false; prop.shorthand = false; - if (isPattern || refShorthandDefaultPos) { + if (isPattern || refShorthandDefaultPos) start = this.currentPos(); - } - if (!isPattern) { + if (!isPattern) isGenerator = this.eat(tt.star); - } } if (this.options.features["es7.asyncFunctions"] && this.isContextual("async")) { if (isGenerator || isPattern) this.unexpected(); @@ -2680,6 +2682,10 @@ } else if (this.options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") { prop.kind = "init"; if (isPattern) { + if (this.isKeyword(prop.key.name) || + (this.strict && (isStrictBadIdWord(prop.key.name) || isStrictReservedWord(prop.key.name))) || + (!this.options.allowReserved && this.isReservedWord(prop.key.name))) + this.raise(prop.key.start, "Binding " + prop.key.name); prop.value = this.parseMaybeDefault(start, prop.key); } else if (this.type === tt.eq && refShorthandDefaultPos) { if (!refShorthandDefaultPos.start) @@ -3040,7 +3046,7 @@ pp.parseYield = function() { var node = this.startNode(); this.next(); - if (this.type == tt.semi || this.canInsertSemicolon()) { + if (this.type == tt.semi || this.canInsertSemicolon() || (this.type != tt.star && !this.type.startsExpr)) { node.delegate = false; node.argument = null; } else { diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 72154470c7..9399254a06 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -5989,6 +5989,53 @@ test("var x = { *test () { yield *v } };", { locations: true }); +test("function* foo() { console.log(yield); }", { + body: [ + { + id: { + name: "foo", + type: "Identifier", + }, + generator: true, + expression: false, + params: [], + body: { + body: [ + { + expression: { + callee: { + object: { + name: "console", + type: "Identifier", + }, + property: { + name: "log", + type: "Identifier", + }, + computed: false, + type: "MemberExpression", + }, + arguments: [ + { + delegate: false, + argument: null, + type: "YieldExpression", + } + ], + type: "CallExpression", + }, + type: "ExpressionStatement", + } + ], + type: "BlockStatement", + }, + type: "FunctionDeclaration", + } + ], + sourceType: "script", + type: "Program" +}, {ecmaVersion: 6}) + test("function* t() {}", { type: "Program", body: [{ @@ -13814,6 +13861,34 @@ test("/[a-z]/u", { ecmaVersion: 6 }); +test("/[\\uD834\\uDF06-\\uD834\\uDF08a-z]/u", { + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "Literal", + regex: { + pattern: "[\\uD834\\uDF06-\\uD834\\uDF08a-z]", + flags: "u" + }, + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 33 + } + } + } + } + ] +}, { + locations: true, + ecmaVersion: 6 +}); test("do {} while (false) foo();", { type: "Program", @@ -15419,6 +15494,36 @@ test("let {x} = y", { "end": 11 }, {ecmaVersion: 6}) +test("[x,,] = 1", { + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AssignmentExpression", + operator: "=", + left: { + type: "ArrayPattern", + elements: [ + { + type: "Identifier", + name: "x" + }, + null + ] + }, + right: { + type: "Literal", + value: 1, + raw: "1" + } + } + } + ] +}, {ecmaVersion: 6}); + +testFail("let [x]", "Complex binding patterns require an initialization value (1:7)", {ecmaVersion: 6}) +testFail("var [x]", "Complex binding patterns require an initialization value (1:7)", {ecmaVersion: 6}) testFail("var _𖫵 = 11;", "Unexpected character '𖫵' (1:5)", {ecmaVersion: 6}); testFail("var 𫠞_ = 12;", "Unexpected character '𫠞' (1:4)", {ecmaVersion: 6}); testFail("var 𫠝_ = 10;", "Unexpected character '𫠝' (1:4)", {ecmaVersion: 5}); @@ -15431,3 +15536,5 @@ testFail("'use strict'; [...eval] = arr", "Assigning to eval in strict mode (1:1 testFail("'use strict'; ({eval = defValue} = obj)", "Assigning to eval in strict mode (1:16)", {ecmaVersion: 6}); testFail("[...eval] = arr", "Assigning to eval in strict mode (1:4)", {ecmaVersion: 6, sourceType: "module"}); + +testFail("function* y({yield}) {}", "Binding yield (1:13)", {ecmaVersion: 6});