From 5b5d27c9b8841ff2c2e7f8a8b79fc8f17ad868a1 Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Sat, 20 Jun 2015 23:28:49 +0100 Subject: [PATCH] resync with upstream acorn --- src/expression.js | 29 ++++++++++--- src/lval.js | 7 +++- src/state.js | 3 +- src/statement.js | 52 ++++++++++++++++------- src/tokenize.js | 102 ++++++++++++++++++++++++++-------------------- 5 files changed, 127 insertions(+), 66 deletions(-) diff --git a/src/expression.js b/src/expression.js index cc5667f815..dccf9962a8 100755 --- a/src/expression.js +++ b/src/expression.js @@ -29,14 +29,23 @@ const pp = Parser.prototype // strict mode, init properties are also not allowed to be repeated. pp.checkPropClash = function(prop, propHash) { - if (this.options.ecmaVersion >= 6) return + if (this.options.ecmaVersion >= 6 && (prop.computed || prop.method || prop.shorthand)) + return let key = prop.key, name switch (key.type) { case "Identifier": name = key.name; break case "Literal": name = String(key.value); break default: return } - let kind = prop.kind || "init", other + let kind = prop.kind + if (this.options.ecmaVersion >= 6) { + if (name === "__proto__" && kind === "init") { + if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property"); + propHash.proto = true + } + return + } + let other if (has(propHash, name)) { other = propHash[name] let isGetSet = kind !== "init" @@ -260,8 +269,10 @@ pp.parseNoCallExpr = function() { pp.parseExprAtom = function(refShorthandDefaultPos) { let node, canBeArrow = this.potentialArrowAt == this.start switch (this.type) { - case tt._this: case tt._super: + if (!this.inFunction) + this.raise(this.start, "'super' outside of function or class") + case tt._this: let type = this.type === tt._this ? "ThisExpression" : "Super" node = this.startNode() this.next() @@ -609,6 +620,14 @@ pp.parseObjPropValue = function (prop, start, isGenerator, isAsync, isPattern, r prop.kind = prop.key.name this.parsePropertyName(prop) prop.value = this.parseMethod(false) + let paramCount = prop.kind === "get" ? 0 : 1 + if (prop.value.params.length !== paramCount) { + let start = prop.value.start + if (prop.kind === "get") + this.raise(start, "getter should have no params"); + else + this.raise(start, "setter should have exactly one param") + } } else if (this.options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") { prop.kind = "init" if (isPattern) { @@ -634,12 +653,12 @@ pp.parsePropertyName = function(prop) { prop.computed = true prop.key = this.parseMaybeAssign() this.expect(tt.bracketR) - return + return prop.key } else { prop.computed = false } } - prop.key = (this.type === tt.num || this.type === tt.string) ? this.parseExprAtom() : this.parseIdent(true) + return prop.key = (this.type === tt.num || this.type === tt.string) ? this.parseExprAtom() : this.parseIdent(true) } // Initialize empty function node. diff --git a/src/lval.js b/src/lval.js index 1cf1371a85..a7718736c6 100755 --- a/src/lval.js +++ b/src/lval.js @@ -35,6 +35,7 @@ pp.toAssignable = function(node, isBinding) { case "AssignmentExpression": if (node.operator === "=") { node.type = "AssignmentPattern" + delete node.operator } else { this.raise(node.left.end, "Only '=' operator can be used for specifying default value.") } @@ -171,7 +172,7 @@ pp.checkLVal = function(expr, isBinding, checkClashes) { break case "ObjectPattern": - for (let i = 0; i < expr.properties.length; i++) { + for (let i = 0; i < expr.properties.length; i++) { var prop = expr.properties[i]; if (prop.type === "Property") prop = prop.value; this.checkLVal(prop, isBinding, checkClashes) @@ -194,6 +195,10 @@ pp.checkLVal = function(expr, isBinding, checkClashes) { this.checkLVal(expr.argument, isBinding, checkClashes) break + case "ParenthesizedExpression": + this.checkLVal(expr.expression, isBinding, checkClashes) + break + default: this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " rvalue") } diff --git a/src/state.js b/src/state.js index bebb6fdf31..c4f6d9292b 100755 --- a/src/state.js +++ b/src/state.js @@ -1,5 +1,6 @@ import {reservedWords, keywords} from "./identifier" -import {types as tt, lineBreak} from "./tokentype" +import {types as tt} from "./tokentype" +import {lineBreak} from "./whitespace" export function Parser(options, input, startPos) { this.options = options diff --git a/src/statement.js b/src/statement.js index 140f349a16..30940130fa 100755 --- a/src/statement.js +++ b/src/statement.js @@ -347,7 +347,14 @@ pp.parseLabeledStatement = function(node, maybeName, expr) { for (let i = 0; i < this.labels.length; ++i) if (this.labels[i].name === maybeName) this.raise(expr.start, "Label '" + maybeName + "' is already declared") let kind = this.type.isLoop ? "loop" : this.type === tt._switch ? "switch" : null - this.labels.push({name: maybeName, kind: kind}) + for (let i = this.labels.length - 1; i >= 0; i--) { + let label = this.labels[i] + if (label.statementStart == node.start) { + label.statementStart = this.start; + label.kind = kind; + } else break; + } + this.labels.push({name: maybeName, kind: kind, statementStart: this.start}) node.body = this.parseStatement(true) this.labels.pop() node.label = expr @@ -466,6 +473,7 @@ pp.parseClass = function(node, isStatement) { this.parseClassId(node, isStatement) this.parseClassSuper(node) var classBody = this.startNode() + let hadConstructor = false classBody.body = [] this.expect(tt.braceL) let decorators = [] @@ -480,16 +488,14 @@ pp.parseClass = function(node, isStatement) { method.decorators = decorators decorators = [] } + let isMaybeStatic = this.type === tt.name && this.value === "static" var isGenerator = this.eat(tt.star), isAsync = false this.parsePropertyName(method) - if (this.type !== tt.parenL && !method.computed && method.key.type === "Identifier" && - method.key.name === "static") { + method.static = isMaybeStatic && this.type !== tt.parenL + if (method.static) { if (isGenerator) this.unexpected() - method['static'] = true isGenerator = this.eat(tt.star) this.parsePropertyName(method) - } else { - method['static'] = false } if (!isGenerator && method.key.type === "Identifier" && !method.computed && this.isClassProperty()) { classBody.body.push(this.parseClassProperty(method)) @@ -500,23 +506,39 @@ pp.parseClass = function(node, isStatement) { isAsync = true this.parsePropertyName(method) } + let isGetSet = false method.kind = "method" - if (!method.computed && !isGenerator && !isAsync) { - if (method.key.type === "Identifier") { - if (this.type !== tt.parenL && (method.key.name === "get" || method.key.name === "set")) { - method.kind = method.key.name - this.parsePropertyName(method) - } else if (!method['static'] && method.key.name === "constructor") { - method.kind = "constructor" - } - } else if (!method['static'] && method.key.type === "Literal" && method.key.value === "constructor") { + if (!method.computed) { + let {key} = method + if (!isAsync && !isGenerator && key.type === "Identifier" && this.type !== tt.parenL && (key.name === "get" || key.name === "set")) { + isGetSet = true + method.kind = key.name + key = this.parsePropertyName(method) + } + if (!method.static && (key.type === "Identifier" && key.name === "constructor" || + key.type === "Literal" && key.value === "constructor")) { + if (hadConstructor) this.raise(key.start, "Duplicate constructor in the same class") + if (isGetSet) this.raise(key.start, "Constructor can't have get/set modifier") + if (isGenerator) this.raise(key.start, "Constructor can't be a generator") + if (isAsync) this.raise(key.start, "Constructor can't be an async function") method.kind = "constructor" + hadConstructor = true } } if (method.kind === "constructor" && method.decorators) { this.raise(method.start, "You can't attach decorators to a class constructor") } this.parseClassMethod(classBody, method, isGenerator, isAsync) + if (isGetSet) { + let paramCount = method.kind === "get" ? 0 : 1 + if (method.value.params.length !== paramCount) { + let start = method.value.start + if (method.kind === "get") + this.raise(start, "getter should have no params"); + else + this.raise(start, "setter should have exactly one param") + } + } } if (decorators.length) { this.raise(this.start, "You have trailing decorators with no method"); diff --git a/src/tokenize.js b/src/tokenize.js index 8d34c43a24..d9c22d5d04 100755 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -25,6 +25,9 @@ export class Token { const pp = Parser.prototype +// Are we running under Rhino? +const isRhino = typeof Packages == "object" && Object.prototype.toString.call(Packages) == "[object JavaPackage]" + // Move to the next token pp.next = function() { @@ -430,23 +433,30 @@ pp.readRegexp = function() { // 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") + tmp = tmp.replace(/\\u\{([0-9a-fA-F]+)\}/g, (match, code, offset) => { + code = Number("0x" + code) + if (code > 0x10FFFF) this.raise(start + offset + 3, "Code point out of bounds") + return "x" + }); + tmp = tmp.replace(/\\u([a-fA-F0-9]{4})|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "x") } } // Detect invalid regular expressions. - try { - new RegExp(tmp) - } catch (e) { - if (e instanceof SyntaxError) this.raise(start, "Error parsing regular expression: " + e.message) - this.raise(e) - } - // Get a regular expression object for this pattern-flag pair, or `null` in - // case the current environment doesn't support the flags it uses. - let value - try { - value = new RegExp(content, mods) - } catch (err) { - value = null + let value = null + // Rhino's regular expression parser is flaky and throws uncatchable exceptions, + // so don't do detection if we are running under Rhino + if (!isRhino) { + try { + new RegExp(tmp) + } catch (e) { + if (e instanceof SyntaxError) this.raise(start, "Error parsing regular expression: " + e.message) + this.raise(e) + } + // Get a regular expression object for this pattern-flag pair, or `null` in + // case the current environment doesn't support the flags it uses. + try { + value = new RegExp(content, mods) + } catch (err) {} } return this.finishToken(tt.regexp, {pattern: content, flags: mods, value: value}) } @@ -514,10 +524,10 @@ pp.readCodePoint = function() { if (ch === 123) { if (this.options.ecmaVersion < 6) this.unexpected() - ++this.pos + let codePos = ++this.pos code = this.readHexChar(this.input.indexOf('}', this.pos) - this.pos) ++this.pos - if (code > 0x10FFFF) this.unexpected() + if (code > 0x10FFFF) this.raise(codePos, "Code point out of bounds") } else { code = this.readHexChar(4) } @@ -539,7 +549,7 @@ pp.readString = function(quote) { if (ch === quote) break if (ch === 92) { // '\' out += this.input.slice(chunkStart, this.pos) - out += this.readEscapedChar() + out += this.readEscapedChar(false) chunkStart = this.pos } else { if (isNewLine(ch)) this.raise(this.start, "Unterminated string constant") @@ -572,7 +582,7 @@ pp.readTmplToken = function() { } if (ch === 92) { // '\' out += this.input.slice(chunkStart, this.pos) - out += this.readEscapedChar() + out += this.readEscapedChar(true) chunkStart = this.pos } else if (isNewLine(ch)) { out += this.input.slice(chunkStart, this.pos) @@ -600,42 +610,46 @@ pp.readTmplToken = function() { // Used to read escaped characters -pp.readEscapedChar = function() { +pp.readEscapedChar = function(inTemplate) { let ch = this.input.charCodeAt(++this.pos) - let octal = /^[0-7]+/.exec(this.input.slice(this.pos, this.pos + 3)) - if (octal) octal = octal[0] - while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1) - if (octal === "0") octal = null ++this.pos - if (octal) { - if (this.strict) this.raise(this.pos - 2, "Octal literal in strict mode") - this.pos += octal.length - 1 - return String.fromCharCode(parseInt(octal, 8)) - } else { - switch (ch) { - case 110: return "\n"; // 'n' -> '\n' - case 114: return "\r"; // 'r' -> '\r' - case 120: return String.fromCharCode(this.readHexChar(2)); // 'x' - case 117: return codePointToString(this.readCodePoint()); // 'u' - case 116: return "\t"; // 't' -> '\t' - case 98: return "\b"; // 'b' -> '\b' - case 118: return "\u000b"; // 'v' -> '\u000b' - case 102: return "\f"; // 'f' -> '\f' - case 48: return "\0"; // 0 -> '\0' - case 13: if (this.input.charCodeAt(this.pos) === 10) ++this.pos; // '\r\n' - case 10: // ' \n' - if (this.options.locations) { this.lineStart = this.pos; ++this.curLine } - return "" - default: return String.fromCharCode(ch) + switch (ch) { + case 110: return "\n"; // 'n' -> '\n' + case 114: return "\r"; // 'r' -> '\r' + case 120: return String.fromCharCode(this.readHexChar(2)); // 'x' + case 117: return codePointToString(this.readCodePoint()); // 'u' + case 116: return "\t"; // 't' -> '\t' + case 98: return "\b"; // 'b' -> '\b' + case 118: return "\u000b"; // 'v' -> '\u000b' + case 102: return "\f"; // 'f' -> '\f' + case 13: if (this.input.charCodeAt(this.pos) === 10) ++this.pos; // '\r\n' + case 10: // ' \n' + if (this.options.locations) { this.lineStart = this.pos; ++this.curLine } + return "" + default: + if (ch >= 48 && ch <= 55) { + let octalStr = this.input.substr(this.pos - 1, 3).match(/^[0-7]+/)[0] + let octal = parseInt(octalStr, 8) + if (octal > 255) { + octalStr = octalStr.slice(0, -1) + octal = parseInt(octalStr, 8) + } + if (octal > 0 && (this.strict || inTemplate)) { + this.raise(this.pos - 2, "Octal literal in strict mode") + } + this.pos += octalStr.length - 1 + return String.fromCharCode(octal) } + return String.fromCharCode(ch) } } // Used to read character escape sequences ('\x', '\u', '\U'). pp.readHexChar = function(len) { + let codePos = this.pos let n = this.readInt(16, len) - if (n === null) this.raise(this.start, "Bad character escape sequence") + if (n === null) this.raise(codePos, "Bad character escape sequence") return n }