Rename "babylon" to "@babel/parser" (#7937) 🎉
This commit is contained in:
committed by
Henry Zhu
parent
0200a3e510
commit
daf0ca8680
35
packages/babel-parser/src/parser/base.js
Normal file
35
packages/babel-parser/src/parser/base.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// @flow
|
||||
|
||||
import type { Options } from "../options";
|
||||
import { reservedWords } from "../util/identifier";
|
||||
|
||||
import type State from "../tokenizer/state";
|
||||
|
||||
export default class BaseParser {
|
||||
// Properties set by constructor in index.js
|
||||
options: Options;
|
||||
inModule: boolean;
|
||||
plugins: { [key: string]: boolean };
|
||||
filename: ?string;
|
||||
sawUnambiguousESM: boolean = false;
|
||||
|
||||
// Initialized by Tokenizer
|
||||
state: State;
|
||||
input: string;
|
||||
|
||||
isReservedWord(word: string): boolean {
|
||||
if (word === "await") {
|
||||
return this.inModule;
|
||||
} else {
|
||||
return reservedWords[6](word);
|
||||
}
|
||||
}
|
||||
|
||||
hasPlugin(name: string): boolean {
|
||||
return Object.hasOwnProperty.call(this.plugins, name);
|
||||
}
|
||||
|
||||
getPluginOption(plugin: string, name: string) {
|
||||
if (this.hasPlugin(plugin)) return this.plugins[plugin][name];
|
||||
}
|
||||
}
|
||||
225
packages/babel-parser/src/parser/comments.js
Normal file
225
packages/babel-parser/src/parser/comments.js
Normal file
@@ -0,0 +1,225 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* Based on the comment attachment algorithm used in espree and estraverse.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import BaseParser from "./base";
|
||||
import type { Comment, Node } from "../types";
|
||||
|
||||
function last<T>(stack: $ReadOnlyArray<T>): T {
|
||||
return stack[stack.length - 1];
|
||||
}
|
||||
|
||||
export default class CommentsParser extends BaseParser {
|
||||
addComment(comment: Comment): void {
|
||||
if (this.filename) comment.loc.filename = this.filename;
|
||||
this.state.trailingComments.push(comment);
|
||||
this.state.leadingComments.push(comment);
|
||||
}
|
||||
|
||||
processComment(node: Node): void {
|
||||
if (node.type === "Program" && node.body.length > 0) return;
|
||||
|
||||
const stack = this.state.commentStack;
|
||||
|
||||
let firstChild, 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 if (stack.length > 0) {
|
||||
const lastInStack = last(stack);
|
||||
if (
|
||||
lastInStack.trailingComments &&
|
||||
lastInStack.trailingComments[0].start >= node.end
|
||||
) {
|
||||
trailingComments = lastInStack.trailingComments;
|
||||
delete lastInStack.trailingComments;
|
||||
}
|
||||
}
|
||||
|
||||
// Eating the stack.
|
||||
if (stack.length > 0 && last(stack).start >= node.start) {
|
||||
firstChild = stack.pop();
|
||||
}
|
||||
|
||||
while (stack.length > 0 && last(stack).start >= node.start) {
|
||||
lastChild = stack.pop();
|
||||
}
|
||||
|
||||
if (!lastChild && firstChild) lastChild = firstChild;
|
||||
|
||||
// Attach comments that follow a trailing comma on the last
|
||||
// property in an object literal or a trailing comma in function arguments
|
||||
// as trailing comments
|
||||
if (firstChild && this.state.leadingComments.length > 0) {
|
||||
const lastComment = last(this.state.leadingComments);
|
||||
|
||||
if (firstChild.type === "ObjectProperty") {
|
||||
if (lastComment.start >= 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) {
|
||||
firstChild.trailingComments = this.state.leadingComments;
|
||||
this.state.leadingComments = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
node.type === "CallExpression" &&
|
||||
node.arguments &&
|
||||
node.arguments.length
|
||||
) {
|
||||
const lastArg = last(node.arguments);
|
||||
|
||||
if (
|
||||
lastArg &&
|
||||
lastComment.start >= lastArg.start &&
|
||||
lastComment.end <= node.end
|
||||
) {
|
||||
if (this.state.commentPreviousNode) {
|
||||
if (this.state.leadingComments.length > 0) {
|
||||
lastArg.trailingComments = this.state.leadingComments;
|
||||
this.state.leadingComments = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastChild) {
|
||||
if (lastChild.leadingComments) {
|
||||
if (
|
||||
lastChild !== node &&
|
||||
lastChild.leadingComments.length > 0 &&
|
||||
last(lastChild.leadingComments).end <= node.start
|
||||
) {
|
||||
node.leadingComments = lastChild.leadingComments;
|
||||
delete lastChild.leadingComments;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
const leadingComments = this.state.leadingComments.slice(0, i);
|
||||
|
||||
if (leadingComments.length) {
|
||||
node.leadingComments = leadingComments;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
1934
packages/babel-parser/src/parser/expression.js
Normal file
1934
packages/babel-parser/src/parser/expression.js
Normal file
File diff suppressed because it is too large
Load Diff
50
packages/babel-parser/src/parser/index.js
Normal file
50
packages/babel-parser/src/parser/index.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// @flow
|
||||
|
||||
import type { Options } from "../options";
|
||||
import type { File } from "../types";
|
||||
import { getOptions } from "../options";
|
||||
import StatementParser from "./statement";
|
||||
|
||||
export const plugins: {
|
||||
[name: string]: (superClass: Class<Parser>) => Class<Parser>,
|
||||
} = {};
|
||||
|
||||
export default class Parser extends StatementParser {
|
||||
constructor(options: ?Options, input: string) {
|
||||
options = getOptions(options);
|
||||
super(options, input);
|
||||
|
||||
this.options = options;
|
||||
this.inModule = this.options.sourceType === "module";
|
||||
this.input = input;
|
||||
this.plugins = pluginsMap(this.options.plugins);
|
||||
this.filename = options.sourceFilename;
|
||||
|
||||
// If enabled, skip leading hashbang line.
|
||||
if (
|
||||
this.state.pos === 0 &&
|
||||
this.input[0] === "#" &&
|
||||
this.input[1] === "!"
|
||||
) {
|
||||
this.skipLineComment(2);
|
||||
}
|
||||
}
|
||||
|
||||
parse(): File {
|
||||
const file = this.startNode();
|
||||
const program = this.startNode();
|
||||
this.nextToken();
|
||||
return this.parseTopLevel(file, program);
|
||||
}
|
||||
}
|
||||
|
||||
function pluginsMap(
|
||||
pluginList: $ReadOnlyArray<string>,
|
||||
): { [key: string]: boolean } {
|
||||
const pluginMap = Object.create(null);
|
||||
for (const plugin of pluginList) {
|
||||
const [name, options = {}] = Array.isArray(plugin) ? plugin : [plugin];
|
||||
pluginMap[name] = options;
|
||||
}
|
||||
return pluginMap;
|
||||
}
|
||||
40
packages/babel-parser/src/parser/location.js
Normal file
40
packages/babel-parser/src/parser/location.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// @flow
|
||||
|
||||
import { getLineInfo, type Position } from "../util/location";
|
||||
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
|
||||
// the location of the error, attaches the position to the end
|
||||
// of the error message, and then raises a `SyntaxError` with that
|
||||
// message.
|
||||
|
||||
export default class LocationParser extends CommentsParser {
|
||||
raise(
|
||||
pos: number,
|
||||
message: string,
|
||||
{
|
||||
missingPluginNames,
|
||||
code,
|
||||
}: {
|
||||
missingPluginNames?: Array<string>,
|
||||
code?: string,
|
||||
} = {},
|
||||
): empty {
|
||||
const loc = getLineInfo(this.input, pos);
|
||||
message += ` (${loc.line}:${loc.column})`;
|
||||
// $FlowIgnore
|
||||
const err: SyntaxError & { pos: number, loc: Position } = new SyntaxError(
|
||||
message,
|
||||
);
|
||||
err.pos = pos;
|
||||
err.loc = loc;
|
||||
if (missingPluginNames) {
|
||||
err.missingPlugin = missingPluginNames;
|
||||
}
|
||||
if (code !== undefined) {
|
||||
err.code = code;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
414
packages/babel-parser/src/parser/lval.js
Normal file
414
packages/babel-parser/src/parser/lval.js
Normal file
@@ -0,0 +1,414 @@
|
||||
// @flow
|
||||
|
||||
import { types as tt, type TokenType } from "../tokenizer/types";
|
||||
import type {
|
||||
TSParameterProperty,
|
||||
Decorator,
|
||||
Expression,
|
||||
Identifier,
|
||||
Node,
|
||||
ObjectExpression,
|
||||
ObjectPattern,
|
||||
Pattern,
|
||||
RestElement,
|
||||
SpreadElement,
|
||||
} from "../types";
|
||||
import type { Pos, Position } from "../util/location";
|
||||
import { NodeUtils } from "./node";
|
||||
|
||||
export default class LValParser extends NodeUtils {
|
||||
// Forward-declaration: defined in expression.js
|
||||
+checkReservedWord: (
|
||||
word: string,
|
||||
startLoc: number,
|
||||
checkKeywords: boolean,
|
||||
isBinding: boolean,
|
||||
) => void;
|
||||
+parseIdentifier: (liberal?: boolean) => Identifier;
|
||||
+parseMaybeAssign: (
|
||||
noIn?: ?boolean,
|
||||
refShorthandDefaultPos?: ?Pos,
|
||||
afterLeftParse?: Function,
|
||||
refNeedsArrowPos?: ?Pos,
|
||||
) => Expression;
|
||||
+parseObj: <T: ObjectPattern | ObjectExpression>(
|
||||
isPattern: boolean,
|
||||
refShorthandDefaultPos?: ?Pos,
|
||||
) => T;
|
||||
// Forward-declaration: defined in statement.js
|
||||
+parseDecorator: () => Decorator;
|
||||
|
||||
// Convert existing expression atom to assignable pattern
|
||||
// if possible.
|
||||
|
||||
toAssignable(
|
||||
node: Node,
|
||||
isBinding: ?boolean,
|
||||
contextDescription: string,
|
||||
): Node {
|
||||
if (node) {
|
||||
switch (node.type) {
|
||||
case "Identifier":
|
||||
case "ObjectPattern":
|
||||
case "ArrayPattern":
|
||||
case "AssignmentPattern":
|
||||
break;
|
||||
|
||||
case "ObjectExpression":
|
||||
node.type = "ObjectPattern";
|
||||
for (let index = 0; index < node.properties.length; index++) {
|
||||
const prop = node.properties[index];
|
||||
const isLast = index === node.properties.length - 1;
|
||||
this.toAssignableObjectExpressionProp(prop, isBinding, isLast);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ObjectProperty":
|
||||
this.toAssignable(node.value, isBinding, contextDescription);
|
||||
break;
|
||||
|
||||
case "SpreadElement": {
|
||||
this.checkToRestConversion(node);
|
||||
|
||||
node.type = "RestElement";
|
||||
const arg = node.argument;
|
||||
this.toAssignable(arg, isBinding, contextDescription);
|
||||
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.",
|
||||
);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
toAssignableObjectExpressionProp(
|
||||
prop: Node,
|
||||
isBinding: ?boolean,
|
||||
isLast: boolean,
|
||||
) {
|
||||
if (prop.type === "ObjectMethod") {
|
||||
const error =
|
||||
prop.kind === "get" || prop.kind === "set"
|
||||
? "Object pattern can't contain getter or setter"
|
||||
: "Object pattern can't contain methods";
|
||||
|
||||
this.raise(prop.key.start, error);
|
||||
} else if (prop.type === "SpreadElement" && !isLast) {
|
||||
this.raise(
|
||||
prop.start,
|
||||
"The rest element has to be the last element when destructuring",
|
||||
);
|
||||
} else {
|
||||
this.toAssignable(prop, isBinding, "object destructuring pattern");
|
||||
}
|
||||
}
|
||||
|
||||
// Convert list of expression atoms to binding list.
|
||||
|
||||
toAssignableList(
|
||||
exprList: Expression[],
|
||||
isBinding: ?boolean,
|
||||
contextDescription: string,
|
||||
): $ReadOnlyArray<Pattern> {
|
||||
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 (
|
||||
[
|
||||
"Identifier",
|
||||
"MemberExpression",
|
||||
"ArrayPattern",
|
||||
"ObjectPattern",
|
||||
].indexOf(arg.type) === -1
|
||||
) {
|
||||
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: $ReadOnlyArray<?Expression>,
|
||||
): $ReadOnlyArray<?Expression> {
|
||||
return exprList;
|
||||
}
|
||||
|
||||
// Parses spread element.
|
||||
|
||||
parseSpread<T: RestElement | SpreadElement>(
|
||||
refShorthandDefaultPos: ?Pos,
|
||||
refNeedsArrowPos?: ?Pos,
|
||||
): T {
|
||||
const node = this.startNode();
|
||||
this.next();
|
||||
node.argument = this.parseMaybeAssign(
|
||||
false,
|
||||
refShorthandDefaultPos,
|
||||
undefined,
|
||||
refNeedsArrowPos,
|
||||
);
|
||||
return this.finishNode(node, "SpreadElement");
|
||||
}
|
||||
|
||||
parseRest(): RestElement {
|
||||
const node = this.startNode();
|
||||
this.next();
|
||||
node.argument = this.parseBindingAtom();
|
||||
return this.finishNode(node, "RestElement");
|
||||
}
|
||||
|
||||
shouldAllowYieldIdentifier(): boolean {
|
||||
return (
|
||||
this.match(tt._yield) && !this.state.strict && !this.state.inGenerator
|
||||
);
|
||||
}
|
||||
|
||||
parseBindingIdentifier(): Identifier {
|
||||
return this.parseIdentifier(this.shouldAllowYieldIdentifier());
|
||||
}
|
||||
|
||||
// Parses lvalue (assignable) atom.
|
||||
parseBindingAtom(): Pattern {
|
||||
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:
|
||||
throw this.unexpected();
|
||||
}
|
||||
}
|
||||
|
||||
parseBindingList(
|
||||
close: TokenType,
|
||||
allowEmpty?: boolean,
|
||||
allowModifiers?: boolean,
|
||||
): $ReadOnlyArray<Pattern | TSParameterProperty> {
|
||||
const elts: Array<Pattern | TSParameterProperty> = [];
|
||||
let first = true;
|
||||
while (!this.eat(close)) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
this.expect(tt.comma);
|
||||
}
|
||||
if (allowEmpty && this.match(tt.comma)) {
|
||||
// $FlowFixMe This method returns `$ReadOnlyArray<?Pattern>` if `allowEmpty` is set.
|
||||
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 = [];
|
||||
if (this.match(tt.at) && this.hasPlugin("decorators")) {
|
||||
this.raise(
|
||||
this.state.start,
|
||||
"Stage 2 decorators cannot be used to decorate parameters",
|
||||
);
|
||||
}
|
||||
while (this.match(tt.at)) {
|
||||
decorators.push(this.parseDecorator());
|
||||
}
|
||||
elts.push(this.parseAssignableListItem(allowModifiers, decorators));
|
||||
}
|
||||
}
|
||||
return elts;
|
||||
}
|
||||
|
||||
parseAssignableListItem(
|
||||
allowModifiers: ?boolean,
|
||||
decorators: Decorator[],
|
||||
): Pattern | TSParameterProperty {
|
||||
const left = this.parseMaybeDefault();
|
||||
this.parseAssignableListItemTypes(left);
|
||||
const elt = this.parseMaybeDefault(left.start, left.loc.start, left);
|
||||
if (decorators.length) {
|
||||
left.decorators = decorators;
|
||||
}
|
||||
return elt;
|
||||
}
|
||||
|
||||
parseAssignableListItemTypes(param: Pattern): Pattern {
|
||||
return param;
|
||||
}
|
||||
|
||||
// Parses assignment pattern around given atom if possible.
|
||||
|
||||
parseMaybeDefault(
|
||||
startPos?: ?number,
|
||||
startLoc?: ?Position,
|
||||
left?: ?Pattern,
|
||||
): Pattern {
|
||||
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: Expression,
|
||||
isBinding: ?boolean,
|
||||
checkClashes: ?{ [key: string]: boolean },
|
||||
contextDescription: string,
|
||||
): void {
|
||||
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, "Binding member expression");
|
||||
break;
|
||||
|
||||
case "ObjectPattern":
|
||||
for (let prop of expr.properties) {
|
||||
if (prop.type === "ObjectProperty") prop = prop.value;
|
||||
this.checkLVal(
|
||||
prop,
|
||||
isBinding,
|
||||
checkClashes,
|
||||
"object destructuring pattern",
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ArrayPattern":
|
||||
for (const elem of expr.elements) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkToRestConversion(node: SpreadElement): void {
|
||||
const validArgumentTypes = ["Identifier", "MemberExpression"];
|
||||
|
||||
if (validArgumentTypes.indexOf(node.argument.type) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.raise(node.argument.start, "Invalid rest operator's argument");
|
||||
}
|
||||
}
|
||||
98
packages/babel-parser/src/parser/node.js
Normal file
98
packages/babel-parser/src/parser/node.js
Normal file
@@ -0,0 +1,98 @@
|
||||
// @flow
|
||||
|
||||
import Parser from "./index";
|
||||
import UtilParser from "./util";
|
||||
import { SourceLocation, type Position } from "../util/location";
|
||||
import type { Comment, Node as NodeType, NodeBase } from "../types";
|
||||
|
||||
// Start an AST node, attaching a start offset.
|
||||
|
||||
const commentKeys = ["leadingComments", "trailingComments", "innerComments"];
|
||||
|
||||
class Node implements NodeBase {
|
||||
constructor(parser: Parser, pos: number, loc: Position) {
|
||||
this.type = "";
|
||||
this.start = pos;
|
||||
this.end = 0;
|
||||
this.loc = new SourceLocation(loc);
|
||||
if (parser && parser.options.ranges) this.range = [pos, 0];
|
||||
if (parser && parser.filename) this.loc.filename = parser.filename;
|
||||
}
|
||||
|
||||
type: string;
|
||||
start: number;
|
||||
end: number;
|
||||
loc: SourceLocation;
|
||||
range: [number, number];
|
||||
leadingComments: Array<Comment>;
|
||||
trailingComments: Array<Comment>;
|
||||
innerComments: Array<Comment>;
|
||||
extra: { [key: string]: any };
|
||||
|
||||
__clone(): this {
|
||||
// $FlowIgnore
|
||||
const node2: any = new Node();
|
||||
Object.keys(this).forEach(key => {
|
||||
// Do not clone comments that are already attached to the node
|
||||
if (commentKeys.indexOf(key) < 0) {
|
||||
// $FlowIgnore
|
||||
node2[key] = this[key];
|
||||
}
|
||||
});
|
||||
|
||||
return node2;
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeUtils extends UtilParser {
|
||||
startNode<T: NodeType>(): T {
|
||||
// $FlowIgnore
|
||||
return new Node(this, this.state.start, this.state.startLoc);
|
||||
}
|
||||
|
||||
startNodeAt<T: NodeType>(pos: number, loc: Position): T {
|
||||
// $FlowIgnore
|
||||
return new Node(this, pos, loc);
|
||||
}
|
||||
|
||||
/** Start a new node with a previous node's location. */
|
||||
startNodeAtNode<T: NodeType>(type: NodeType): T {
|
||||
return this.startNodeAt(type.start, type.loc.start);
|
||||
}
|
||||
|
||||
// Finish an AST node, adding `type` and `end` properties.
|
||||
|
||||
finishNode<T: NodeType>(node: T, type: string): T {
|
||||
return this.finishNodeAt(
|
||||
node,
|
||||
type,
|
||||
this.state.lastTokEnd,
|
||||
this.state.lastTokEndLoc,
|
||||
);
|
||||
}
|
||||
|
||||
// Finish node at given position
|
||||
|
||||
finishNodeAt<T: NodeType>(
|
||||
node: T,
|
||||
type: string,
|
||||
pos: number,
|
||||
loc: Position,
|
||||
): T {
|
||||
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: NodeBase, locationNode: NodeBase): void {
|
||||
node.start = locationNode.start;
|
||||
node.loc.start = locationNode.loc.start;
|
||||
if (this.options.ranges) node.range[0] = locationNode.range[0];
|
||||
}
|
||||
}
|
||||
1785
packages/babel-parser/src/parser/statement.js
Normal file
1785
packages/babel-parser/src/parser/statement.js
Normal file
File diff suppressed because it is too large
Load Diff
150
packages/babel-parser/src/parser/util.js
Normal file
150
packages/babel-parser/src/parser/util.js
Normal file
@@ -0,0 +1,150 @@
|
||||
// @flow
|
||||
|
||||
import { types as tt, type TokenType } from "../tokenizer/types";
|
||||
import Tokenizer from "../tokenizer";
|
||||
import type { Node } from "../types";
|
||||
import { lineBreak } from "../util/whitespace";
|
||||
|
||||
// ## Parser utilities
|
||||
|
||||
export default class UtilParser extends Tokenizer {
|
||||
// TODO
|
||||
|
||||
addExtra(node: Node, key: string, val: any): void {
|
||||
if (!node) return;
|
||||
|
||||
const extra = (node.extra = node.extra || {});
|
||||
extra[key] = val;
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
isRelational(op: "<" | ">"): boolean {
|
||||
return this.match(tt.relational) && this.state.value === op;
|
||||
}
|
||||
|
||||
isLookaheadRelational(op: "<" | ">"): boolean {
|
||||
const l = this.lookahead();
|
||||
return l.type == tt.relational && l.value == op;
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
expectRelational(op: "<" | ">"): void {
|
||||
if (this.isRelational(op)) {
|
||||
this.next();
|
||||
} else {
|
||||
this.unexpected(null, tt.relational);
|
||||
}
|
||||
}
|
||||
|
||||
// eat() for relational operators.
|
||||
|
||||
eatRelational(op: "<" | ">"): boolean {
|
||||
if (this.isRelational(op)) {
|
||||
this.next();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tests whether parsed token is a contextual keyword.
|
||||
|
||||
isContextual(name: string): boolean {
|
||||
return (
|
||||
this.match(tt.name) &&
|
||||
this.state.value === name &&
|
||||
!this.state.containsEsc
|
||||
);
|
||||
}
|
||||
|
||||
isLookaheadContextual(name: string): boolean {
|
||||
const l = this.lookahead();
|
||||
return l.type === tt.name && l.value === name;
|
||||
}
|
||||
|
||||
// Consumes contextual keyword if possible.
|
||||
|
||||
eatContextual(name: string): boolean {
|
||||
return this.isContextual(name) && this.eat(tt.name);
|
||||
}
|
||||
|
||||
// Asserts that following token is given contextual keyword.
|
||||
|
||||
expectContextual(name: string, message?: string): void {
|
||||
if (!this.eatContextual(name)) this.unexpected(null, message);
|
||||
}
|
||||
|
||||
// Test whether a semicolon can be inserted at the current position.
|
||||
|
||||
canInsertSemicolon(): boolean {
|
||||
return (
|
||||
this.match(tt.eof) ||
|
||||
this.match(tt.braceR) ||
|
||||
this.hasPrecedingLineBreak()
|
||||
);
|
||||
}
|
||||
|
||||
hasPrecedingLineBreak(): boolean {
|
||||
return lineBreak.test(
|
||||
this.input.slice(this.state.lastTokEnd, this.state.start),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
isLineTerminator(): boolean {
|
||||
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(): void {
|
||||
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: TokenType, pos?: ?number): void {
|
||||
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: ?number,
|
||||
messageOrType: string | TokenType = "Unexpected token",
|
||||
): empty {
|
||||
if (typeof messageOrType !== "string") {
|
||||
messageOrType = `Unexpected token, expected "${messageOrType.label}"`;
|
||||
}
|
||||
throw this.raise(pos != null ? pos : this.state.start, messageOrType);
|
||||
}
|
||||
|
||||
expectPlugin(name: string, pos?: ?number): true {
|
||||
if (!this.hasPlugin(name)) {
|
||||
throw this.raise(
|
||||
pos != null ? pos : this.state.start,
|
||||
`This experimental syntax requires enabling the parser plugin: '${name}'`,
|
||||
{ missingPluginNames: [name] },
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
expectOnePlugin(names: Array<string>, pos?: ?number): void {
|
||||
if (!names.some(n => this.hasPlugin(n))) {
|
||||
throw this.raise(
|
||||
pos != null ? pos : this.state.start,
|
||||
`This experimental syntax requires enabling one of the following parser plugin(s): '${names.join(
|
||||
", ",
|
||||
)}'`,
|
||||
{ missingPluginNames: names },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user