Convert each file with parser methods to a class in an inheritance chain (#481)
This commit is contained in:
parent
2c1193b7da
commit
c4fb3fe742
24
src/parser/base.js
Normal file
24
src/parser/base.js
Normal 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];
|
||||
}
|
||||
}
|
||||
@ -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
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -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
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user