fix various bugs surfaced by the esprima test suite, remove some incorrect tests

This commit is contained in:
Sebastian McKenzie
2015-08-11 16:58:20 +01:00
parent b7b43dc282
commit 8887444cf7
45 changed files with 704 additions and 703 deletions

View File

@@ -186,7 +186,7 @@ pp.parseMaybeUnary = function (refShorthandDefaultPos) {
if (refShorthandDefaultPos && refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start);
if (update) {
this.checkLVal(node.argument);
} else if (this.strict && node.operator === "delete" && node.argument.type === "Identifier") {
} else if (this.state.strict && node.operator === "delete" && node.argument.type === "Identifier") {
this.raise(node.start, "Deleting local variable in strict mode");
}
return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
@@ -335,10 +335,8 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {
return this.finishNode(node, "ThisExpression");
case tt._yield:
// NOTE: falls through to _let
if (!this.state.inGenerator && this.strict) this.unexpected();
if (this.state.inGenerator) this.unexpected();
case tt._let:
case tt.name:
node = this.startNode();
let id = this.parseIdentifier(true);
@@ -363,7 +361,6 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {
return id;
case tt._do:
if (this.options.features["es7.doExpressions"]) {
let node = this.startNode();
@@ -683,7 +680,7 @@ pp.parseObjPropValue = function (prop, startPos, startLoc, isGenerator, isAsync,
prop.kind = "init";
if (isPattern) {
var illegalBinding = this.isKeyword(prop.key.name);
if (!illegalBinding && this.strict) {
if (!illegalBinding && this.state.strict) {
illegalBinding = reservedWords.strictBind(prop.key.name) || reservedWords.strict(prop.key.name);
}
if (illegalBinding) {
@@ -772,19 +769,29 @@ pp.parseFunctionBody = function (node, allowExpression) {
// If this is a strict mode function, verify that argument names
// are not repeated, and it does not try to bind the words `eval`
// or `arguments`.
var checkLVal = this.strict;
var checkLVal = this.state.strict;
var checkLValStrict = false;
// arrow function
if (allowExpression) checkLVal = true;
// normal function
if (!isExpression && node.body.body.length && this.isUseStrict(node.body.body[0])) checkLVal = true;
if (!isExpression && node.body.body.length && this.isUseStrict(node.body.body[0])) {
checkLVal = true;
checkLValStrict = true;
}
if (checkLVal) {
let nameHash = Object.create(null);
let oldStrict = this.state.strict;
if (checkLValStrict) this.state.strict = true;
if (node.id) {
this.checkLVal(node.id, true);
}
for (let param of (node.params: Array)) {
this.checkLVal(param, true, nameHash);
}
this.state.strict = oldStrict;
}
};
@@ -828,8 +835,8 @@ pp.parseExprListItem = function (allowEmpty, refShorthandDefaultPos) {
pp.parseIdentifier = function (liberal) {
let node = this.startNode();
if (this.isName()) {
if (!liberal && this.strict && reservedWords.strict(this.state.value)) {
if (this.match(tt.name)) {
if (!liberal && this.state.strict && reservedWords.strict(this.state.value)) {
this.raise(this.state.start, "The keyword '" + this.state.value + "' is reserved");
}
@@ -847,7 +854,7 @@ pp.parseIdentifier = function (liberal) {
// Parses await expression inside async function.
pp.parseAwait = function (node) {
if (this.eat(tt.semi) || this.canInsertSemicolon()) {
if (this.isLineTerminator()) {
this.unexpected();
}
node.all = this.eat(tt.star);

View File

@@ -1,4 +1,4 @@
import { reservedWords, isKeyword } from "../util/identifier";
import { reservedWords } from "../util/identifier";
import { getOptions } from "../options";
import Tokenizer from "../tokenizer";
@@ -8,17 +8,16 @@ export const plugins = {};
export default class Parser extends Tokenizer {
constructor(options, input) {
super(input);
options = getOptions(options);
super(options, input);
this.options = getOptions(options);
this.isKeyword = isKeyword;
this.options = options;
this.isReservedWord = reservedWords[6];
this.input = input;
this.loadPlugins(this.options.plugins);
// Figure out if it's a module code.
this.inModule = this.options.sourceType === "module";
this.strict = this.options.strictMode === false ? false : this.inModule;
// If enabled, skip leading hashbang line.
if (this.state.pos === 0 && this.input[0] === "#" && this.input[1] === "!") {

View File

@@ -92,7 +92,7 @@ pp.parseSpread = function (refShorthandDefaultPos) {
pp.parseRest = function () {
let node = this.startNode();
this.next();
if (this.isName() || this.match(tt.bracketL)) {
if (this.match(tt.name) || this.match(tt.bracketL)) {
node.argument = this.parseBindingAtom();
} else {
this.unexpected();
@@ -103,7 +103,7 @@ pp.parseRest = function () {
// Parses lvalue (assignable) atom.
pp.parseBindingAtom = function () {
if (this.isName()) {
if (this.match(tt.name)) {
return this.parseIdentifier(true);
}
@@ -168,7 +168,7 @@ pp.parseMaybeDefault = function (startPos, startLoc, left) {
pp.checkLVal = function (expr, isBinding, checkClashes) {
switch (expr.type) {
case "Identifier":
if (this.strict && (reservedWords.strictBind(expr.name) || reservedWords.strict(expr.name))) {
if (this.state.strict && (reservedWords.strictBind(expr.name) || reservedWords.strict(expr.name))) {
this.raise(expr.start, (isBinding ? "Binding " : "Assigning to ") + expr.name + " in strict mode");
}

View File

@@ -74,19 +74,6 @@ pp.parseStatement = function (declaration, topLevel) {
case tt._try: return this.parseTryStatement(node);
case tt._let:
// NOTE: falls through to _const
if (!this.strict) {
let state = this.state.clone();
this.next();
var isBindingAtomStart = this.isName() || this.match(tt.braceL) || this.match(tt.bracketL);
// set back lookahead
this.state = state;
if (!isBindingAtomStart) break;
}
case tt._const:
if (!declaration) this.unexpected(); // NOTE: falls through to _var
@@ -171,7 +158,7 @@ pp.parseBreakContinueStatement = function (node, keyword) {
let isBreak = keyword === "break";
this.next();
if (this.eat(tt.semi) || this.canInsertSemicolon()) {
if (this.isLineTerminator()) {
node.label = null;
} else if (!this.match(tt.name)) {
this.unexpected();
@@ -232,9 +219,13 @@ pp.parseForStatement = function (node) {
this.next();
this.parseVar(init, true, varKind);
this.finishNode(init, "VariableDeclaration");
if ((this.match(tt._in) || this.isContextual("of")) && init.declarations.length === 1 &&
!(varKind !== tt._var && init.declarations[0].init))
return this.parseForIn(node, init);
if (this.match(tt._in) || this.isContextual("of")) {
if (init.declarations.length === 1 && !init.declarations[0].init) {
return this.parseForIn(node, init);
}
}
return this.parseFor(node, init);
}
@@ -274,7 +265,7 @@ pp.parseReturnStatement = function (node) {
// optional arguments, we eagerly look for a semicolon or the
// possibility to insert one.
if (this.eat(tt.semi) || this.canInsertSemicolon()) {
if (this.isLineTerminator()) {
node.argument = null;
} else {
node.argument = this.parseExpression();
@@ -343,7 +334,7 @@ pp.parseTryStatement = function (node) {
this.next();
this.expect(tt.parenL);
clause.param = this.parseBindingAtom();
this.checkLVal(clause.param, true);
this.checkLVal(clause.param, true, Object.create(null));
this.expect(tt.parenR);
clause.body = this.parseBlock();
node.handler = this.finishNode(clause, "CatchClause");
@@ -376,7 +367,7 @@ pp.parseWhileStatement = function (node) {
};
pp.parseWithStatement = function (node) {
if (this.strict) this.raise(this.state.start, "'with' in strict mode");
if (this.state.strict) this.raise(this.state.start, "'with' in strict mode");
this.next();
node.object = this.parseParenExpression();
node.body = this.parseStatement(false);
@@ -431,8 +422,8 @@ pp.parseBlock = function (allowStrict) {
let stmt = this.parseStatement(true);
node.body.push(stmt);
if (first && allowStrict && this.isUseStrict(stmt)) {
oldStrict = this.strict;
this.setStrict(this.strict = true);
oldStrict = this.state.strict;
this.setStrict(this.state.strict = true);
}
first = false;
}
@@ -505,11 +496,11 @@ pp.parseFunction = function (node, isStatement, allowExpressionBody, isAsync, op
this.initFunction(node, isAsync);
node.generator = this.eat(tt.star);
if (isStatement && !optionalId && !this.isName()) {
if (isStatement && !optionalId && !this.match(tt.name)) {
this.unexpected();
}
if (this.isName()) {
if (this.match(tt.name)) {
node.id = this.parseIdentifier();
}

View File

@@ -28,23 +28,6 @@ pp.expectRelational = function (op) {
}
};
// TODO
pp.isName = function () {
if (this.match(tt.name)) {
return true;
} else if (!this.strict) {
var keyword = this.state.type.keyword;
if (keyword === "let") {
return true;
} else if (keyword === "yield") {
return !this.state.inGenerator;
}
}
return false;
};
// Tests whether parsed token is a contextual keyword.
pp.isContextual = function (name) {
@@ -71,6 +54,12 @@ pp.canInsertSemicolon = function () {
lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start));
};
// TODO
pp.isLineTerminator = function () {
return this.eat(tt.semi) || this.canInsertSemicolon();
};
// Consume a semicolon, or, failing that, see if we are allowed to
// pretend that there is a semicolon at this position.

View File

@@ -609,7 +609,7 @@ export default function (instance) {
instance.extend("parseStatement", function (inner) {
return function (declaration, topLevel) {
// strict mode handling of `interface` since it's a reserved word
if (this.strict && this.match(tt.name) && this.state.value === "interface") {
if (this.state.strict && this.match(tt.name) && this.state.value === "interface") {
var node = this.startNode();
this.next();
return this.flowParseInterface(node);

View File

@@ -1,4 +1,4 @@
import { isIdentifierStart, isIdentifierChar } from "../util/identifier";
import { isIdentifierStart, isIdentifierChar, isKeyword } from "../util/identifier";
import { types as tt, keywords as keywordTypes } from "./types";
import { types as ct } from "./context";
import { SourceLocation } from "../util/location";
@@ -48,9 +48,9 @@ function codePointToString(code) {
}
export default class Tokenizer {
constructor(input) {
constructor(options, input) {
this.state = new State;
this.state.init(input);
this.state.init(options, input);
}
// Move to the next token
@@ -84,6 +84,22 @@ export default class Tokenizer {
// TODO
isKeyword(word) {
if (!this.state.strict) {
if (word === "yield" && !this.state.inGenerator) {
return false;
}
if (word === "let") {
// check if next token is a name, braceL or bracketL, if so, it's a keyword!
}
}
return isKeyword(word);
}
// TODO
lookahead() {
var old = this.state;
this.state = old.clone();
@@ -97,7 +113,7 @@ export default class Tokenizer {
// pedantic tests (`"use strict"; 010;` should fail).
setStrict(strict) {
this.strict = strict;
this.state.strict = strict;
if (!this.match(tt.num) && !this.match(tt.string)) return;
this.state.pos = this.state.start;
while (this.state.pos < this.state.lineStart) {
@@ -589,7 +605,7 @@ export default class Tokenizer {
val = parseFloat(str);
} else if (!octal || str.length === 1) {
val = parseInt(str, 10);
} else if (/[89]/.test(str) || this.strict) {
} else if (/[89]/.test(str) || this.state.strict) {
this.raise(start, "Invalid number");
} else {
val = parseInt(str, 8);
@@ -705,7 +721,7 @@ export default class Tokenizer {
octalStr = octalStr.slice(0, -1);
octal = parseInt(octalStr, 8);
}
if (octal > 0 && (this.strict || inTemplate)) {
if (octal > 0 && (this.state.strict || inTemplate)) {
this.raise(this.state.pos - 2, "Octal literal in strict mode");
}
this.state.pos += octalStr.length - 1;
@@ -769,8 +785,9 @@ export default class Tokenizer {
readWord() {
let word = this.readWord1();
let type = tt.name;
if (!this.state.containsEsc && this.isKeyword(word))
if (!this.state.containsEsc && this.isKeyword(word)) {
type = keywordTypes[word];
}
return this.finishToken(type, word);
}

View File

@@ -3,7 +3,10 @@ import { types as ct } from "./context";
import { types as tt } from "./types";
export default class State {
init(input) {
init(options, input) {
// strict
this.strict = options.strictMode === false ? false : options.sourceType === "module";
this.input = input;
// Used to signify the start of a potential arrow function