Convert each file with parser methods to a class in an inheritance chain (#481)

This commit is contained in:
Andy 2017-04-23 15:40:49 -07:00 committed by Daniel Tschinder
parent 2c1193b7da
commit c4fb3fe742
10 changed files with 2487 additions and 2483 deletions

24
src/parser/base.js Normal file
View File

@ -0,0 +1,24 @@
// @flow
import type { Options } from "../options";
import { reservedWords } from "../util/identifier";
export default class BaseParser {
// Properties set by constructor in index.js
options: Options;
inModule: boolean;
plugins: { [key: string]: boolean };
filename: ?string;
isReservedWord(word: string): boolean {
if (word === "await") {
return this.inModule;
} else {
return reservedWords[6](word);
}
}
hasPlugin(name: string): boolean {
return !!this.plugins[name];
}
}

View File

@ -24,133 +24,133 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Parser from "./index";
import BaseParser from "./base";
function last(stack) {
return stack[stack.length - 1];
}
const pp = Parser.prototype;
pp.addComment = function (comment) {
if (this.filename) comment.loc.filename = this.filename;
this.state.trailingComments.push(comment);
this.state.leadingComments.push(comment);
};
pp.processComment = function (node) {
if (node.type === "Program" && node.body.length > 0) return;
const stack = this.state.commentStack;
let lastChild, trailingComments, i, j;
if (this.state.trailingComments.length > 0) {
// If the first comment in trailingComments comes after the
// current node, then we're good - all comments in the array will
// come after the node and so it's safe to add them as official
// trailingComments.
if (this.state.trailingComments[0].start >= node.end) {
trailingComments = this.state.trailingComments;
this.state.trailingComments = [];
} else {
// Otherwise, if the first comment doesn't come after the
// current node, that means we have a mix of leading and trailing
// comments in the array and that leadingComments contains the
// same items as trailingComments. Reset trailingComments to
// zero items and we'll handle this by evaluating leadingComments
// later.
this.state.trailingComments.length = 0;
}
} else {
const lastInStack = last(stack);
if (stack.length > 0 && lastInStack.trailingComments && lastInStack.trailingComments[0].start >= node.end) {
trailingComments = lastInStack.trailingComments;
lastInStack.trailingComments = null;
}
export default class CommentsParser extends BaseParser {
addComment(comment) {
if (this.filename) comment.loc.filename = this.filename;
this.state.trailingComments.push(comment);
this.state.leadingComments.push(comment);
}
// Eating the stack.
while (stack.length > 0 && last(stack).start >= node.start) {
lastChild = stack.pop();
}
processComment(node) {
if (node.type === "Program" && node.body.length > 0) return;
if (lastChild) {
if (lastChild.leadingComments) {
if (lastChild !== node && last(lastChild.leadingComments).end <= node.start) {
node.leadingComments = lastChild.leadingComments;
lastChild.leadingComments = null;
const stack = this.state.commentStack;
let lastChild, trailingComments, i, j;
if (this.state.trailingComments.length > 0) {
// If the first comment in trailingComments comes after the
// current node, then we're good - all comments in the array will
// come after the node and so it's safe to add them as official
// trailingComments.
if (this.state.trailingComments[0].start >= node.end) {
trailingComments = this.state.trailingComments;
this.state.trailingComments = [];
} else {
// A leading comment for an anonymous class had been stolen by its first ClassMethod,
// so this takes back the leading comment.
// See also: https://github.com/eslint/espree/issues/158
for (i = lastChild.leadingComments.length - 2; i >= 0; --i) {
if (lastChild.leadingComments[i].end <= node.start) {
node.leadingComments = lastChild.leadingComments.splice(0, i + 1);
// Otherwise, if the first comment doesn't come after the
// current node, that means we have a mix of leading and trailing
// comments in the array and that leadingComments contains the
// same items as trailingComments. Reset trailingComments to
// zero items and we'll handle this by evaluating leadingComments
// later.
this.state.trailingComments.length = 0;
}
} else {
const lastInStack = last(stack);
if (stack.length > 0 && lastInStack.trailingComments && lastInStack.trailingComments[0].start >= node.end) {
trailingComments = lastInStack.trailingComments;
lastInStack.trailingComments = null;
}
}
// Eating the stack.
while (stack.length > 0 && last(stack).start >= node.start) {
lastChild = stack.pop();
}
if (lastChild) {
if (lastChild.leadingComments) {
if (lastChild !== node && last(lastChild.leadingComments).end <= node.start) {
node.leadingComments = lastChild.leadingComments;
lastChild.leadingComments = null;
} else {
// A leading comment for an anonymous class had been stolen by its first ClassMethod,
// so this takes back the leading comment.
// See also: https://github.com/eslint/espree/issues/158
for (i = lastChild.leadingComments.length - 2; i >= 0; --i) {
if (lastChild.leadingComments[i].end <= node.start) {
node.leadingComments = lastChild.leadingComments.splice(0, i + 1);
break;
}
}
}
}
} else if (this.state.leadingComments.length > 0) {
if (last(this.state.leadingComments).end <= node.start) {
if (this.state.commentPreviousNode) {
for (j = 0; j < this.state.leadingComments.length; j++) {
if (this.state.leadingComments[j].end < this.state.commentPreviousNode.end) {
this.state.leadingComments.splice(j, 1);
j--;
}
}
}
if (this.state.leadingComments.length > 0) {
node.leadingComments = this.state.leadingComments;
this.state.leadingComments = [];
}
} else {
// https://github.com/eslint/espree/issues/2
//
// In special cases, such as return (without a value) and
// debugger, all comments will end up as leadingComments and
// will otherwise be eliminated. This step runs when the
// commentStack is empty and there are comments left
// in leadingComments.
//
// This loop figures out the stopping point between the actual
// leading and trailing comments by finding the location of the
// first comment that comes after the given node.
for (i = 0; i < this.state.leadingComments.length; i++) {
if (this.state.leadingComments[i].end > node.start) {
break;
}
}
}
}
} else if (this.state.leadingComments.length > 0) {
if (last(this.state.leadingComments).end <= node.start) {
if (this.state.commentPreviousNode) {
for (j = 0; j < this.state.leadingComments.length; j++) {
if (this.state.leadingComments[j].end < this.state.commentPreviousNode.end) {
this.state.leadingComments.splice(j, 1);
j--;
}
}
}
if (this.state.leadingComments.length > 0) {
node.leadingComments = this.state.leadingComments;
this.state.leadingComments = [];
}
} else {
// https://github.com/eslint/espree/issues/2
//
// In special cases, such as return (without a value) and
// debugger, all comments will end up as leadingComments and
// will otherwise be eliminated. This step runs when the
// commentStack is empty and there are comments left
// in leadingComments.
//
// This loop figures out the stopping point between the actual
// leading and trailing comments by finding the location of the
// first comment that comes after the given node.
for (i = 0; i < this.state.leadingComments.length; i++) {
if (this.state.leadingComments[i].end > node.start) {
break;
// Split the array based on the location of the first comment
// that comes after the node. Keep in mind that this could
// result in an empty array, and if so, the array must be
// deleted.
node.leadingComments = this.state.leadingComments.slice(0, i);
if ((node.leadingComments: Array<any>).length === 0) {
node.leadingComments = null;
}
// Similarly, trailing comments are attached later. The variable
// must be reset to null if there are no trailing comments.
trailingComments = this.state.leadingComments.slice(i);
if (trailingComments.length === 0) {
trailingComments = null;
}
}
}
// Split the array based on the location of the first comment
// that comes after the node. Keep in mind that this could
// result in an empty array, and if so, the array must be
// deleted.
node.leadingComments = this.state.leadingComments.slice(0, i);
if ((node.leadingComments: Array<any>).length === 0) {
node.leadingComments = null;
}
this.state.commentPreviousNode = node;
// Similarly, trailing comments are attached later. The variable
// must be reset to null if there are no trailing comments.
trailingComments = this.state.leadingComments.slice(i);
if (trailingComments.length === 0) {
trailingComments = null;
if (trailingComments) {
if (trailingComments.length && trailingComments[0].start >= node.start && last(trailingComments).end <= node.end) {
node.innerComments = trailingComments;
} else {
node.trailingComments = trailingComments;
}
}
stack.push(node);
}
this.state.commentPreviousNode = node;
if (trailingComments) {
if (trailingComments.length && trailingComments[0].start >= node.start && last(trailingComments).end <= node.end) {
node.innerComments = trailingComments;
} else {
node.trailingComments = trailingComments;
}
}
stack.push(node);
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,14 @@
import { reservedWords } from "../util/identifier";
// @flow
import type { Options } from "../options";
import type { File } from "../types";
import { getOptions } from "../options";
import Tokenizer from "../tokenizer";
import StatementParser from "./statement";
export const plugins = {};
export default class Parser extends Tokenizer {
constructor(options: Object, input: string) {
export default class Parser extends StatementParser {
constructor(options: Options, input: string) {
options = getOptions(options);
super(options, input);
@ -21,25 +24,7 @@ export default class Parser extends Tokenizer {
}
}
isReservedWord(word: string): boolean {
if (word === "await") {
return this.inModule;
} else {
return reservedWords[6](word);
}
}
hasPlugin(name: string): boolean {
return !!this.plugins[name];
}
parse(): {
type: "File",
program: {
type: "Program",
body: Array<Object>
}
} {
parse(): File {
const file = this.startNode();
const program = this.startNode();
this.nextToken();

View File

@ -1,7 +1,5 @@
import { getLineInfo } from "../util/location";
import Parser from "./index";
const pp = Parser.prototype;
import CommentsParser from "./comments";
// This function is used to raise exceptions on parse errors. It
// takes an offset integer (into the current `input`) to indicate
@ -9,11 +7,13 @@ const pp = Parser.prototype;
// of the error message, and then raises a `SyntaxError` with that
// message.
pp.raise = function (pos, message) {
const loc = getLineInfo(this.input, pos);
message += ` (${loc.line}:${loc.column})`;
const err = new SyntaxError(message);
err.pos = pos;
err.loc = loc;
throw err;
};
export default class LocationParser extends CommentsParser {
raise(pos, message) {
const loc = getLineInfo(this.input, pos);
message += ` (${loc.line}:${loc.column})`;
const err = new SyntaxError(message);
err.pos = pos;
err.loc = loc;
throw err;
}
}

View File

@ -1,259 +1,260 @@
import { types as tt } from "../tokenizer/types";
import Parser from "./index";
import { NodeUtils } from "./node";
const pp = Parser.prototype;
export default class LValParser extends NodeUtils {
// Convert existing expression atom to assignable pattern
// if possible.
// Convert existing expression atom to assignable pattern
// if possible.
toAssignable(node, isBinding, contextDescription) {
if (node) {
switch (node.type) {
case "Identifier":
case "ObjectPattern":
case "ArrayPattern":
case "AssignmentPattern":
break;
pp.toAssignable = function (node, isBinding, contextDescription) {
if (node) {
switch (node.type) {
case "Identifier":
case "ObjectPattern":
case "ArrayPattern":
case "AssignmentPattern":
break;
case "ObjectExpression":
node.type = "ObjectPattern";
for (const prop of (node.properties: Array<Object>)) {
if (prop.type === "ObjectMethod") {
if (prop.kind === "get" || prop.kind === "set") {
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
case "ObjectExpression":
node.type = "ObjectPattern";
for (const prop of (node.properties: Array<Object>)) {
if (prop.type === "ObjectMethod") {
if (prop.kind === "get" || prop.kind === "set") {
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
} else {
this.raise(prop.key.start, "Object pattern can't contain methods");
}
} else {
this.raise(prop.key.start, "Object pattern can't contain methods");
this.toAssignable(prop, isBinding, "object destructuring pattern");
}
}
break;
case "ObjectProperty":
this.toAssignable(node.value, isBinding, contextDescription);
break;
case "SpreadElement":
node.type = "RestElement";
break;
case "ArrayExpression":
node.type = "ArrayPattern";
this.toAssignableList(node.elements, isBinding, contextDescription);
break;
case "AssignmentExpression":
if (node.operator === "=") {
node.type = "AssignmentPattern";
delete node.operator;
} else {
this.toAssignable(prop, isBinding, "object destructuring pattern");
this.raise(node.left.end, "Only '=' operator can be used for specifying default value.");
}
break;
case "MemberExpression":
if (!isBinding) break;
default: {
const message = "Invalid left-hand side" +
(contextDescription ? " in " + contextDescription : /* istanbul ignore next */ "expression");
this.raise(node.start, message);
}
}
}
return node;
}
// Convert list of expression atoms to binding list.
toAssignableList(exprList, isBinding, contextDescription) {
let end = exprList.length;
if (end) {
const last = exprList[end - 1];
if (last && last.type === "RestElement") {
--end;
} else if (last && last.type === "SpreadElement") {
last.type = "RestElement";
const arg = last.argument;
this.toAssignable(arg, isBinding, contextDescription);
if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") {
this.unexpected(arg.start);
}
--end;
}
}
for (let i = 0; i < end; i++) {
const elt = exprList[i];
if (elt && elt.type === "SpreadElement")
this.raise(elt.start, "The rest element has to be the last element when destructuring");
if (elt) this.toAssignable(elt, isBinding, contextDescription);
}
return exprList;
}
// Convert list of expression atoms to a list of
toReferencedList(exprList) {
return exprList;
}
// Parses spread element.
parseSpread(refShorthandDefaultPos) {
const node = this.startNode();
this.next();
node.argument = this.parseMaybeAssign(false, refShorthandDefaultPos);
return this.finishNode(node, "SpreadElement");
}
parseRest() {
const node = this.startNode();
this.next();
node.argument = this.parseBindingAtom();
return this.finishNode(node, "RestElement");
}
shouldAllowYieldIdentifier() {
return this.match(tt._yield) && !this.state.strict && !this.state.inGenerator;
}
parseBindingIdentifier() {
return this.parseIdentifier(this.shouldAllowYieldIdentifier());
}
// Parses lvalue (assignable) atom.
parseBindingAtom() {
switch (this.state.type) {
case tt._yield:
case tt.name:
return this.parseBindingIdentifier();
case tt.bracketL:
const node = this.startNode();
this.next();
node.elements = this.parseBindingList(tt.bracketR, true);
return this.finishNode(node, "ArrayPattern");
case tt.braceL:
return this.parseObj(true);
default:
this.unexpected();
}
}
parseBindingList(close, allowEmpty) {
const elts = [];
let first = true;
while (!this.eat(close)) {
if (first) {
first = false;
} else {
this.expect(tt.comma);
}
if (allowEmpty && this.match(tt.comma)) {
elts.push(null);
} else if (this.eat(close)) {
break;
} else if (this.match(tt.ellipsis)) {
elts.push(this.parseAssignableListItemTypes(this.parseRest()));
this.expect(close);
break;
} else {
const decorators = [];
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
const left = this.parseMaybeDefault();
if (decorators.length) {
left.decorators = decorators;
}
this.parseAssignableListItemTypes(left);
elts.push(this.parseMaybeDefault(left.start, left.loc.start, left));
}
}
return elts;
}
parseAssignableListItemTypes(param) {
return param;
}
// Parses assignment pattern around given atom if possible.
parseMaybeDefault(startPos, startLoc, left) {
startLoc = startLoc || this.state.startLoc;
startPos = startPos || this.state.start;
left = left || this.parseBindingAtom();
if (!this.eat(tt.eq)) return left;
const node = this.startNodeAt(startPos, startLoc);
node.left = left;
node.right = this.parseMaybeAssign();
return this.finishNode(node, "AssignmentPattern");
}
// Verify that a node is an lval — something that can be assigned
// to.
checkLVal(expr, isBinding, checkClashes, contextDescription) {
switch (expr.type) {
case "Identifier":
this.checkReservedWord(expr.name, expr.start, false, true);
if (checkClashes) {
// we need to prefix this with an underscore for the cases where we have a key of
// `__proto__`. there's a bug in old V8 where the following wouldn't work:
//
// > var obj = Object.create(null);
// undefined
// > obj.__proto__
// null
// > obj.__proto__ = true;
// true
// > obj.__proto__
// null
const key = `_${expr.name}`;
if (checkClashes[key]) {
this.raise(expr.start, "Argument name clash in strict mode");
} else {
checkClashes[key] = true;
}
}
break;
case "ObjectProperty":
this.toAssignable(node.value, isBinding, contextDescription);
case "MemberExpression":
if (isBinding)
this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " member expression");
break;
case "SpreadElement":
node.type = "RestElement";
break;
case "ArrayExpression":
node.type = "ArrayPattern";
this.toAssignableList(node.elements, isBinding, contextDescription);
break;
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.");
case "ObjectPattern":
for (let prop of (expr.properties: Array<Object>)) {
if (prop.type === "ObjectProperty") prop = prop.value;
this.checkLVal(prop, isBinding, checkClashes, "object destructuring pattern");
}
break;
case "MemberExpression":
if (!isBinding) break;
case "ArrayPattern":
for (const elem of (expr.elements: Array<Object>)) {
if (elem) this.checkLVal(elem, isBinding, checkClashes, "array destructuring pattern");
}
break;
case "AssignmentPattern":
this.checkLVal(expr.left, isBinding, checkClashes, "assignment pattern");
break;
case "RestElement":
this.checkLVal(expr.argument, isBinding, checkClashes, "rest element");
break;
default: {
const message = "Invalid left-hand side" +
const message = (isBinding ? /* istanbul ignore next */ "Binding invalid" : "Invalid") +
" left-hand side" +
(contextDescription ? " in " + contextDescription : /* istanbul ignore next */ "expression");
this.raise(node.start, message);
this.raise(expr.start, message);
}
}
}
return node;
};
// Convert list of expression atoms to binding list.
pp.toAssignableList = function (exprList, isBinding, contextDescription) {
let end = exprList.length;
if (end) {
const last = exprList[end - 1];
if (last && last.type === "RestElement") {
--end;
} else if (last && last.type === "SpreadElement") {
last.type = "RestElement";
const arg = last.argument;
this.toAssignable(arg, isBinding, contextDescription);
if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") {
this.unexpected(arg.start);
}
--end;
}
}
for (let i = 0; i < end; i++) {
const elt = exprList[i];
if (elt && elt.type === "SpreadElement")
this.raise(elt.start, "The rest element has to be the last element when destructuring");
if (elt) this.toAssignable(elt, isBinding, contextDescription);
}
return exprList;
};
// Convert list of expression atoms to a list of
pp.toReferencedList = function (exprList) {
return exprList;
};
// Parses spread element.
pp.parseSpread = function (refShorthandDefaultPos) {
const node = this.startNode();
this.next();
node.argument = this.parseMaybeAssign(false, refShorthandDefaultPos);
return this.finishNode(node, "SpreadElement");
};
pp.parseRest = function () {
const node = this.startNode();
this.next();
node.argument = this.parseBindingAtom();
return this.finishNode(node, "RestElement");
};
pp.shouldAllowYieldIdentifier = function () {
return this.match(tt._yield) && !this.state.strict && !this.state.inGenerator;
};
pp.parseBindingIdentifier = function () {
return this.parseIdentifier(this.shouldAllowYieldIdentifier());
};
// Parses lvalue (assignable) atom.
pp.parseBindingAtom = function () {
switch (this.state.type) {
case tt._yield:
case tt.name:
return this.parseBindingIdentifier();
case tt.bracketL:
const node = this.startNode();
this.next();
node.elements = this.parseBindingList(tt.bracketR, true);
return this.finishNode(node, "ArrayPattern");
case tt.braceL:
return this.parseObj(true);
default:
this.unexpected();
}
};
pp.parseBindingList = function (close, allowEmpty) {
const elts = [];
let first = true;
while (!this.eat(close)) {
if (first) {
first = false;
} else {
this.expect(tt.comma);
}
if (allowEmpty && this.match(tt.comma)) {
elts.push(null);
} else if (this.eat(close)) {
break;
} else if (this.match(tt.ellipsis)) {
elts.push(this.parseAssignableListItemTypes(this.parseRest()));
this.expect(close);
break;
} else {
const decorators = [];
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
const left = this.parseMaybeDefault();
if (decorators.length) {
left.decorators = decorators;
}
this.parseAssignableListItemTypes(left);
elts.push(this.parseMaybeDefault(left.start, left.loc.start, left));
}
}
return elts;
};
pp.parseAssignableListItemTypes = function (param) {
return param;
};
// Parses assignment pattern around given atom if possible.
pp.parseMaybeDefault = function (startPos, startLoc, left) {
startLoc = startLoc || this.state.startLoc;
startPos = startPos || this.state.start;
left = left || this.parseBindingAtom();
if (!this.eat(tt.eq)) return left;
const node = this.startNodeAt(startPos, startLoc);
node.left = left;
node.right = this.parseMaybeAssign();
return this.finishNode(node, "AssignmentPattern");
};
// Verify that a node is an lval — something that can be assigned
// to.
pp.checkLVal = function (expr, isBinding, checkClashes, contextDescription) {
switch (expr.type) {
case "Identifier":
this.checkReservedWord(expr.name, expr.start, false, true);
if (checkClashes) {
// we need to prefix this with an underscore for the cases where we have a key of
// `__proto__`. there's a bug in old V8 where the following wouldn't work:
//
// > var obj = Object.create(null);
// undefined
// > obj.__proto__
// null
// > obj.__proto__ = true;
// true
// > obj.__proto__
// null
const key = `_${expr.name}`;
if (checkClashes[key]) {
this.raise(expr.start, "Argument name clash in strict mode");
} else {
checkClashes[key] = true;
}
}
break;
case "MemberExpression":
if (isBinding) this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " member expression");
break;
case "ObjectPattern":
for (let prop of (expr.properties: Array<Object>)) {
if (prop.type === "ObjectProperty") prop = prop.value;
this.checkLVal(prop, isBinding, checkClashes, "object destructuring pattern");
}
break;
case "ArrayPattern":
for (const elem of (expr.elements: Array<Object>)) {
if (elem) this.checkLVal(elem, isBinding, checkClashes, "array destructuring pattern");
}
break;
case "AssignmentPattern":
this.checkLVal(expr.left, isBinding, checkClashes, "assignment pattern");
break;
case "RestElement":
this.checkLVal(expr.argument, isBinding, checkClashes, "rest element");
break;
default: {
const message = (isBinding ? /* istanbul ignore next */ "Binding invalid" : "Invalid") +
" left-hand side" +
(contextDescription ? " in " + contextDescription : /* istanbul ignore next */ "expression");
this.raise(expr.start, message);
}
}
};
}

View File

@ -1,9 +1,9 @@
import Parser from "./index";
import UtilParser from "./util";
import { SourceLocation, type Position } from "../util/location";
// Start an AST node, attaching a start offset.
const pp = Parser.prototype;
const commentKeys = ["leadingComments", "trailingComments", "innerComments"];
class Node {
@ -34,43 +34,40 @@ class Node {
}
}
pp.startNode = function () {
return new Node(this, this.state.start, this.state.startLoc);
};
export class NodeUtils extends UtilParser {
startNode() {
return new Node(this, this.state.start, this.state.startLoc);
}
pp.startNodeAt = function (pos, loc) {
return new Node(this, pos, loc);
};
startNodeAt(pos, loc) {
return new Node(this, pos, loc);
}
function finishNodeAt(node, type, pos, loc) {
node.type = type;
node.end = pos;
node.loc.end = loc;
if (this.options.ranges) node.range[1] = pos;
this.processComment(node);
return node;
// Finish an AST node, adding `type` and `end` properties.
finishNode(node, type) {
return this.finishNodeAt(node, type, this.state.lastTokEnd, this.state.lastTokEndLoc);
}
// Finish node at given position
finishNodeAt(node, type, pos, loc) {
node.type = type;
node.end = pos;
node.loc.end = loc;
if (this.options.ranges) node.range[1] = pos;
this.processComment(node);
return node;
}
/**
* Reset the start location of node to the start location of locationNode
*/
resetStartLocationFromNode(node, locationNode) {
node.start = locationNode.start;
node.loc.start = locationNode.loc.start;
if (this.options.ranges) node.range[0] = locationNode.range[0];
return node;
}
}
// Finish an AST node, adding `type` and `end` properties.
pp.finishNode = function (node, type) {
return finishNodeAt.call(this, node, type, this.state.lastTokEnd, this.state.lastTokEndLoc);
};
// Finish node at given position
pp.finishNodeAt = function (node, type, pos, loc) {
return finishNodeAt.call(this, node, type, pos, loc);
};
/**
* Reset the start location of node to the start location of locationNode
*/
pp.resetStartLocationFromNode = function (node, locationNode) {
node.start = locationNode.start;
node.loc.start = locationNode.loc.start;
if (this.options.ranges) node.range[0] = locationNode.range[0];
return node;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,88 +1,88 @@
import { types as tt } from "../tokenizer/types";
import Parser from "./index";
import Tokenizer from "../tokenizer";
import { lineBreak } from "../util/whitespace";
const pp = Parser.prototype;
// ## Parser utilities
// TODO
export default class UtilParser extends Tokenizer {
// TODO
pp.addExtra = function (node, key, val) {
if (!node) return;
addExtra(node, key, val) {
if (!node) return;
const extra = node.extra = node.extra || {};
extra[key] = val;
};
// TODO
pp.isRelational = function (op) {
return this.match(tt.relational) && this.state.value === op;
};
// TODO
pp.expectRelational = function (op) {
if (this.isRelational(op)) {
this.next();
} else {
this.unexpected(null, tt.relational);
const extra = node.extra = node.extra || {};
extra[key] = val;
}
};
// Tests whether parsed token is a contextual keyword.
// TODO
pp.isContextual = function (name) {
return this.match(tt.name) && this.state.value === name;
};
// Consumes contextual keyword if possible.
pp.eatContextual = function (name) {
return this.state.value === name && this.eat(tt.name);
};
// Asserts that following token is given contextual keyword.
pp.expectContextual = function (name, message) {
if (!this.eatContextual(name)) this.unexpected(null, message);
};
// Test whether a semicolon can be inserted at the current position.
pp.canInsertSemicolon = function () {
return this.match(tt.eof) ||
this.match(tt.braceR) ||
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.
pp.semicolon = function () {
if (!this.isLineTerminator()) this.unexpected(null, tt.semi);
};
// Expect a token of a given type. If found, consume it, otherwise,
// raise an unexpected token error at given pos.
pp.expect = function (type, pos) {
return this.eat(type) || this.unexpected(pos, type);
};
// Raise an unexpected token error. Can take the expected token type
// instead of a message string.
pp.unexpected = function (pos, messageOrType = "Unexpected token") {
if (messageOrType && typeof messageOrType === "object" && messageOrType.label) {
messageOrType = `Unexpected token, expected ${messageOrType.label}`;
isRelational(op) {
return this.match(tt.relational) && this.state.value === op;
}
this.raise(pos != null ? pos : this.state.start, messageOrType);
};
// TODO
expectRelational(op) {
if (this.isRelational(op)) {
this.next();
} else {
this.unexpected(null, tt.relational);
}
}
// Tests whether parsed token is a contextual keyword.
isContextual(name) {
return this.match(tt.name) && this.state.value === name;
}
// Consumes contextual keyword if possible.
eatContextual(name) {
return this.state.value === name && this.eat(tt.name);
}
// Asserts that following token is given contextual keyword.
expectContextual(name, message) {
if (!this.eatContextual(name)) this.unexpected(null, message);
}
// Test whether a semicolon can be inserted at the current position.
canInsertSemicolon() {
return this.match(tt.eof) ||
this.match(tt.braceR) ||
lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start));
}
// TODO
isLineTerminator() {
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.
semicolon() {
if (!this.isLineTerminator()) this.unexpected(null, tt.semi);
}
// Expect a token of a given type. If found, consume it, otherwise,
// raise an unexpected token error at given pos.
expect(type, pos) {
return this.eat(type) || this.unexpected(pos, type);
}
// Raise an unexpected token error. Can take the expected token type
// instead of a message string.
unexpected(pos, messageOrType = "Unexpected token") {
if (messageOrType && typeof messageOrType === "object" && messageOrType.label) {
messageOrType = `Unexpected token, expected ${messageOrType.label}`;
}
this.raise(pos != null ? pos : this.state.start, messageOrType);
}
}

View File

@ -8,6 +8,7 @@ import type { Position } from "../util/location";
import { isIdentifierStart, isIdentifierChar, isKeyword } from "../util/identifier";
import { types as tt, keywords as keywordTypes } from "./types";
import { type TokContext, types as ct } from "./context";
import LocationParser from "../parser/location";
import { SourceLocation } from "../util/location";
import { lineBreak, lineBreakG, isNewLine, nonASCIIwhitespace } from "../util/whitespace";
import State from "./state";
@ -43,14 +44,8 @@ function codePointToString(code: number): string {
}
}
export default class Tokenizer {
export default class Tokenizer extends LocationParser {
// Forward-declarations
// location.js
+raise: (pos: number, message: string) => empty;
// comments.js (TODO: Better type for the parameter)
+addComment: (comment: Object) => void;
// parser/index.js
+hasPlugin: (name: string) => boolean;
// parser/util.js
+unexpected: (pos?: ?number, messageOrType?: string | TokenType) => empty;
@ -59,6 +54,7 @@ export default class Tokenizer {
input: string;
constructor(options: Options, input: string) {
super();
this.state = new State;
this.state.init(options, input);
}