Merge branch 'master' into feat-optional-chaining
This commit is contained in:
49
src/index.js
49
src/index.js
@@ -1,3 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import type { Options } from "./options";
|
||||
import Parser, { plugins } from "./parser";
|
||||
import "./parser/util";
|
||||
import "./parser/statement";
|
||||
@@ -11,6 +14,8 @@ import { types as tokTypes } from "./tokenizer/types";
|
||||
import "./tokenizer";
|
||||
import "./tokenizer/context";
|
||||
|
||||
import type { Expression, File } from "./types";
|
||||
|
||||
import estreePlugin from "./plugins/estree";
|
||||
import flowPlugin from "./plugins/flow";
|
||||
import jsxPlugin from "./plugins/jsx";
|
||||
@@ -18,12 +23,12 @@ plugins.estree = estreePlugin;
|
||||
plugins.flow = flowPlugin;
|
||||
plugins.jsx = jsxPlugin;
|
||||
|
||||
export function parse(input, options) {
|
||||
return new Parser(options, input).parse();
|
||||
export function parse(input: string, options?: Options): File {
|
||||
return getParser(options, input).parse();
|
||||
}
|
||||
|
||||
export function parseExpression(input, options) {
|
||||
const parser = new Parser(options, input);
|
||||
export function parseExpression(input: string, options?: Options): Expression {
|
||||
const parser = getParser(options, input);
|
||||
if (parser.options.strictMode) {
|
||||
parser.state.strict = true;
|
||||
}
|
||||
@@ -32,3 +37,39 @@ export function parseExpression(input, options) {
|
||||
|
||||
|
||||
export { tokTypes };
|
||||
|
||||
function getParser(options: ?Options, input: string): Parser {
|
||||
const cls = options && options.plugins ? getParserClass(options.plugins) : Parser;
|
||||
return new cls(options, input);
|
||||
}
|
||||
|
||||
const parserClassCache: { [key: string]: Class<Parser> } = {};
|
||||
|
||||
/** Get a Parser class with plugins applied. */
|
||||
function getParserClass(pluginsFromOptions: $ReadOnlyArray<string>): Class<Parser> {
|
||||
// Filter out just the plugins that have an actual mixin associated with them.
|
||||
let pluginList = pluginsFromOptions.filter((p) => p === "estree" || p === "flow" || p === "jsx");
|
||||
|
||||
if (pluginList.indexOf("flow") >= 0) {
|
||||
// ensure flow plugin loads last
|
||||
pluginList = pluginList.filter((plugin) => plugin !== "flow");
|
||||
pluginList.push("flow");
|
||||
}
|
||||
|
||||
if (pluginList.indexOf("estree") >= 0) {
|
||||
// ensure estree plugin loads first
|
||||
pluginList = pluginList.filter((plugin) => plugin !== "estree");
|
||||
pluginList.unshift("estree");
|
||||
}
|
||||
|
||||
const key = pluginList.join("/");
|
||||
let cls = parserClassCache[key];
|
||||
if (!cls) {
|
||||
cls = Parser;
|
||||
for (const plugin of pluginList) {
|
||||
cls = plugins[plugin](cls);
|
||||
}
|
||||
parserClassCache[key] = cls;
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
// @flow
|
||||
|
||||
// A second optional argument can be given to further configure
|
||||
// the parser process. These options are recognized:
|
||||
|
||||
export const defaultOptions: {
|
||||
sourceType: string,
|
||||
sourceFilename: any,
|
||||
startLine: number,
|
||||
allowReturnOutsideFunction: boolean,
|
||||
allowImportExportEverywhere: boolean,
|
||||
allowSuperOutsideMethod: boolean,
|
||||
plugins: Array<string>,
|
||||
strictMode: any,
|
||||
ranges: boolean,
|
||||
} = {
|
||||
export type Options = {
|
||||
sourceType: "script" | "module";
|
||||
sourceFilename?: string;
|
||||
startLine: number;
|
||||
allowReturnOutsideFunction: boolean;
|
||||
allowImportExportEverywhere: boolean;
|
||||
allowSuperOutsideMethod: boolean;
|
||||
plugins: $ReadOnlyArray<string>;
|
||||
strictMode: ?boolean;
|
||||
ranges: boolean;
|
||||
};
|
||||
|
||||
export const defaultOptions: Options = {
|
||||
// Source type ("script" or "module") for different semantics
|
||||
sourceType: "script",
|
||||
// Source filename.
|
||||
@@ -44,8 +48,8 @@ export const defaultOptions: {
|
||||
|
||||
// Interpret and default an options object
|
||||
|
||||
export function getOptions(opts?: Object): Object {
|
||||
const options = {};
|
||||
export function getOptions(opts: ?Options): Options {
|
||||
const options: any = {};
|
||||
for (const key in defaultOptions) {
|
||||
options[key] = opts && key in opts ? opts[key] : defaultOptions[key];
|
||||
}
|
||||
|
||||
31
src/parser/base.js
Normal file
31
src/parser/base.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// @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;
|
||||
|
||||
// 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 !!this.plugins[name];
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint max-len: 0 */
|
||||
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* Based on the comment attachment algorithm used in espree and estraverse.
|
||||
*
|
||||
@@ -24,133 +26,163 @@
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import Parser from "./index";
|
||||
import BaseParser from "./base";
|
||||
import type { Comment, Node } from "../types";
|
||||
|
||||
function last(stack) {
|
||||
function last<T>(stack: $ReadOnlyArray<T>): T {
|
||||
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: Comment): void {
|
||||
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: Node): void {
|
||||
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 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 {
|
||||
// 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.
|
||||
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 &&
|
||||
(firstChild.type === "ObjectProperty" ||
|
||||
(node.type === "CallExpression")) &&
|
||||
this.state.leadingComments.length > 0) {
|
||||
const lastComment = last(this.state.leadingComments);
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
const leadingComments = this.state.leadingComments.slice(0, i);
|
||||
node.leadingComments = leadingComments.length === 0 ? null : 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,18 +1,21 @@
|
||||
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 const plugins: { [name: string]: (superClass: Class<Parser>) => Class<Parser> } = {};
|
||||
|
||||
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);
|
||||
|
||||
this.options = options;
|
||||
this.inModule = this.options.sourceType === "module";
|
||||
this.input = input;
|
||||
this.plugins = this.loadPlugins(this.options.plugins);
|
||||
this.plugins = pluginsMap(this.options.plugins);
|
||||
this.filename = options.sourceFilename;
|
||||
|
||||
// If enabled, skip leading hashbang line.
|
||||
@@ -21,59 +24,18 @@ 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];
|
||||
}
|
||||
|
||||
extend(name: string, f: Function) {
|
||||
this[name] = f(this[name]);
|
||||
}
|
||||
|
||||
loadPlugins(pluginList: Array<string>): { [key: string]: boolean } {
|
||||
const pluginMap = {};
|
||||
|
||||
if (pluginList.indexOf("flow") >= 0) {
|
||||
// ensure flow plugin loads last
|
||||
pluginList = pluginList.filter((plugin) => plugin !== "flow");
|
||||
pluginList.push("flow");
|
||||
}
|
||||
|
||||
if (pluginList.indexOf("estree") >= 0) {
|
||||
// ensure estree plugin loads first
|
||||
pluginList = pluginList.filter((plugin) => plugin !== "estree");
|
||||
pluginList.unshift("estree");
|
||||
}
|
||||
|
||||
for (const name of pluginList) {
|
||||
if (!pluginMap[name]) {
|
||||
pluginMap[name] = true;
|
||||
|
||||
const plugin = plugins[name];
|
||||
if (plugin) plugin(this);
|
||||
}
|
||||
}
|
||||
|
||||
return pluginMap;
|
||||
}
|
||||
|
||||
parse(): {
|
||||
type: "File",
|
||||
program: {
|
||||
type: "Program",
|
||||
body: Array<Object>
|
||||
}
|
||||
} {
|
||||
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 = {};
|
||||
for (const name of pluginList) {
|
||||
pluginMap[name] = true;
|
||||
}
|
||||
return pluginMap;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getLineInfo } from "../util/location";
|
||||
import Parser from "./index";
|
||||
// @flow
|
||||
|
||||
const pp = Parser.prototype;
|
||||
import { getLineInfo } 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
|
||||
@@ -9,11 +9,14 @@ 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: number, message: 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;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,259 +1,291 @@
|
||||
import { types as tt } from "../tokenizer/types";
|
||||
import Parser from "./index";
|
||||
// @flow
|
||||
|
||||
const pp = Parser.prototype;
|
||||
import { types as tt, type TokenType } from "../tokenizer/types";
|
||||
import type { Decorator, Expression, Identifier, Node, ObjectExpression, ObjectPattern, Pattern, RestElement,
|
||||
SpreadElement } from "../types";
|
||||
import type { Pos, Position } from "../util/location";
|
||||
import { NodeUtils } from "./node";
|
||||
|
||||
// Convert existing expression atom to assignable pattern
|
||||
// if possible.
|
||||
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;
|
||||
|
||||
pp.toAssignable = function (node, isBinding, contextDescription) {
|
||||
if (node) {
|
||||
switch (node.type) {
|
||||
case "Identifier":
|
||||
case "ObjectPattern":
|
||||
case "ArrayPattern":
|
||||
case "AssignmentPattern":
|
||||
break;
|
||||
// Convert existing expression atom to assignable pattern
|
||||
// if possible.
|
||||
|
||||
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");
|
||||
toAssignable(node: Node, isBinding: ?boolean, contextDescription: string): Node {
|
||||
if (node) {
|
||||
switch (node.type) {
|
||||
case "Identifier":
|
||||
case "PrivateName":
|
||||
case "ObjectPattern":
|
||||
case "ArrayPattern":
|
||||
case "AssignmentPattern":
|
||||
break;
|
||||
|
||||
case "ObjectExpression":
|
||||
node.type = "ObjectPattern";
|
||||
for (const prop of node.properties) {
|
||||
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";
|
||||
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.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: 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 (
|
||||
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: $ReadOnlyArray<?Expression>): $ReadOnlyArray<?Expression> {
|
||||
return exprList;
|
||||
}
|
||||
|
||||
// Parses spread element.
|
||||
|
||||
parseSpread<T : RestElement | SpreadElement>(refShorthandDefaultPos: ?Pos): T {
|
||||
const node = this.startNode();
|
||||
this.next();
|
||||
node.argument = this.parseMaybeAssign(false, refShorthandDefaultPos);
|
||||
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): $ReadOnlyArray<Pattern> {
|
||||
const elts = [];
|
||||
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 = [];
|
||||
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: 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 "PrivateName":
|
||||
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) {
|
||||
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) {
|
||||
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,13 +1,16 @@
|
||||
// @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 pp = Parser.prototype;
|
||||
const commentKeys = ["leadingComments", "trailingComments", "innerComments"];
|
||||
|
||||
class Node {
|
||||
constructor(parser?: Parser, pos?: number, loc?: Position) {
|
||||
class Node implements NodeBase {
|
||||
constructor(parser: Parser, pos: number, loc: Position) {
|
||||
this.type = "";
|
||||
this.start = pos;
|
||||
this.end = 0;
|
||||
@@ -17,15 +20,22 @@ class Node {
|
||||
}
|
||||
|
||||
type: string;
|
||||
start: ?number;
|
||||
start: number;
|
||||
end: number;
|
||||
loc: SourceLocation;
|
||||
range: [number, number];
|
||||
leadingComments: ?Array<Comment>;
|
||||
trailingComments: ?Array<Comment>;
|
||||
innerComments: ?Array<Comment>;
|
||||
extra: { [key: string]: any };
|
||||
|
||||
__clone(): Node {
|
||||
const node2 = new Node;
|
||||
__clone(): this {
|
||||
// $FlowIgnore
|
||||
const node2: any = new Node;
|
||||
for (const key in this) {
|
||||
// Do not clone comments that are already attached to the node
|
||||
if (commentKeys.indexOf(key) < 0) {
|
||||
// $FlowIgnore
|
||||
node2[key] = this[key];
|
||||
}
|
||||
}
|
||||
@@ -34,43 +44,40 @@ class Node {
|
||||
}
|
||||
}
|
||||
|
||||
pp.startNode = function () {
|
||||
return new Node(this, this.state.start, this.state.startLoc);
|
||||
};
|
||||
export class NodeUtils extends UtilParser {
|
||||
startNode<T : NodeType>(): T {
|
||||
// $FlowIgnore
|
||||
return new Node(this, this.state.start, this.state.startLoc);
|
||||
}
|
||||
|
||||
pp.startNodeAt = function (pos, loc) {
|
||||
return new Node(this, pos, loc);
|
||||
};
|
||||
startNodeAt<T : NodeType>(pos: number, loc: Position): T {
|
||||
// $FlowIgnore
|
||||
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<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];
|
||||
}
|
||||
}
|
||||
|
||||
// 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,91 @@
|
||||
import { types as tt } from "../tokenizer/types";
|
||||
import Parser from "./index";
|
||||
import { lineBreak } from "../util/whitespace";
|
||||
// @flow
|
||||
|
||||
const pp = Parser.prototype;
|
||||
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
|
||||
|
||||
// TODO
|
||||
export default class UtilParser extends Tokenizer {
|
||||
// TODO
|
||||
|
||||
pp.addExtra = function (node, key, val) {
|
||||
if (!node) return;
|
||||
addExtra(node: Node, key: string, val: any): void {
|
||||
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: "<" | ">"): boolean {
|
||||
return this.match(tt.relational) && this.state.value === op;
|
||||
}
|
||||
this.raise(pos != null ? pos : this.state.start, messageOrType);
|
||||
};
|
||||
|
||||
// TODO
|
||||
|
||||
expectRelational(op: "<" | ">"): void {
|
||||
if (this.isRelational(op)) {
|
||||
this.next();
|
||||
} else {
|
||||
this.unexpected(null, tt.relational);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests whether parsed token is a contextual keyword.
|
||||
|
||||
isContextual(name: string): boolean {
|
||||
return this.match(tt.name) && this.state.value === name;
|
||||
}
|
||||
|
||||
// Consumes contextual keyword if possible.
|
||||
|
||||
eatContextual(name: string): boolean {
|
||||
return this.state.value === 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) ||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,248 +1,234 @@
|
||||
// @flow
|
||||
|
||||
import { types as tt } from "../tokenizer/types";
|
||||
import Parser from "../parser";
|
||||
import type Parser from "../parser";
|
||||
import * as N from "../types";
|
||||
|
||||
const pp = Parser.prototype;
|
||||
|
||||
pp.estreeParseRegExpLiteral = function ({ pattern, flags }) {
|
||||
let regex = null;
|
||||
try {
|
||||
regex = new RegExp(pattern, flags);
|
||||
} catch (e) {
|
||||
// In environments that don't support these flags value will
|
||||
// be null as the regex can't be represented natively.
|
||||
}
|
||||
const node = this.estreeParseLiteral(regex);
|
||||
node.regex = { pattern, flags };
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
pp.estreeParseLiteral = function (value) {
|
||||
return this.parseLiteral(value, "Literal");
|
||||
};
|
||||
|
||||
pp.directiveToStmt = function (directive) {
|
||||
const directiveLiteral = directive.value;
|
||||
|
||||
const stmt = this.startNodeAt(directive.start, directive.loc.start);
|
||||
const expression = this.startNodeAt(directiveLiteral.start, directiveLiteral.loc.start);
|
||||
|
||||
expression.value = directiveLiteral.value;
|
||||
expression.raw = directiveLiteral.extra.raw;
|
||||
|
||||
stmt.expression = this.finishNodeAt(expression, "Literal", directiveLiteral.end, directiveLiteral.loc.end);
|
||||
stmt.directive = directiveLiteral.extra.raw.slice(1, -1);
|
||||
|
||||
return this.finishNodeAt(stmt, "ExpressionStatement", directive.end, directive.loc.end);
|
||||
};
|
||||
|
||||
function isSimpleProperty(node) {
|
||||
return node &&
|
||||
function isSimpleProperty(node: N.Node): boolean {
|
||||
return node != null &&
|
||||
node.type === "Property" &&
|
||||
node.kind === "init" &&
|
||||
node.method === false;
|
||||
}
|
||||
|
||||
export default function (instance) {
|
||||
instance.extend("checkDeclaration", function(inner) {
|
||||
return function (node) {
|
||||
if (isSimpleProperty(node)) {
|
||||
this.checkDeclaration(node.value);
|
||||
export default (superClass: Class<Parser>): Class<Parser> => class extends superClass {
|
||||
estreeParseRegExpLiteral({ pattern, flags }: N.RegExpLiteral): N.Node {
|
||||
let regex = null;
|
||||
try {
|
||||
regex = new RegExp(pattern, flags);
|
||||
} catch (e) {
|
||||
// In environments that don't support these flags value will
|
||||
// be null as the regex can't be represented natively.
|
||||
}
|
||||
const node = this.estreeParseLiteral(regex);
|
||||
node.regex = { pattern, flags };
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
estreeParseLiteral(value: any): N.Node {
|
||||
return this.parseLiteral(value, "Literal");
|
||||
}
|
||||
|
||||
directiveToStmt(directive: N.Directive): N.ExpressionStatement {
|
||||
const directiveLiteral = directive.value;
|
||||
|
||||
const stmt = this.startNodeAt(directive.start, directive.loc.start);
|
||||
const expression = this.startNodeAt(directiveLiteral.start, directiveLiteral.loc.start);
|
||||
|
||||
expression.value = directiveLiteral.value;
|
||||
expression.raw = directiveLiteral.extra.raw;
|
||||
|
||||
stmt.expression = this.finishNodeAt(
|
||||
expression, "Literal", directiveLiteral.end, directiveLiteral.loc.end);
|
||||
stmt.directive = directiveLiteral.extra.raw.slice(1, -1);
|
||||
|
||||
return this.finishNodeAt(stmt, "ExpressionStatement", directive.end, directive.loc.end);
|
||||
}
|
||||
|
||||
// ==================================
|
||||
// Overrides
|
||||
// ==================================
|
||||
|
||||
checkDeclaration(node: N.Pattern): void {
|
||||
if (isSimpleProperty(node)) {
|
||||
// $FlowFixMe
|
||||
this.checkDeclaration(node.value);
|
||||
} else {
|
||||
super.checkDeclaration(node);
|
||||
}
|
||||
}
|
||||
|
||||
checkGetterSetterParamCount(prop: N.ObjectMethod | N.ClassMethod): void {
|
||||
const paramCount = prop.kind === "get" ? 0 : 1;
|
||||
// $FlowFixMe (prop.value present for ObjectMethod, but for ClassMethod should use prop.params?)
|
||||
if (prop.value.params.length !== paramCount) {
|
||||
const start = prop.start;
|
||||
if (prop.kind === "get") {
|
||||
this.raise(start, "getter should have no params");
|
||||
} else {
|
||||
inner.call(this, node);
|
||||
this.raise(start, "setter should have exactly one param");
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
instance.extend("checkGetterSetterParamCount", function() {
|
||||
return function (prop) {
|
||||
const paramCount = prop.kind === "get" ? 0 : 1;
|
||||
if (prop.value.params.length !== paramCount) {
|
||||
const start = prop.start;
|
||||
if (prop.kind === "get") {
|
||||
this.raise(start, "getter should have no params");
|
||||
checkLVal(
|
||||
expr: N.Expression, isBinding: ?boolean, checkClashes: ?{ [key: string]: boolean }, ...args): void {
|
||||
switch (expr.type) {
|
||||
case "ObjectPattern":
|
||||
expr.properties.forEach((prop) => {
|
||||
this.checkLVal(
|
||||
prop.type === "Property" ? prop.value : prop,
|
||||
isBinding,
|
||||
checkClashes,
|
||||
"object destructuring pattern"
|
||||
);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
super.checkLVal(expr, isBinding, checkClashes, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
checkPropClash(prop: N.ObjectMember, propHash: { [key: string]: boolean }): void {
|
||||
if (prop.computed || !isSimpleProperty(prop)) return;
|
||||
|
||||
const key = prop.key;
|
||||
// It is either an Identifier or a String/NumericLiteral
|
||||
const name = key.type === "Identifier" ? key.name : String(key.value);
|
||||
|
||||
if (name === "__proto__") {
|
||||
if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property");
|
||||
propHash.proto = true;
|
||||
}
|
||||
}
|
||||
|
||||
isStrictBody(node: { body: N.BlockStatement }, isExpression?: boolean): boolean {
|
||||
if (!isExpression && node.body.body.length > 0) {
|
||||
for (const directive of node.body.body) {
|
||||
if (directive.type === "ExpressionStatement" && directive.expression.type === "Literal") {
|
||||
if (directive.expression.value === "use strict") return true;
|
||||
} else {
|
||||
this.raise(start, "setter should have exactly one param");
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("checkLVal", function(inner) {
|
||||
return function (expr, isBinding, checkClashes, ...args) {
|
||||
switch (expr.type) {
|
||||
case "ObjectPattern":
|
||||
expr.properties.forEach((prop) => {
|
||||
this.checkLVal(
|
||||
prop.type === "Property" ? prop.value : prop,
|
||||
isBinding,
|
||||
checkClashes,
|
||||
"object destructuring pattern"
|
||||
);
|
||||
});
|
||||
// Break for the first non literal expression
|
||||
break;
|
||||
default:
|
||||
inner.call(this, expr, isBinding, checkClashes, ...args);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
instance.extend("checkPropClash", function () {
|
||||
return function (prop, propHash) {
|
||||
if (prop.computed || !isSimpleProperty(prop)) return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const key = prop.key;
|
||||
// It is either an Identifier or a String/NumericLiteral
|
||||
const name = key.type === "Identifier" ? key.name : String(key.value);
|
||||
isValidDirective(stmt: N.Statement): boolean {
|
||||
return stmt.type === "ExpressionStatement" &&
|
||||
stmt.expression.type === "Literal" &&
|
||||
typeof stmt.expression.value === "string" &&
|
||||
(!stmt.expression.extra || !stmt.expression.extra.parenthesized);
|
||||
}
|
||||
|
||||
if (name === "__proto__") {
|
||||
if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property");
|
||||
propHash.proto = true;
|
||||
}
|
||||
};
|
||||
});
|
||||
parseBlockBody(node: N.BlockStatementLike, ...args): void {
|
||||
super.parseBlockBody(node, ...args);
|
||||
|
||||
instance.extend("isStrictBody", function () {
|
||||
return function (node, isExpression) {
|
||||
if (!isExpression && node.body.body.length > 0) {
|
||||
for (const directive of (node.body.body: Array<Object>)) {
|
||||
if (directive.type === "ExpressionStatement" && directive.expression.type === "Literal") {
|
||||
if (directive.expression.value === "use strict") return true;
|
||||
} else {
|
||||
// Break for the first non literal expression
|
||||
break;
|
||||
}
|
||||
const directiveStatements = node.directives.map((d) => this.directiveToStmt(d));
|
||||
node.body = directiveStatements.concat(node.body);
|
||||
delete node.directives;
|
||||
}
|
||||
|
||||
parseClassMethod(classBody: N.ClassBody, ...args) {
|
||||
super.parseClassMethod(classBody, ...args);
|
||||
|
||||
const body = classBody.body;
|
||||
// $FlowIgnore
|
||||
body[body.length - 1].type = "MethodDefinition";
|
||||
}
|
||||
|
||||
parseExprAtom(...args): N.Expression {
|
||||
switch (this.state.type) {
|
||||
case tt.regexp:
|
||||
return this.estreeParseRegExpLiteral(this.state.value);
|
||||
|
||||
case tt.num:
|
||||
case tt.string:
|
||||
return this.estreeParseLiteral(this.state.value);
|
||||
|
||||
case tt._null:
|
||||
return this.estreeParseLiteral(null);
|
||||
|
||||
case tt._true:
|
||||
return this.estreeParseLiteral(true);
|
||||
|
||||
case tt._false:
|
||||
return this.estreeParseLiteral(false);
|
||||
|
||||
default:
|
||||
return super.parseExprAtom(...args);
|
||||
}
|
||||
}
|
||||
|
||||
parseLiteral<T : N.Literal>(...args): T {
|
||||
const node = super.parseLiteral(...args);
|
||||
node.raw = node.extra.raw;
|
||||
delete node.extra;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
parseMethod(node: N.MethodLike, ...args): N.MethodLike {
|
||||
let funcNode = this.startNode();
|
||||
funcNode.kind = node.kind; // provide kind, so super method correctly sets state
|
||||
funcNode = super.parseMethod(funcNode, ...args);
|
||||
delete funcNode.kind;
|
||||
// $FlowIgnore
|
||||
node.value = this.finishNode(funcNode, "FunctionExpression");
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
parseObjectMethod(...args): ?N.ObjectMethod {
|
||||
const node = super.parseObjectMethod(...args);
|
||||
|
||||
if (node) {
|
||||
// $FlowIgnore
|
||||
if (node.kind === "method") node.kind = "init";
|
||||
// $FlowIgnore
|
||||
node.type = "Property";
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
parseObjectProperty(...args): ?N.ObjectProperty {
|
||||
const node = super.parseObjectProperty(...args);
|
||||
|
||||
if (node) {
|
||||
// $FlowIgnore
|
||||
node.kind = "init";
|
||||
// $FlowIgnore
|
||||
node.type = "Property";
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
toAssignable(node: N.Node, isBinding: ?boolean, ...args): N.Node {
|
||||
if (isSimpleProperty(node)) {
|
||||
this.toAssignable(node.value, isBinding, ...args);
|
||||
|
||||
return node;
|
||||
} else if (node.type === "ObjectExpression") {
|
||||
node.type = "ObjectPattern";
|
||||
for (const prop of node.properties) {
|
||||
if (prop.kind === "get" || prop.kind === "set") {
|
||||
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
|
||||
} else if (prop.method) {
|
||||
this.raise(prop.key.start, "Object pattern can't contain methods");
|
||||
} else {
|
||||
this.toAssignable(prop, isBinding, "object destructuring pattern");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("isValidDirective", function () {
|
||||
return function (stmt) {
|
||||
return stmt.type === "ExpressionStatement" &&
|
||||
stmt.expression.type === "Literal" &&
|
||||
typeof stmt.expression.value === "string" &&
|
||||
(!stmt.expression.extra || !stmt.expression.extra.parenthesized);
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("parseBlockBody", function (inner) {
|
||||
return function (node, ...args) {
|
||||
inner.call(this, node, ...args);
|
||||
|
||||
node.directives.reverse().forEach((directive) => {
|
||||
node.body.unshift(this.directiveToStmt(directive));
|
||||
});
|
||||
delete node.directives;
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("parseClassMethod", function (inner) {
|
||||
return function (classBody, ...args) {
|
||||
inner.call(this, classBody, ...args);
|
||||
|
||||
const body = classBody.body;
|
||||
body[body.length - 1].type = "MethodDefinition";
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("parseExprAtom", function(inner) {
|
||||
return function (...args) {
|
||||
switch (this.state.type) {
|
||||
case tt.regexp:
|
||||
return this.estreeParseRegExpLiteral(this.state.value);
|
||||
|
||||
case tt.num:
|
||||
case tt.string:
|
||||
return this.estreeParseLiteral(this.state.value);
|
||||
|
||||
case tt._null:
|
||||
return this.estreeParseLiteral(null);
|
||||
|
||||
case tt._true:
|
||||
return this.estreeParseLiteral(true);
|
||||
|
||||
case tt._false:
|
||||
return this.estreeParseLiteral(false);
|
||||
|
||||
default:
|
||||
return inner.call(this, ...args);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("parseLiteral", function(inner) {
|
||||
return function (...args) {
|
||||
const node = inner.call(this, ...args);
|
||||
node.raw = node.extra.raw;
|
||||
delete node.extra;
|
||||
|
||||
return node;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
instance.extend("parseMethod", function(inner) {
|
||||
return function (node, ...args) {
|
||||
let funcNode = this.startNode();
|
||||
funcNode.kind = node.kind; // provide kind, so inner method correctly sets state
|
||||
funcNode = inner.call(this, funcNode, ...args);
|
||||
delete funcNode.kind;
|
||||
node.value = this.finishNode(funcNode, "FunctionExpression");
|
||||
|
||||
return node;
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("parseObjectMethod", function(inner) {
|
||||
return function (...args) {
|
||||
const node = inner.call(this, ...args);
|
||||
|
||||
if (node) {
|
||||
if (node.kind === "method") node.kind = "init";
|
||||
node.type = "Property";
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("parseObjectProperty", function(inner) {
|
||||
return function (...args) {
|
||||
const node = inner.call(this, ...args);
|
||||
|
||||
if (node) {
|
||||
node.kind = "init";
|
||||
node.type = "Property";
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("toAssignable", function(inner) {
|
||||
return function (node, isBinding, ...args) {
|
||||
if (isSimpleProperty(node)) {
|
||||
this.toAssignable(node.value, isBinding, ...args);
|
||||
|
||||
return node;
|
||||
} else if (node.type === "ObjectExpression") {
|
||||
node.type = "ObjectPattern";
|
||||
for (const prop of (node.properties: Array<Object>)) {
|
||||
if (prop.kind === "get" || prop.kind === "set") {
|
||||
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
|
||||
} else if (prop.method) {
|
||||
this.raise(prop.key.start, "Object pattern can't contain methods");
|
||||
} else {
|
||||
this.toAssignable(prop, isBinding, "object destructuring pattern");
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
return inner.call(this, node, isBinding, ...args);
|
||||
};
|
||||
});
|
||||
}
|
||||
return super.toAssignable(node, isBinding, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
2602
src/plugins/flow.js
2602
src/plugins/flow.js
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,12 @@
|
||||
// @flow
|
||||
|
||||
import XHTMLEntities from "./xhtml";
|
||||
import type Parser from "../../parser";
|
||||
import { TokenType, types as tt } from "../../tokenizer/types";
|
||||
import { TokContext, types as tc } from "../../tokenizer/context";
|
||||
import Parser from "../../parser";
|
||||
import * as N from "../../types";
|
||||
import { isIdentifierChar, isIdentifierStart } from "../../util/identifier";
|
||||
import type { Pos, Position } from "../../util/location";
|
||||
import { isNewLine } from "../../util/whitespace";
|
||||
|
||||
const HEX_NUMBER = /^[\da-fA-F]+$/;
|
||||
@@ -33,147 +37,9 @@ tt.jsxTagEnd.updateContext = function(prevType) {
|
||||
}
|
||||
};
|
||||
|
||||
const pp = Parser.prototype;
|
||||
|
||||
// Reads inline JSX contents token.
|
||||
|
||||
pp.jsxReadToken = function() {
|
||||
let out = "";
|
||||
let chunkStart = this.state.pos;
|
||||
for (;;) {
|
||||
if (this.state.pos >= this.input.length) {
|
||||
this.raise(this.state.start, "Unterminated JSX contents");
|
||||
}
|
||||
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
|
||||
switch (ch) {
|
||||
case 60: // "<"
|
||||
case 123: // "{"
|
||||
if (this.state.pos === this.state.start) {
|
||||
if (ch === 60 && this.state.exprAllowed) {
|
||||
++this.state.pos;
|
||||
return this.finishToken(tt.jsxTagStart);
|
||||
}
|
||||
return this.getTokenFromCode(ch);
|
||||
}
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
return this.finishToken(tt.jsxText, out);
|
||||
|
||||
case 38: // "&"
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
out += this.jsxReadEntity();
|
||||
chunkStart = this.state.pos;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (isNewLine(ch)) {
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
out += this.jsxReadNewLine(true);
|
||||
chunkStart = this.state.pos;
|
||||
} else {
|
||||
++this.state.pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pp.jsxReadNewLine = function(normalizeCRLF) {
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
let out;
|
||||
++this.state.pos;
|
||||
if (ch === 13 && this.input.charCodeAt(this.state.pos) === 10) {
|
||||
++this.state.pos;
|
||||
out = normalizeCRLF ? "\n" : "\r\n";
|
||||
} else {
|
||||
out = String.fromCharCode(ch);
|
||||
}
|
||||
++this.state.curLine;
|
||||
this.state.lineStart = this.state.pos;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
pp.jsxReadString = function(quote) {
|
||||
let out = "";
|
||||
let chunkStart = ++this.state.pos;
|
||||
for (;;) {
|
||||
if (this.state.pos >= this.input.length) {
|
||||
this.raise(this.state.start, "Unterminated string constant");
|
||||
}
|
||||
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
if (ch === quote) break;
|
||||
if (ch === 38) { // "&"
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
out += this.jsxReadEntity();
|
||||
chunkStart = this.state.pos;
|
||||
} else if (isNewLine(ch)) {
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
out += this.jsxReadNewLine(false);
|
||||
chunkStart = this.state.pos;
|
||||
} else {
|
||||
++this.state.pos;
|
||||
}
|
||||
}
|
||||
out += this.input.slice(chunkStart, this.state.pos++);
|
||||
return this.finishToken(tt.string, out);
|
||||
};
|
||||
|
||||
pp.jsxReadEntity = function() {
|
||||
let str = "";
|
||||
let count = 0;
|
||||
let entity;
|
||||
let ch = this.input[this.state.pos];
|
||||
|
||||
const startPos = ++this.state.pos;
|
||||
while (this.state.pos < this.input.length && count++ < 10) {
|
||||
ch = this.input[this.state.pos++];
|
||||
if (ch === ";") {
|
||||
if (str[0] === "#") {
|
||||
if (str[1] === "x") {
|
||||
str = str.substr(2);
|
||||
if (HEX_NUMBER.test(str))
|
||||
entity = String.fromCodePoint(parseInt(str, 16));
|
||||
} else {
|
||||
str = str.substr(1);
|
||||
if (DECIMAL_NUMBER.test(str))
|
||||
entity = String.fromCodePoint(parseInt(str, 10));
|
||||
}
|
||||
} else {
|
||||
entity = XHTMLEntities[str];
|
||||
}
|
||||
break;
|
||||
}
|
||||
str += ch;
|
||||
}
|
||||
if (!entity) {
|
||||
this.state.pos = startPos;
|
||||
return "&";
|
||||
}
|
||||
return entity;
|
||||
};
|
||||
|
||||
|
||||
// Read a JSX identifier (valid tag or attribute name).
|
||||
//
|
||||
// Optimized version since JSX identifiers can"t contain
|
||||
// escape characters and so can be read as single slice.
|
||||
// Also assumes that first character was already checked
|
||||
// by isIdentifierStart in readToken.
|
||||
|
||||
pp.jsxReadWord = function() {
|
||||
let ch;
|
||||
const start = this.state.pos;
|
||||
do {
|
||||
ch = this.input.charCodeAt(++this.state.pos);
|
||||
} while (isIdentifierChar(ch) || ch === 45); // "-"
|
||||
return this.finishToken(tt.jsxName, this.input.slice(start, this.state.pos));
|
||||
};
|
||||
|
||||
// Transforms JSX element name to string.
|
||||
|
||||
function getQualifiedJSXName(object) {
|
||||
function getQualifiedJSXName(object: N.JSXIdentifier | N.JSXNamespacedName | N.JSXMemberExpression): string {
|
||||
if (object.type === "JSXIdentifier") {
|
||||
return object.name;
|
||||
}
|
||||
@@ -185,282 +51,421 @@ function getQualifiedJSXName(object) {
|
||||
if (object.type === "JSXMemberExpression") {
|
||||
return getQualifiedJSXName(object.object) + "." + getQualifiedJSXName(object.property);
|
||||
}
|
||||
|
||||
// istanbul ignore next
|
||||
throw new Error("Node had unexpected type: " + object.type);
|
||||
}
|
||||
|
||||
// Parse next token as JSX identifier
|
||||
export default (superClass: Class<Parser>): Class<Parser> => class extends superClass {
|
||||
// Reads inline JSX contents token.
|
||||
|
||||
pp.jsxParseIdentifier = function() {
|
||||
const node = this.startNode();
|
||||
if (this.match(tt.jsxName)) {
|
||||
node.name = this.state.value;
|
||||
} else if (this.state.type.keyword) {
|
||||
node.name = this.state.type.keyword;
|
||||
} else {
|
||||
this.unexpected();
|
||||
}
|
||||
this.next();
|
||||
return this.finishNode(node, "JSXIdentifier");
|
||||
};
|
||||
|
||||
// Parse namespaced identifier.
|
||||
|
||||
pp.jsxParseNamespacedName = function() {
|
||||
const startPos = this.state.start;
|
||||
const startLoc = this.state.startLoc;
|
||||
const name = this.jsxParseIdentifier();
|
||||
if (!this.eat(tt.colon)) return name;
|
||||
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
node.namespace = name;
|
||||
node.name = this.jsxParseIdentifier();
|
||||
return this.finishNode(node, "JSXNamespacedName");
|
||||
};
|
||||
|
||||
// Parses element name in any form - namespaced, member
|
||||
// or single identifier.
|
||||
|
||||
pp.jsxParseElementName = function() {
|
||||
const startPos = this.state.start;
|
||||
const startLoc = this.state.startLoc;
|
||||
let node = this.jsxParseNamespacedName();
|
||||
while (this.eat(tt.dot)) {
|
||||
const newNode = this.startNodeAt(startPos, startLoc);
|
||||
newNode.object = node;
|
||||
newNode.property = this.jsxParseIdentifier();
|
||||
node = this.finishNode(newNode, "JSXMemberExpression");
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
// Parses any type of JSX attribute value.
|
||||
|
||||
pp.jsxParseAttributeValue = function() {
|
||||
let node;
|
||||
switch (this.state.type) {
|
||||
case tt.braceL:
|
||||
node = this.jsxParseExpressionContainer();
|
||||
if (node.expression.type === "JSXEmptyExpression") {
|
||||
this.raise(node.start, "JSX attributes must only be assigned a non-empty expression");
|
||||
} else {
|
||||
return node;
|
||||
jsxReadToken(): void {
|
||||
let out = "";
|
||||
let chunkStart = this.state.pos;
|
||||
for (;;) {
|
||||
if (this.state.pos >= this.input.length) {
|
||||
this.raise(this.state.start, "Unterminated JSX contents");
|
||||
}
|
||||
|
||||
case tt.jsxTagStart:
|
||||
case tt.string:
|
||||
return this.parseExprAtom();
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
|
||||
default:
|
||||
this.raise(this.state.start, "JSX value should be either an expression or a quoted JSX text");
|
||||
}
|
||||
};
|
||||
|
||||
// JSXEmptyExpression is unique type since it doesn't actually parse anything,
|
||||
// and so it should start at the end of last read token (left brace) and finish
|
||||
// at the beginning of the next one (right brace).
|
||||
|
||||
pp.jsxParseEmptyExpression = function() {
|
||||
const node = this.startNodeAt(this.state.lastTokEnd, this.state.lastTokEndLoc);
|
||||
return this.finishNodeAt(node, "JSXEmptyExpression", this.state.start, this.state.startLoc);
|
||||
};
|
||||
|
||||
// Parse JSX spread child
|
||||
|
||||
pp.jsxParseSpreadChild = function() {
|
||||
const node = this.startNode();
|
||||
this.expect(tt.braceL);
|
||||
this.expect(tt.ellipsis);
|
||||
node.expression = this.parseExpression();
|
||||
this.expect(tt.braceR);
|
||||
|
||||
return this.finishNode(node, "JSXSpreadChild");
|
||||
};
|
||||
|
||||
// Parses JSX expression enclosed into curly brackets.
|
||||
|
||||
|
||||
pp.jsxParseExpressionContainer = function() {
|
||||
const node = this.startNode();
|
||||
this.next();
|
||||
if (this.match(tt.braceR)) {
|
||||
node.expression = this.jsxParseEmptyExpression();
|
||||
} else {
|
||||
node.expression = this.parseExpression();
|
||||
}
|
||||
this.expect(tt.braceR);
|
||||
return this.finishNode(node, "JSXExpressionContainer");
|
||||
};
|
||||
|
||||
// Parses following JSX attribute name-value pair.
|
||||
|
||||
pp.jsxParseAttribute = function() {
|
||||
const node = this.startNode();
|
||||
if (this.eat(tt.braceL)) {
|
||||
this.expect(tt.ellipsis);
|
||||
node.argument = this.parseMaybeAssign();
|
||||
this.expect(tt.braceR);
|
||||
return this.finishNode(node, "JSXSpreadAttribute");
|
||||
}
|
||||
node.name = this.jsxParseNamespacedName();
|
||||
node.value = this.eat(tt.eq) ? this.jsxParseAttributeValue() : null;
|
||||
return this.finishNode(node, "JSXAttribute");
|
||||
};
|
||||
|
||||
// Parses JSX opening tag starting after "<".
|
||||
|
||||
pp.jsxParseOpeningElementAt = function(startPos, startLoc) {
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
node.attributes = [];
|
||||
node.name = this.jsxParseElementName();
|
||||
while (!this.match(tt.slash) && !this.match(tt.jsxTagEnd)) {
|
||||
node.attributes.push(this.jsxParseAttribute());
|
||||
}
|
||||
node.selfClosing = this.eat(tt.slash);
|
||||
this.expect(tt.jsxTagEnd);
|
||||
return this.finishNode(node, "JSXOpeningElement");
|
||||
};
|
||||
|
||||
// Parses JSX closing tag starting after "</".
|
||||
|
||||
pp.jsxParseClosingElementAt = function(startPos, startLoc) {
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
node.name = this.jsxParseElementName();
|
||||
this.expect(tt.jsxTagEnd);
|
||||
return this.finishNode(node, "JSXClosingElement");
|
||||
};
|
||||
|
||||
// Parses entire JSX element, including it"s opening tag
|
||||
// (starting after "<"), attributes, contents and closing tag.
|
||||
|
||||
pp.jsxParseElementAt = function(startPos, startLoc) {
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
const children = [];
|
||||
const openingElement = this.jsxParseOpeningElementAt(startPos, startLoc);
|
||||
let closingElement = null;
|
||||
|
||||
if (!openingElement.selfClosing) {
|
||||
contents: for (;;) {
|
||||
switch (this.state.type) {
|
||||
case tt.jsxTagStart:
|
||||
startPos = this.state.start; startLoc = this.state.startLoc;
|
||||
this.next();
|
||||
if (this.eat(tt.slash)) {
|
||||
closingElement = this.jsxParseClosingElementAt(startPos, startLoc);
|
||||
break contents;
|
||||
switch (ch) {
|
||||
case 60: // "<"
|
||||
case 123: // "{"
|
||||
if (this.state.pos === this.state.start) {
|
||||
if (ch === 60 && this.state.exprAllowed) {
|
||||
++this.state.pos;
|
||||
return this.finishToken(tt.jsxTagStart);
|
||||
}
|
||||
return this.getTokenFromCode(ch);
|
||||
}
|
||||
children.push(this.jsxParseElementAt(startPos, startLoc));
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
return this.finishToken(tt.jsxText, out);
|
||||
|
||||
case 38: // "&"
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
out += this.jsxReadEntity();
|
||||
chunkStart = this.state.pos;
|
||||
break;
|
||||
|
||||
case tt.jsxText:
|
||||
children.push(this.parseExprAtom());
|
||||
break;
|
||||
|
||||
case tt.braceL:
|
||||
if (this.lookahead().type === tt.ellipsis) {
|
||||
children.push(this.jsxParseSpreadChild());
|
||||
} else {
|
||||
children.push(this.jsxParseExpressionContainer());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// istanbul ignore next - should never happen
|
||||
default:
|
||||
this.unexpected();
|
||||
if (isNewLine(ch)) {
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
out += this.jsxReadNewLine(true);
|
||||
chunkStart = this.state.pos;
|
||||
} else {
|
||||
++this.state.pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
|
||||
this.raise(
|
||||
closingElement.start,
|
||||
"Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">"
|
||||
);
|
||||
jsxReadNewLine(normalizeCRLF: boolean): string {
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
let out;
|
||||
++this.state.pos;
|
||||
if (ch === 13 && this.input.charCodeAt(this.state.pos) === 10) {
|
||||
++this.state.pos;
|
||||
out = normalizeCRLF ? "\n" : "\r\n";
|
||||
} else {
|
||||
out = String.fromCharCode(ch);
|
||||
}
|
||||
++this.state.curLine;
|
||||
this.state.lineStart = this.state.pos;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
node.openingElement = openingElement;
|
||||
node.closingElement = closingElement;
|
||||
node.children = children;
|
||||
if (this.match(tt.relational) && this.state.value === "<") {
|
||||
this.raise(this.state.start, "Adjacent JSX elements must be wrapped in an enclosing tag");
|
||||
}
|
||||
return this.finishNode(node, "JSXElement");
|
||||
};
|
||||
jsxReadString(quote: number): void {
|
||||
let out = "";
|
||||
let chunkStart = ++this.state.pos;
|
||||
for (;;) {
|
||||
if (this.state.pos >= this.input.length) {
|
||||
this.raise(this.state.start, "Unterminated string constant");
|
||||
}
|
||||
|
||||
// Parses entire JSX element from current position.
|
||||
|
||||
pp.jsxParseElement = function() {
|
||||
const startPos = this.state.start;
|
||||
const startLoc = this.state.startLoc;
|
||||
this.next();
|
||||
return this.jsxParseElementAt(startPos, startLoc);
|
||||
};
|
||||
|
||||
export default function(instance) {
|
||||
instance.extend("parseExprAtom", function(inner) {
|
||||
return function(refShortHandDefaultPos) {
|
||||
if (this.match(tt.jsxText)) {
|
||||
return this.parseLiteral(this.state.value, "JSXText");
|
||||
} else if (this.match(tt.jsxTagStart)) {
|
||||
return this.jsxParseElement();
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
if (ch === quote) break;
|
||||
if (ch === 38) { // "&"
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
out += this.jsxReadEntity();
|
||||
chunkStart = this.state.pos;
|
||||
} else if (isNewLine(ch)) {
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
out += this.jsxReadNewLine(false);
|
||||
chunkStart = this.state.pos;
|
||||
} else {
|
||||
return inner.call(this, refShortHandDefaultPos);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
instance.extend("readToken", function(inner) {
|
||||
return function(code) {
|
||||
if (this.state.inPropertyName) return inner.call(this, code);
|
||||
|
||||
const context = this.curContext();
|
||||
|
||||
if (context === tc.j_expr) {
|
||||
return this.jsxReadToken();
|
||||
}
|
||||
|
||||
if (context === tc.j_oTag || context === tc.j_cTag) {
|
||||
if (isIdentifierStart(code)) {
|
||||
return this.jsxReadWord();
|
||||
}
|
||||
|
||||
if (code === 62) {
|
||||
++this.state.pos;
|
||||
return this.finishToken(tt.jsxTagEnd);
|
||||
}
|
||||
|
||||
if ((code === 34 || code === 39) && context === tc.j_oTag) {
|
||||
return this.jsxReadString(code);
|
||||
}
|
||||
}
|
||||
|
||||
if (code === 60 && this.state.exprAllowed) {
|
||||
++this.state.pos;
|
||||
return this.finishToken(tt.jsxTagStart);
|
||||
}
|
||||
}
|
||||
out += this.input.slice(chunkStart, this.state.pos++);
|
||||
return this.finishToken(tt.string, out);
|
||||
}
|
||||
|
||||
return inner.call(this, code);
|
||||
};
|
||||
});
|
||||
jsxReadEntity(): string {
|
||||
let str = "";
|
||||
let count = 0;
|
||||
let entity;
|
||||
let ch = this.input[this.state.pos];
|
||||
|
||||
instance.extend("updateContext", function(inner) {
|
||||
return function(prevType) {
|
||||
if (this.match(tt.braceL)) {
|
||||
const curContext = this.curContext();
|
||||
if (curContext === tc.j_oTag) {
|
||||
this.state.context.push(tc.braceExpression);
|
||||
} else if (curContext === tc.j_expr) {
|
||||
this.state.context.push(tc.templateQuasi);
|
||||
const startPos = ++this.state.pos;
|
||||
while (this.state.pos < this.input.length && count++ < 10) {
|
||||
ch = this.input[this.state.pos++];
|
||||
if (ch === ";") {
|
||||
if (str[0] === "#") {
|
||||
if (str[1] === "x") {
|
||||
str = str.substr(2);
|
||||
if (HEX_NUMBER.test(str))
|
||||
entity = String.fromCodePoint(parseInt(str, 16));
|
||||
} else {
|
||||
str = str.substr(1);
|
||||
if (DECIMAL_NUMBER.test(str))
|
||||
entity = String.fromCodePoint(parseInt(str, 10));
|
||||
}
|
||||
} else {
|
||||
inner.call(this, prevType);
|
||||
entity = XHTMLEntities[str];
|
||||
}
|
||||
this.state.exprAllowed = true;
|
||||
} else if (this.match(tt.slash) && prevType === tt.jsxTagStart) {
|
||||
this.state.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore
|
||||
this.state.context.push(tc.j_cTag); // reconsider as closing tag context
|
||||
this.state.exprAllowed = false;
|
||||
} else {
|
||||
return inner.call(this, prevType);
|
||||
break;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
str += ch;
|
||||
}
|
||||
if (!entity) {
|
||||
this.state.pos = startPos;
|
||||
return "&";
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
// Read a JSX identifier (valid tag or attribute name).
|
||||
//
|
||||
// Optimized version since JSX identifiers can"t contain
|
||||
// escape characters and so can be read as single slice.
|
||||
// Also assumes that first character was already checked
|
||||
// by isIdentifierStart in readToken.
|
||||
|
||||
jsxReadWord(): void {
|
||||
let ch;
|
||||
const start = this.state.pos;
|
||||
do {
|
||||
ch = this.input.charCodeAt(++this.state.pos);
|
||||
} while (isIdentifierChar(ch) || ch === 45); // "-"
|
||||
return this.finishToken(tt.jsxName, this.input.slice(start, this.state.pos));
|
||||
}
|
||||
|
||||
// Parse next token as JSX identifier
|
||||
|
||||
jsxParseIdentifier(): N.JSXIdentifier {
|
||||
const node = this.startNode();
|
||||
if (this.match(tt.jsxName)) {
|
||||
node.name = this.state.value;
|
||||
} else if (this.state.type.keyword) {
|
||||
node.name = this.state.type.keyword;
|
||||
} else {
|
||||
this.unexpected();
|
||||
}
|
||||
this.next();
|
||||
return this.finishNode(node, "JSXIdentifier");
|
||||
}
|
||||
|
||||
// Parse namespaced identifier.
|
||||
|
||||
jsxParseNamespacedName(): N.JSXNamespacedName {
|
||||
const startPos = this.state.start;
|
||||
const startLoc = this.state.startLoc;
|
||||
const name = this.jsxParseIdentifier();
|
||||
if (!this.eat(tt.colon)) return name;
|
||||
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
node.namespace = name;
|
||||
node.name = this.jsxParseIdentifier();
|
||||
return this.finishNode(node, "JSXNamespacedName");
|
||||
}
|
||||
|
||||
// Parses element name in any form - namespaced, member
|
||||
// or single identifier.
|
||||
|
||||
jsxParseElementName(): N.JSXNamespacedName | N.JSXMemberExpression {
|
||||
const startPos = this.state.start;
|
||||
const startLoc = this.state.startLoc;
|
||||
let node = this.jsxParseNamespacedName();
|
||||
while (this.eat(tt.dot)) {
|
||||
const newNode = this.startNodeAt(startPos, startLoc);
|
||||
newNode.object = node;
|
||||
newNode.property = this.jsxParseIdentifier();
|
||||
node = this.finishNode(newNode, "JSXMemberExpression");
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// Parses any type of JSX attribute value.
|
||||
|
||||
jsxParseAttributeValue(): N.Expression {
|
||||
let node;
|
||||
switch (this.state.type) {
|
||||
case tt.braceL:
|
||||
node = this.jsxParseExpressionContainer();
|
||||
if (node.expression.type === "JSXEmptyExpression") {
|
||||
throw this.raise(node.start, "JSX attributes must only be assigned a non-empty expression");
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
|
||||
case tt.jsxTagStart:
|
||||
case tt.string:
|
||||
return this.parseExprAtom();
|
||||
|
||||
default:
|
||||
throw this.raise(this.state.start, "JSX value should be either an expression or a quoted JSX text");
|
||||
}
|
||||
}
|
||||
|
||||
// JSXEmptyExpression is unique type since it doesn't actually parse anything,
|
||||
// and so it should start at the end of last read token (left brace) and finish
|
||||
// at the beginning of the next one (right brace).
|
||||
|
||||
jsxParseEmptyExpression(): N.JSXEmptyExpression {
|
||||
const node = this.startNodeAt(this.state.lastTokEnd, this.state.lastTokEndLoc);
|
||||
return this.finishNodeAt(node, "JSXEmptyExpression", this.state.start, this.state.startLoc);
|
||||
}
|
||||
|
||||
// Parse JSX spread child
|
||||
|
||||
jsxParseSpreadChild(): N.JSXSpreadChild {
|
||||
const node = this.startNode();
|
||||
this.expect(tt.braceL);
|
||||
this.expect(tt.ellipsis);
|
||||
node.expression = this.parseExpression();
|
||||
this.expect(tt.braceR);
|
||||
|
||||
return this.finishNode(node, "JSXSpreadChild");
|
||||
}
|
||||
|
||||
// Parses JSX expression enclosed into curly brackets.
|
||||
|
||||
|
||||
jsxParseExpressionContainer(): N.JSXExpressionContainer {
|
||||
const node = this.startNode();
|
||||
this.next();
|
||||
if (this.match(tt.braceR)) {
|
||||
node.expression = this.jsxParseEmptyExpression();
|
||||
} else {
|
||||
node.expression = this.parseExpression();
|
||||
}
|
||||
this.expect(tt.braceR);
|
||||
return this.finishNode(node, "JSXExpressionContainer");
|
||||
}
|
||||
|
||||
// Parses following JSX attribute name-value pair.
|
||||
|
||||
jsxParseAttribute(): N.JSXAttribute {
|
||||
const node = this.startNode();
|
||||
if (this.eat(tt.braceL)) {
|
||||
this.expect(tt.ellipsis);
|
||||
node.argument = this.parseMaybeAssign();
|
||||
this.expect(tt.braceR);
|
||||
return this.finishNode(node, "JSXSpreadAttribute");
|
||||
}
|
||||
node.name = this.jsxParseNamespacedName();
|
||||
node.value = this.eat(tt.eq) ? this.jsxParseAttributeValue() : null;
|
||||
return this.finishNode(node, "JSXAttribute");
|
||||
}
|
||||
|
||||
// Parses JSX opening tag starting after "<".
|
||||
|
||||
jsxParseOpeningElementAt(startPos: number, startLoc: Position): N.JSXOpeningElement {
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
node.attributes = [];
|
||||
node.name = this.jsxParseElementName();
|
||||
while (!this.match(tt.slash) && !this.match(tt.jsxTagEnd)) {
|
||||
node.attributes.push(this.jsxParseAttribute());
|
||||
}
|
||||
node.selfClosing = this.eat(tt.slash);
|
||||
this.expect(tt.jsxTagEnd);
|
||||
return this.finishNode(node, "JSXOpeningElement");
|
||||
}
|
||||
|
||||
// Parses JSX closing tag starting after "</".
|
||||
|
||||
jsxParseClosingElementAt(startPos: number, startLoc: Position): N.JSXClosingElement {
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
node.name = this.jsxParseElementName();
|
||||
this.expect(tt.jsxTagEnd);
|
||||
return this.finishNode(node, "JSXClosingElement");
|
||||
}
|
||||
|
||||
// Parses entire JSX element, including it"s opening tag
|
||||
// (starting after "<"), attributes, contents and closing tag.
|
||||
|
||||
jsxParseElementAt(startPos: number, startLoc: Position): N.JSXElement {
|
||||
const node = this.startNodeAt(startPos, startLoc);
|
||||
const children = [];
|
||||
const openingElement = this.jsxParseOpeningElementAt(startPos, startLoc);
|
||||
let closingElement = null;
|
||||
|
||||
if (!openingElement.selfClosing) {
|
||||
contents: for (;;) {
|
||||
switch (this.state.type) {
|
||||
case tt.jsxTagStart:
|
||||
startPos = this.state.start; startLoc = this.state.startLoc;
|
||||
this.next();
|
||||
if (this.eat(tt.slash)) {
|
||||
closingElement = this.jsxParseClosingElementAt(startPos, startLoc);
|
||||
break contents;
|
||||
}
|
||||
children.push(this.jsxParseElementAt(startPos, startLoc));
|
||||
break;
|
||||
|
||||
case tt.jsxText:
|
||||
children.push(this.parseExprAtom());
|
||||
break;
|
||||
|
||||
case tt.braceL:
|
||||
if (this.lookahead().type === tt.ellipsis) {
|
||||
children.push(this.jsxParseSpreadChild());
|
||||
} else {
|
||||
children.push(this.jsxParseExpressionContainer());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// istanbul ignore next - should never happen
|
||||
default:
|
||||
throw this.unexpected();
|
||||
}
|
||||
}
|
||||
|
||||
// $FlowIgnore
|
||||
if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
|
||||
this.raise(
|
||||
// $FlowIgnore
|
||||
closingElement.start,
|
||||
"Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
node.openingElement = openingElement;
|
||||
node.closingElement = closingElement;
|
||||
node.children = children;
|
||||
if (this.match(tt.relational) && this.state.value === "<") {
|
||||
this.raise(this.state.start, "Adjacent JSX elements must be wrapped in an enclosing tag");
|
||||
}
|
||||
return this.finishNode(node, "JSXElement");
|
||||
}
|
||||
|
||||
// Parses entire JSX element from current position.
|
||||
|
||||
jsxParseElement(): N.JSXElement {
|
||||
const startPos = this.state.start;
|
||||
const startLoc = this.state.startLoc;
|
||||
this.next();
|
||||
return this.jsxParseElementAt(startPos, startLoc);
|
||||
}
|
||||
|
||||
// ==================================
|
||||
// Overrides
|
||||
// ==================================
|
||||
|
||||
parseExprAtom(refShortHandDefaultPos: ?Pos): N.Expression {
|
||||
if (this.match(tt.jsxText)) {
|
||||
return this.parseLiteral(this.state.value, "JSXText");
|
||||
} else if (this.match(tt.jsxTagStart)) {
|
||||
return this.jsxParseElement();
|
||||
} else {
|
||||
return super.parseExprAtom(refShortHandDefaultPos);
|
||||
}
|
||||
}
|
||||
|
||||
readToken(code: number): void {
|
||||
if (this.state.inPropertyName) return super.readToken(code);
|
||||
|
||||
const context = this.curContext();
|
||||
|
||||
if (context === tc.j_expr) {
|
||||
return this.jsxReadToken();
|
||||
}
|
||||
|
||||
if (context === tc.j_oTag || context === tc.j_cTag) {
|
||||
if (isIdentifierStart(code)) {
|
||||
return this.jsxReadWord();
|
||||
}
|
||||
|
||||
if (code === 62) {
|
||||
++this.state.pos;
|
||||
return this.finishToken(tt.jsxTagEnd);
|
||||
}
|
||||
|
||||
if ((code === 34 || code === 39) && context === tc.j_oTag) {
|
||||
return this.jsxReadString(code);
|
||||
}
|
||||
}
|
||||
|
||||
if (code === 60 && this.state.exprAllowed) {
|
||||
++this.state.pos;
|
||||
return this.finishToken(tt.jsxTagStart);
|
||||
}
|
||||
|
||||
return super.readToken(code);
|
||||
}
|
||||
|
||||
updateContext(prevType: TokenType): void {
|
||||
if (this.match(tt.braceL)) {
|
||||
const curContext = this.curContext();
|
||||
if (curContext === tc.j_oTag) {
|
||||
this.state.context.push(tc.braceExpression);
|
||||
} else if (curContext === tc.j_expr) {
|
||||
this.state.context.push(tc.templateQuasi);
|
||||
} else {
|
||||
super.updateContext(prevType);
|
||||
}
|
||||
this.state.exprAllowed = true;
|
||||
} else if (this.match(tt.slash) && prevType === tt.jsxTagStart) {
|
||||
this.state.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore
|
||||
this.state.context.push(tc.j_cTag); // reconsider as closing tag context
|
||||
this.state.exprAllowed = false;
|
||||
} else {
|
||||
return super.updateContext(prevType);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export default {
|
||||
// @flow
|
||||
|
||||
const entities: { [name: string]: string } = {
|
||||
quot: "\u0022",
|
||||
amp: "&",
|
||||
apos: "\u0027",
|
||||
@@ -253,3 +255,4 @@ export default {
|
||||
hearts: "\u2665",
|
||||
diams: "\u2666"
|
||||
};
|
||||
export default entities;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
// The algorithm used to determine whether a regexp can appear at a
|
||||
// given point in the program is loosely based on sweet.js' approach.
|
||||
// See https://github.com/mozilla/sweet.js/wiki/design
|
||||
|
||||
@@ -1,19 +1,41 @@
|
||||
/* eslint max-len: 0 */
|
||||
|
||||
// @flow
|
||||
|
||||
import type { TokenType } from "./types";
|
||||
import type { Options } from "../options";
|
||||
import type { Position } from "../util/location";
|
||||
import { isIdentifierStart, isIdentifierChar, isKeyword } from "../util/identifier";
|
||||
import { types as tt, keywords as keywordTypes } from "./types";
|
||||
import { types as ct } from "./context";
|
||||
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";
|
||||
|
||||
|
||||
// The following character codes are forbidden from being
|
||||
// an immediate sibling of NumericLiteralSeparator _
|
||||
|
||||
const forbiddenNumericLiteralSeparatorSiblings = [
|
||||
46, // .
|
||||
66, // B
|
||||
69, // E
|
||||
79, // O
|
||||
88, // X
|
||||
95, // _ (multiple separators are not allowed)
|
||||
98, // b
|
||||
101, // e
|
||||
111, // o
|
||||
120, // x
|
||||
];
|
||||
|
||||
// Object type used to represent tokens. Note that normally, tokens
|
||||
// simply exist as properties on the parser object. This is only
|
||||
// used for the onToken callback and the external tokenizer.
|
||||
|
||||
export class Token {
|
||||
constructor(state) {
|
||||
constructor(state: State) {
|
||||
this.type = state.type;
|
||||
this.value = state.value;
|
||||
this.start = state.start;
|
||||
@@ -30,7 +52,7 @@ export class Token {
|
||||
|
||||
// ## Tokenizer
|
||||
|
||||
function codePointToString(code) {
|
||||
function codePointToString(code: number): string {
|
||||
// UTF-16 Decoding
|
||||
if (code <= 0xFFFF) {
|
||||
return String.fromCharCode(code);
|
||||
@@ -39,15 +61,22 @@ function codePointToString(code) {
|
||||
}
|
||||
}
|
||||
|
||||
export default class Tokenizer {
|
||||
constructor(options, input) {
|
||||
export default class Tokenizer extends LocationParser {
|
||||
// Forward-declarations
|
||||
// parser/util.js
|
||||
+unexpected: (pos?: ?number, messageOrType?: string | TokenType) => empty;
|
||||
|
||||
isLookahead: boolean;
|
||||
|
||||
constructor(options: Options, input: string) {
|
||||
super();
|
||||
this.state = new State;
|
||||
this.state.init(options, input);
|
||||
}
|
||||
|
||||
// Move to the next token
|
||||
|
||||
next() {
|
||||
next(): void {
|
||||
if (!this.isLookahead) {
|
||||
this.state.tokens.push(new Token(this.state));
|
||||
}
|
||||
@@ -61,7 +90,7 @@ export default class Tokenizer {
|
||||
|
||||
// TODO
|
||||
|
||||
eat(type) {
|
||||
eat(type: TokenType): boolean {
|
||||
if (this.match(type)) {
|
||||
this.next();
|
||||
return true;
|
||||
@@ -72,19 +101,19 @@ export default class Tokenizer {
|
||||
|
||||
// TODO
|
||||
|
||||
match(type) {
|
||||
match(type: TokenType): boolean {
|
||||
return this.state.type === type;
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
isKeyword(word) {
|
||||
isKeyword(word: string): boolean {
|
||||
return isKeyword(word);
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
lookahead() {
|
||||
lookahead(): State {
|
||||
const old = this.state;
|
||||
this.state = old.clone(true);
|
||||
|
||||
@@ -100,7 +129,7 @@ export default class Tokenizer {
|
||||
// Toggle strict mode. Re-reads the next number or string to please
|
||||
// pedantic tests (`"use strict"; 010;` should fail).
|
||||
|
||||
setStrict(strict) {
|
||||
setStrict(strict: boolean): void {
|
||||
this.state.strict = strict;
|
||||
if (!this.match(tt.num) && !this.match(tt.string)) return;
|
||||
this.state.pos = this.state.start;
|
||||
@@ -111,14 +140,14 @@ export default class Tokenizer {
|
||||
this.nextToken();
|
||||
}
|
||||
|
||||
curContext() {
|
||||
curContext(): TokContext {
|
||||
return this.state.context[this.state.context.length - 1];
|
||||
}
|
||||
|
||||
// Read a single token, updating the parser object's token-related
|
||||
// properties.
|
||||
|
||||
nextToken() {
|
||||
nextToken(): void {
|
||||
const curContext = this.curContext();
|
||||
if (!curContext || !curContext.preserveSpace) this.skipSpace();
|
||||
|
||||
@@ -135,7 +164,7 @@ export default class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
readToken(code) {
|
||||
readToken(code: number): void {
|
||||
// Identifier or keyword. '\uXXXX' sequences are allowed in
|
||||
// identifiers, so '\' also dispatches to that.
|
||||
if (isIdentifierStart(code) || code === 92 /* '\' */) {
|
||||
@@ -145,7 +174,7 @@ export default class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
fullCharCodeAtPos() {
|
||||
fullCharCodeAtPos(): number {
|
||||
const code = this.input.charCodeAt(this.state.pos);
|
||||
if (code <= 0xd7ff || code >= 0xe000) return code;
|
||||
|
||||
@@ -153,7 +182,7 @@ export default class Tokenizer {
|
||||
return (code << 10) + next - 0x35fdc00;
|
||||
}
|
||||
|
||||
pushComment(block, text, start, end, startLoc, endLoc) {
|
||||
pushComment(block: boolean, text: string, start: number, end: number, startLoc: Position, endLoc: Position): void {
|
||||
const comment = {
|
||||
type: block ? "CommentBlock" : "CommentLine",
|
||||
value: text,
|
||||
@@ -169,7 +198,7 @@ export default class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
skipBlockComment() {
|
||||
skipBlockComment(): void {
|
||||
const startLoc = this.state.curPosition();
|
||||
const start = this.state.pos;
|
||||
const end = this.input.indexOf("*/", this.state.pos += 2);
|
||||
@@ -186,7 +215,7 @@ export default class Tokenizer {
|
||||
this.pushComment(true, this.input.slice(start + 2, end), start, this.state.pos, startLoc, this.state.curPosition());
|
||||
}
|
||||
|
||||
skipLineComment(startSkip) {
|
||||
skipLineComment(startSkip: number): void {
|
||||
const start = this.state.pos;
|
||||
const startLoc = this.state.curPosition();
|
||||
let ch = this.input.charCodeAt(this.state.pos += startSkip);
|
||||
@@ -201,7 +230,7 @@ export default class Tokenizer {
|
||||
// Called at the start of the parse and after every token. Skips
|
||||
// whitespace and comments, and.
|
||||
|
||||
skipSpace() {
|
||||
skipSpace(): void {
|
||||
loop: while (this.state.pos < this.input.length) {
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
switch (ch) {
|
||||
@@ -250,7 +279,7 @@ export default class Tokenizer {
|
||||
// the token, so that the next one's `start` will point at the
|
||||
// right position.
|
||||
|
||||
finishToken(type, val) {
|
||||
finishToken(type: TokenType, val: any): void {
|
||||
this.state.end = this.state.pos;
|
||||
this.state.endLoc = this.state.curPosition();
|
||||
const prevType = this.state.type;
|
||||
@@ -269,7 +298,7 @@ export default class Tokenizer {
|
||||
//
|
||||
// All in the name of speed.
|
||||
//
|
||||
readToken_dot() {
|
||||
readToken_dot(): void {
|
||||
const next = this.input.charCodeAt(this.state.pos + 1);
|
||||
if (next >= 48 && next <= 57) {
|
||||
return this.readNumber(true);
|
||||
@@ -285,7 +314,7 @@ export default class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
readToken_slash() { // '/'
|
||||
readToken_slash(): void { // '/'
|
||||
if (this.state.exprAllowed) {
|
||||
++this.state.pos;
|
||||
return this.readRegexp();
|
||||
@@ -299,7 +328,7 @@ export default class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
readToken_mult_modulo(code) { // '%*'
|
||||
readToken_mult_modulo(code: number): void { // '%*'
|
||||
let type = code === 42 ? tt.star : tt.modulo;
|
||||
let width = 1;
|
||||
let next = this.input.charCodeAt(this.state.pos + 1);
|
||||
@@ -318,7 +347,7 @@ export default class Tokenizer {
|
||||
return this.finishOp(type, width);
|
||||
}
|
||||
|
||||
readToken_pipe_amp(code) { // '|&'
|
||||
readToken_pipe_amp(code: number): void { // '|&'
|
||||
const next = this.input.charCodeAt(this.state.pos + 1);
|
||||
if (next === code) return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2);
|
||||
if (next === 61) return this.finishOp(tt.assign, 2);
|
||||
@@ -326,7 +355,7 @@ export default class Tokenizer {
|
||||
return this.finishOp(code === 124 ? tt.bitwiseOR : tt.bitwiseAND, 1);
|
||||
}
|
||||
|
||||
readToken_caret() { // '^'
|
||||
readToken_caret(): void { // '^'
|
||||
const next = this.input.charCodeAt(this.state.pos + 1);
|
||||
if (next === 61) {
|
||||
return this.finishOp(tt.assign, 2);
|
||||
@@ -335,7 +364,7 @@ export default class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
readToken_plus_min(code) { // '+-'
|
||||
readToken_plus_min(code: number): void { // '+-'
|
||||
const next = this.input.charCodeAt(this.state.pos + 1);
|
||||
|
||||
if (next === code) {
|
||||
@@ -355,7 +384,7 @@ export default class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
readToken_lt_gt(code) { // '<>'
|
||||
readToken_lt_gt(code: number): void { // '<>'
|
||||
const next = this.input.charCodeAt(this.state.pos + 1);
|
||||
let size = 1;
|
||||
|
||||
@@ -381,7 +410,7 @@ export default class Tokenizer {
|
||||
return this.finishOp(tt.relational, size);
|
||||
}
|
||||
|
||||
readToken_eq_excl(code) { // '=!'
|
||||
readToken_eq_excl(code: number): void { // '=!'
|
||||
const next = this.input.charCodeAt(this.state.pos + 1);
|
||||
if (next === 61) return this.finishOp(tt.equality, this.input.charCodeAt(this.state.pos + 2) === 61 ? 3 : 2);
|
||||
if (code === 61 && next === 62) { // '=>'
|
||||
@@ -403,10 +432,19 @@ export default class Tokenizer {
|
||||
}
|
||||
}
|
||||
|
||||
getTokenFromCode(code) {
|
||||
getTokenFromCode(code: number): void {
|
||||
switch (code) {
|
||||
|
||||
case 35: // '#'
|
||||
if (this.hasPlugin("classPrivateProperties") && this.state.inClass) {
|
||||
++this.state.pos; return this.finishToken(tt.hash);
|
||||
} else {
|
||||
this.raise(this.state.pos, `Unexpected character '${codePointToString(code)}'`);
|
||||
}
|
||||
|
||||
// The interpretation of a dot depends on whether it is followed
|
||||
// by a digit or another two dots.
|
||||
|
||||
case 46: // '.'
|
||||
return this.readToken_dot();
|
||||
|
||||
@@ -491,13 +529,13 @@ export default class Tokenizer {
|
||||
this.raise(this.state.pos, `Unexpected character '${codePointToString(code)}'`);
|
||||
}
|
||||
|
||||
finishOp(type, size) {
|
||||
finishOp(type: TokenType, size: number): void {
|
||||
const str = this.input.slice(this.state.pos, this.state.pos + size);
|
||||
this.state.pos += size;
|
||||
return this.finishToken(type, str);
|
||||
}
|
||||
|
||||
readRegexp() {
|
||||
readRegexp(): void {
|
||||
const start = this.state.pos;
|
||||
let escaped, inClass;
|
||||
for (;;) {
|
||||
@@ -539,13 +577,30 @@ export default class Tokenizer {
|
||||
// were read, the integer value otherwise. When `len` is given, this
|
||||
// will return `null` unless the integer has exactly `len` digits.
|
||||
|
||||
readInt(radix, len) {
|
||||
readInt(radix: number, len?: number): number | null {
|
||||
const start = this.state.pos;
|
||||
let total = 0;
|
||||
|
||||
for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) {
|
||||
const code = this.input.charCodeAt(this.state.pos);
|
||||
let val;
|
||||
|
||||
if (this.hasPlugin("numericSeparator")) {
|
||||
const prev = this.input.charCodeAt(this.state.pos - 1);
|
||||
const next = this.input.charCodeAt(this.state.pos + 1);
|
||||
if (code === 95) {
|
||||
if ((forbiddenNumericLiteralSeparatorSiblings.indexOf(prev) > -1) ||
|
||||
(forbiddenNumericLiteralSeparatorSiblings.indexOf(next) > -1) ||
|
||||
Number.isNaN(next)) {
|
||||
this.raise(this.state.pos, "Invalid NumericLiteralSeparator");
|
||||
}
|
||||
|
||||
// Ignore this _ character
|
||||
++this.state.pos;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (code >= 97) {
|
||||
val = code - 97 + 10; // a
|
||||
} else if (code >= 65) {
|
||||
@@ -564,7 +619,7 @@ export default class Tokenizer {
|
||||
return total;
|
||||
}
|
||||
|
||||
readRadixNumber(radix) {
|
||||
readRadixNumber(radix: number): void {
|
||||
this.state.pos += 2; // 0x
|
||||
const val = this.readInt(radix);
|
||||
if (val == null) this.raise(this.state.start + 2, "Expected number in radix " + radix);
|
||||
@@ -574,32 +629,36 @@ export default class Tokenizer {
|
||||
|
||||
// Read an integer, octal integer, or floating-point number.
|
||||
|
||||
readNumber(startsWithDot) {
|
||||
readNumber(startsWithDot: boolean): void {
|
||||
const start = this.state.pos;
|
||||
const firstIsZero = this.input.charCodeAt(start) === 48; // '0'
|
||||
let octal = this.input.charCodeAt(start) === 48; // '0'
|
||||
let isFloat = false;
|
||||
|
||||
if (!startsWithDot && this.readInt(10) === null) this.raise(start, "Invalid number");
|
||||
if (octal && this.state.pos == start + 1) octal = false; // number === 0
|
||||
|
||||
let next = this.input.charCodeAt(this.state.pos);
|
||||
if (next === 46) { // '.'
|
||||
if (next === 46 && !octal) { // '.'
|
||||
++this.state.pos;
|
||||
this.readInt(10);
|
||||
isFloat = true;
|
||||
next = this.input.charCodeAt(this.state.pos);
|
||||
}
|
||||
if (next === 69 || next === 101) { // 'eE'
|
||||
|
||||
if ((next === 69 || next === 101) && !octal) { // 'eE'
|
||||
next = this.input.charCodeAt(++this.state.pos);
|
||||
if (next === 43 || next === 45) ++this.state.pos; // '+-'
|
||||
if (this.readInt(10) === null) this.raise(start, "Invalid number");
|
||||
isFloat = true;
|
||||
}
|
||||
|
||||
if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.state.pos, "Identifier directly after number");
|
||||
|
||||
const str = this.input.slice(start, this.state.pos);
|
||||
const str = this.input.slice(start, this.state.pos).replace(/_/g, "");
|
||||
let val;
|
||||
if (isFloat) {
|
||||
val = parseFloat(str);
|
||||
} else if (!firstIsZero || str.length === 1) {
|
||||
} else if (!octal || str.length === 1) {
|
||||
val = parseInt(str, 10);
|
||||
} else if (this.state.strict) {
|
||||
this.raise(start, "Invalid number");
|
||||
@@ -613,7 +672,7 @@ export default class Tokenizer {
|
||||
|
||||
// Read a string value, interpreting backslash-escapes.
|
||||
|
||||
readCodePoint(throwOnInvalid) {
|
||||
readCodePoint(throwOnInvalid: boolean): number | null {
|
||||
const ch = this.input.charCodeAt(this.state.pos);
|
||||
let code;
|
||||
|
||||
@@ -622,6 +681,7 @@ export default class Tokenizer {
|
||||
code = this.readHexChar(this.input.indexOf("}", this.state.pos) - this.state.pos, throwOnInvalid);
|
||||
++this.state.pos;
|
||||
if (code === null) {
|
||||
// $FlowFixMe (is this always non-null?)
|
||||
--this.state.invalidTemplateEscapePosition; // to point to the '\'' instead of the 'u'
|
||||
} else if (code > 0x10FFFF) {
|
||||
if (throwOnInvalid) {
|
||||
@@ -637,7 +697,7 @@ export default class Tokenizer {
|
||||
return code;
|
||||
}
|
||||
|
||||
readString(quote) {
|
||||
readString(quote: number): void {
|
||||
let out = "", chunkStart = ++this.state.pos;
|
||||
for (;;) {
|
||||
if (this.state.pos >= this.input.length) this.raise(this.state.start, "Unterminated string constant");
|
||||
@@ -645,6 +705,7 @@ export default class Tokenizer {
|
||||
if (ch === quote) break;
|
||||
if (ch === 92) { // '\'
|
||||
out += this.input.slice(chunkStart, this.state.pos);
|
||||
// $FlowFixMe
|
||||
out += this.readEscapedChar(false);
|
||||
chunkStart = this.state.pos;
|
||||
} else {
|
||||
@@ -658,7 +719,7 @@ export default class Tokenizer {
|
||||
|
||||
// Reads template string tokens.
|
||||
|
||||
readTmplToken() {
|
||||
readTmplToken(): void {
|
||||
let out = "", chunkStart = this.state.pos, containsInvalid = false;
|
||||
for (;;) {
|
||||
if (this.state.pos >= this.input.length) this.raise(this.state.start, "Unterminated template");
|
||||
@@ -709,7 +770,7 @@ export default class Tokenizer {
|
||||
|
||||
// Used to read escaped characters
|
||||
|
||||
readEscapedChar(inTemplate) {
|
||||
readEscapedChar(inTemplate: boolean): string | null {
|
||||
const throwOnInvalid = !inTemplate;
|
||||
const ch = this.input.charCodeAt(++this.state.pos);
|
||||
++this.state.pos;
|
||||
@@ -736,6 +797,7 @@ export default class Tokenizer {
|
||||
default:
|
||||
if (ch >= 48 && ch <= 55) {
|
||||
const codePos = this.state.pos - 1;
|
||||
// $FlowFixMe
|
||||
let octalStr = this.input.substr(this.state.pos - 1, 3).match(/^[0-7]+/)[0];
|
||||
let octal = parseInt(octalStr, 8);
|
||||
if (octal > 255) {
|
||||
@@ -764,7 +826,7 @@ export default class Tokenizer {
|
||||
|
||||
// Used to read character escape sequences ('\x', '\u').
|
||||
|
||||
readHexChar(len, throwOnInvalid) {
|
||||
readHexChar(len: number, throwOnInvalid: boolean): number | null {
|
||||
const codePos = this.state.pos;
|
||||
const n = this.readInt(16, len);
|
||||
if (n === null) {
|
||||
@@ -784,7 +846,7 @@ export default class Tokenizer {
|
||||
// Incrementally adds only escaped chars, adding other chunks as-is
|
||||
// as a micro-optimization.
|
||||
|
||||
readWord1() {
|
||||
readWord1(): string {
|
||||
this.state.containsEsc = false;
|
||||
let word = "", first = true, chunkStart = this.state.pos;
|
||||
while (this.state.pos < this.input.length) {
|
||||
@@ -803,10 +865,12 @@ export default class Tokenizer {
|
||||
|
||||
++this.state.pos;
|
||||
const esc = this.readCodePoint(true);
|
||||
// $FlowFixMe (thinks esc may be null, but throwOnInvalid is true)
|
||||
if (!(first ? isIdentifierStart : isIdentifierChar)(esc, true)) {
|
||||
this.raise(escStart, "Invalid Unicode escape");
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
word += codePointToString(esc);
|
||||
chunkStart = this.state.pos;
|
||||
} else {
|
||||
@@ -820,7 +884,7 @@ export default class Tokenizer {
|
||||
// Read an identifier or keyword token. Will check for reserved
|
||||
// words when necessary.
|
||||
|
||||
readWord() {
|
||||
readWord(): void {
|
||||
const word = this.readWord1();
|
||||
let type = tt.name;
|
||||
if (!this.state.containsEsc && this.isKeyword(word)) {
|
||||
@@ -829,7 +893,7 @@ export default class Tokenizer {
|
||||
return this.finishToken(type, word);
|
||||
}
|
||||
|
||||
braceIsBlock(prevType) {
|
||||
braceIsBlock(prevType: TokenType): boolean {
|
||||
if (prevType === tt.colon) {
|
||||
const parent = this.curContext();
|
||||
if (parent === ct.braceStatement || parent === ct.braceExpression) {
|
||||
@@ -852,7 +916,7 @@ export default class Tokenizer {
|
||||
return !this.state.exprAllowed;
|
||||
}
|
||||
|
||||
updateContext(prevType) {
|
||||
updateContext(prevType: TokenType): void {
|
||||
const type = this.state.type;
|
||||
let update;
|
||||
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
// @flow
|
||||
|
||||
import type { Options } from "../options";
|
||||
import * as N from "../types";
|
||||
|
||||
import type { TokContext } from "./context";
|
||||
import type { Token } from "./index";
|
||||
import type { TokenType } from "./types";
|
||||
import { Position } from "../util/location";
|
||||
import { types as ct } from "./context";
|
||||
import { types as tt } from "./types";
|
||||
|
||||
export default class State {
|
||||
init(options: Object, input: string) {
|
||||
init(options: Options, input: string): void {
|
||||
this.strict = options.strictMode === false ? false : options.sourceType === "module";
|
||||
|
||||
this.input = input;
|
||||
@@ -18,6 +24,8 @@ export default class State {
|
||||
this.inAsync =
|
||||
this.inPropertyName =
|
||||
this.inType =
|
||||
this.inClass =
|
||||
this.inClassProperty =
|
||||
this.noAnonFunctionType =
|
||||
false;
|
||||
|
||||
@@ -41,6 +49,7 @@ export default class State {
|
||||
this.start = this.end = this.pos;
|
||||
this.startLoc = this.endLoc = this.curPosition();
|
||||
|
||||
// $FlowIgnore
|
||||
this.lastTokEndLoc = this.lastTokStartLoc = null;
|
||||
this.lastTokStart = this.lastTokEnd = this.pos;
|
||||
|
||||
@@ -53,8 +62,6 @@ export default class State {
|
||||
this.invalidTemplateEscapePosition = null;
|
||||
|
||||
this.exportedIdentifiers = [];
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO
|
||||
@@ -69,27 +76,35 @@ export default class State {
|
||||
// Flags to track whether we are in a function, a generator.
|
||||
inFunction: boolean;
|
||||
inGenerator: boolean;
|
||||
inMethod: boolean;
|
||||
inMethod: boolean | N.MethodKind;
|
||||
inAsync: boolean;
|
||||
inType: boolean;
|
||||
noAnonFunctionType: boolean;
|
||||
inPropertyName: boolean;
|
||||
inClassProperty: boolean;
|
||||
inClass: boolean;
|
||||
|
||||
// Labels in scope.
|
||||
labels: Array<Object>;
|
||||
labels: Array<{ kind: ?("loop" | "switch"), statementStart?: number }>;
|
||||
|
||||
// Leading decorators.
|
||||
decorators: Array<Object>;
|
||||
decorators: Array<N.Decorator>;
|
||||
|
||||
// Token store.
|
||||
tokens: Array<Object>;
|
||||
tokens: Array<Token | N.Comment>;
|
||||
|
||||
// Comment store.
|
||||
comments: Array<Object>;
|
||||
comments: Array<N.Comment>;
|
||||
|
||||
// Comment attachment store
|
||||
trailingComments: Array<Object>;
|
||||
leadingComments: Array<Object>;
|
||||
commentStack: Array<Object>;
|
||||
trailingComments: Array<N.Comment>;
|
||||
leadingComments: Array<N.Comment>;
|
||||
commentStack: Array<{
|
||||
start: number;
|
||||
leadingComments: ?Array<N.Comment>;
|
||||
trailingComments: ?Array<N.Comment>;
|
||||
}>;
|
||||
commentPreviousNode: N.Node;
|
||||
|
||||
// The current position of the tokenizer in the input.
|
||||
pos: number;
|
||||
@@ -113,8 +128,8 @@ export default class State {
|
||||
endLoc: Position;
|
||||
|
||||
// Position information for the previous token
|
||||
lastTokEndLoc: ?Position;
|
||||
lastTokStartLoc: ?Position;
|
||||
lastTokEndLoc: Position;
|
||||
lastTokStartLoc: Position;
|
||||
lastTokStart: number;
|
||||
lastTokEnd: number;
|
||||
|
||||
@@ -137,19 +152,23 @@ export default class State {
|
||||
// `export default foo;` and `export { foo as default };`.
|
||||
exportedIdentifiers: Array<string>;
|
||||
|
||||
curPosition() {
|
||||
invalidTemplateEscapePosition: ?number;
|
||||
|
||||
curPosition(): Position {
|
||||
return new Position(this.curLine, this.pos - this.lineStart);
|
||||
}
|
||||
|
||||
clone(skipArrays?) {
|
||||
clone(skipArrays?: boolean): State {
|
||||
const state = new State;
|
||||
for (const key in this) {
|
||||
// $FlowIgnore
|
||||
let val = this[key];
|
||||
|
||||
if ((!skipArrays || key === "context") && Array.isArray(val)) {
|
||||
val = val.slice();
|
||||
}
|
||||
|
||||
// $FlowIgnore
|
||||
state[key] = val;
|
||||
}
|
||||
return state;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
// ## Token types
|
||||
|
||||
// The assignment of fine-grained, information-carrying type objects
|
||||
@@ -23,8 +25,33 @@ const isAssign = true;
|
||||
const prefix = true;
|
||||
const postfix = true;
|
||||
|
||||
type TokenOptions = {
|
||||
keyword?: string;
|
||||
|
||||
beforeExpr?: boolean;
|
||||
startsExpr?: boolean;
|
||||
rightAssociative?: boolean;
|
||||
isLoop?: boolean;
|
||||
isAssign?: boolean;
|
||||
prefix?: boolean;
|
||||
postfix?: boolean;
|
||||
binop?: ?number;
|
||||
};
|
||||
|
||||
export class TokenType {
|
||||
constructor(label, conf = {}) {
|
||||
label: string;
|
||||
keyword: ?string;
|
||||
beforeExpr: boolean;
|
||||
startsExpr: boolean;
|
||||
rightAssociative: boolean;
|
||||
isLoop: boolean;
|
||||
isAssign: boolean;
|
||||
prefix: boolean;
|
||||
postfix: boolean;
|
||||
binop: ?number;
|
||||
updateContext: ?((prevType: TokenType) => void);
|
||||
|
||||
constructor(label: string, conf: TokenOptions = {}) {
|
||||
this.label = label;
|
||||
this.keyword = conf.keyword;
|
||||
this.beforeExpr = !!conf.beforeExpr;
|
||||
@@ -40,7 +67,7 @@ export class TokenType {
|
||||
}
|
||||
|
||||
class KeywordTokenType extends TokenType {
|
||||
constructor(name, options = {}) {
|
||||
constructor(name: string, options: TokenOptions = {}) {
|
||||
options.keyword = name;
|
||||
|
||||
super(name, options);
|
||||
@@ -48,12 +75,12 @@ class KeywordTokenType extends TokenType {
|
||||
}
|
||||
|
||||
export class BinopTokenType extends TokenType {
|
||||
constructor(name, prec) {
|
||||
constructor(name: string, prec: number) {
|
||||
super(name, { beforeExpr, binop: prec });
|
||||
}
|
||||
}
|
||||
|
||||
export const types = {
|
||||
export const types: { [name: string]: TokenType } = {
|
||||
num: new TokenType("num", { startsExpr }),
|
||||
regexp: new TokenType("regexp", { startsExpr }),
|
||||
string: new TokenType("string", { startsExpr }),
|
||||
@@ -82,6 +109,7 @@ export const types = {
|
||||
backQuote: new TokenType("`", { startsExpr }),
|
||||
dollarBraceL: new TokenType("${", { beforeExpr, startsExpr }),
|
||||
at: new TokenType("@"),
|
||||
hash: new TokenType("#"),
|
||||
|
||||
// Operators. These carry several kinds of properties to help the
|
||||
// parser use them properly (the presence of these properties is
|
||||
|
||||
726
src/types.js
Normal file
726
src/types.js
Normal file
@@ -0,0 +1,726 @@
|
||||
// @flow
|
||||
|
||||
import type { Token } from "./tokenizer";
|
||||
import type { SourceLocation } from "./util/location";
|
||||
|
||||
/*
|
||||
* If making any changes to the AST, update:
|
||||
* - This repository:
|
||||
* - This file
|
||||
* - `ast` directory
|
||||
* - Babel repository:
|
||||
* - packages/babel-types/src/definitions
|
||||
* - packages/babel-generators/src/generators
|
||||
*/
|
||||
|
||||
export type Comment = {
|
||||
type: "CommentBlock" | "CommentLine";
|
||||
value: string;
|
||||
start: number;
|
||||
end: number;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
export interface NodeBase {
|
||||
start: number;
|
||||
end: number;
|
||||
loc: SourceLocation;
|
||||
range: [number, number];
|
||||
leadingComments?: ?Array<Comment>;
|
||||
trailingComments?: ?Array<Comment>;
|
||||
innerComments?: ?Array<Comment>;
|
||||
|
||||
extra: { [key: string]: any };
|
||||
}
|
||||
|
||||
// Using a union type for `Node` makes type-checking too slow.
|
||||
// Instead, add an index signature to allow a Node to be treated as anything.
|
||||
export type Node = NodeBase & { [key: string]: any };
|
||||
export type Expression = Node;
|
||||
export type Statement = Node;
|
||||
export type Pattern =
|
||||
| Identifier
|
||||
| ObjectPattern
|
||||
| ArrayPattern
|
||||
| RestElement
|
||||
| AssignmentPattern;
|
||||
export type Declaration =
|
||||
| VariableDeclaration
|
||||
| ClassDeclaration
|
||||
| FunctionDeclaration;
|
||||
export type DeclarationBase = NodeBase;
|
||||
|
||||
// TODO: Not in spec
|
||||
export type HasDecorators = NodeBase & {
|
||||
decorators?: $ReadOnlyArray<Decorator>;
|
||||
};
|
||||
|
||||
export type Identifier = PatternBase & {
|
||||
type: "Identifier";
|
||||
name: string;
|
||||
|
||||
__clone(): Identifier;
|
||||
};
|
||||
|
||||
export type PrivateName = NodeBase & {
|
||||
type: "PrivateName";
|
||||
name: string;
|
||||
};
|
||||
|
||||
// Literals
|
||||
|
||||
export type Literal = RegExpLiteral | NullLiteral | StringLiteral | BooleanLiteral | NumericLiteral;
|
||||
|
||||
export type RegExpLiteral = NodeBase & {
|
||||
type: "RegExpLiteral";
|
||||
pattern: string;
|
||||
flags: RegExp$flags;
|
||||
};
|
||||
|
||||
export type NullLiteral = NodeBase & {
|
||||
type: "NullLiteral";
|
||||
}
|
||||
|
||||
export type StringLiteral = NodeBase & {
|
||||
type: "StringLiteral";
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type BooleanLiteral = NodeBase & {
|
||||
type: "BooleanLiteral";
|
||||
value: boolean;
|
||||
};
|
||||
|
||||
export type NumericLiteral = NodeBase & {
|
||||
type: "NumericLiteral";
|
||||
value: number;
|
||||
};
|
||||
|
||||
// Programs
|
||||
|
||||
export type BlockStatementLike = Program | BlockStatement;
|
||||
|
||||
export type File = NodeBase & {
|
||||
type: "File";
|
||||
program: Program;
|
||||
comments: $ReadOnlyArray<Comment>;
|
||||
tokens: $ReadOnlyArray<Token | Comment>;
|
||||
};
|
||||
|
||||
export type Program = NodeBase & {
|
||||
type: "Program";
|
||||
sourceType: "script" | "module";
|
||||
body: Array<Statement | ModuleDeclaration>; // TODO: $ReadOnlyArray
|
||||
directives: $ReadOnlyArray<Directive>; // TODO: Not in spec
|
||||
};
|
||||
|
||||
// Functions
|
||||
|
||||
export type Function =
|
||||
NormalFunction | ArrowFunctionExpression | ObjectMethod | ClassMethod;
|
||||
|
||||
export type NormalFunction =
|
||||
FunctionDeclaration | FunctionExpression;
|
||||
|
||||
export type FunctionBase = HasDecorators & {
|
||||
id: ?Identifier;
|
||||
params: $ReadOnlyArray<Pattern>;
|
||||
body: BlockStatement;
|
||||
generator: boolean;
|
||||
async: boolean;
|
||||
|
||||
expression: boolean; // TODO: Not in spec
|
||||
typeParameters?: ?FlowTypeParameterDeclaration; // TODO: Not in spec
|
||||
returnType?: ?FlowTypeAnnotation; // TODO: Not in spec
|
||||
};
|
||||
|
||||
// Statements
|
||||
|
||||
export type ExpressionStatement = NodeBase & {
|
||||
type: "ExpressionStatement";
|
||||
expression: Expression;
|
||||
};
|
||||
|
||||
export type BlockStatement = NodeBase & {
|
||||
type: "BlockStatement";
|
||||
body: Array<Statement>; // TODO: $ReadOnlyArray
|
||||
directives: $ReadOnlyArray<Directive>;
|
||||
};
|
||||
|
||||
export type EmptyStatement = NodeBase & {
|
||||
type: "EmptyStatement"
|
||||
};
|
||||
|
||||
export type DebuggerStatement = NodeBase & {
|
||||
type: "DebuggerStatement"
|
||||
};
|
||||
|
||||
export type WithStatement = NodeBase & {
|
||||
type: "WithStatement";
|
||||
object: Expression;
|
||||
body: Statement;
|
||||
};
|
||||
|
||||
export type ReturnStatement = NodeBase & {
|
||||
type: "ReturnStatement";
|
||||
argument: ?Expression;
|
||||
};
|
||||
|
||||
export type LabeledStatement = NodeBase & {
|
||||
type: "LabeledStatement";
|
||||
label: Identifier;
|
||||
body: Statement;
|
||||
};
|
||||
|
||||
export type BreakStatement = NodeBase & {
|
||||
type: "BreakStatement";
|
||||
label: ?Identifier;
|
||||
};
|
||||
|
||||
export type ContinueStatement = NodeBase & {
|
||||
type: "ContinueStatement";
|
||||
label: ?Identifier;
|
||||
};
|
||||
|
||||
// Choice
|
||||
|
||||
export type IfStatement = NodeBase & {
|
||||
type: "IfStatement";
|
||||
test: Expression;
|
||||
consequent: Statement;
|
||||
alternate: ?Statement;
|
||||
};
|
||||
|
||||
export type SwitchStatement = NodeBase & {
|
||||
type: "SwitchStatement";
|
||||
discriminant: Expression;
|
||||
cases: $ReadOnlyArray<SwitchCase>;
|
||||
};
|
||||
|
||||
export type SwitchCase = NodeBase & {
|
||||
type: "SwitchCase";
|
||||
test: ?Expression;
|
||||
consequent: $ReadOnlyArray<Statement>;
|
||||
};
|
||||
|
||||
// Exceptions
|
||||
|
||||
export type ThrowStatement = NodeBase & {
|
||||
type: "ThrowStatement";
|
||||
argument: Expression;
|
||||
};
|
||||
|
||||
export type TryStatement = NodeBase & {
|
||||
type: "TryStatement";
|
||||
block: BlockStatement;
|
||||
handler: CatchClause | null;
|
||||
finalizer: BlockStatement | null;
|
||||
|
||||
guardedHandlers: $ReadOnlyArray<empty>; // TODO: Not in spec
|
||||
};
|
||||
|
||||
export type CatchClause = NodeBase & {
|
||||
type: "CatchClause";
|
||||
param: Pattern;
|
||||
body: BlockStatement;
|
||||
};
|
||||
|
||||
// Loops
|
||||
|
||||
export type WhileStatement = NodeBase & {
|
||||
type: "WhileStatement";
|
||||
test: Expression;
|
||||
body: Statement;
|
||||
};
|
||||
|
||||
export type DoWhileStatement = NodeBase & {
|
||||
type: "DoWhileStatement";
|
||||
body: Statement;
|
||||
test: Expression;
|
||||
};
|
||||
|
||||
export type ForLike = ForStatement | ForInOf;
|
||||
|
||||
export type ForStatement = NodeBase & {
|
||||
type: "ForStatement";
|
||||
init: ?(VariableDeclaration | Expression);
|
||||
test: ?Expression;
|
||||
update: ?Expression;
|
||||
body: Statement;
|
||||
};
|
||||
|
||||
export type ForInOf = ForInStatement | ForOfStatement;
|
||||
|
||||
export type ForInOfBase = NodeBase & {
|
||||
type: "ForInStatement";
|
||||
left: VariableDeclaration | Expression;
|
||||
right: Expression;
|
||||
body: Statement;
|
||||
};
|
||||
|
||||
export type ForInStatement = ForInOfBase & {
|
||||
type: "ForInStatement";
|
||||
// TODO: Shouldn't be here, but have to declare it because it's assigned to a ForInOf unconditionally.
|
||||
await: boolean;
|
||||
};
|
||||
|
||||
export type ForOfStatement = ForInOfBase & {
|
||||
type: "ForOfStatement";
|
||||
await: boolean;
|
||||
};
|
||||
|
||||
// Declarations
|
||||
|
||||
export type OptFunctionDeclaration = FunctionBase & DeclarationBase & HasDecorators & {
|
||||
type: "FunctionDeclaration";
|
||||
};
|
||||
|
||||
export type FunctionDeclaration = OptFunctionDeclaration & {
|
||||
id: Identifier;
|
||||
}
|
||||
|
||||
export type VariableDeclaration = DeclarationBase & HasDecorators & {
|
||||
type: "VariableDeclaration";
|
||||
declarations: $ReadOnlyArray<VariableDeclarator>;
|
||||
kind: "var" | "let" | "const";
|
||||
};
|
||||
|
||||
export type VariableDeclarator = NodeBase & {
|
||||
type: "VariableDeclarator";
|
||||
id: Pattern;
|
||||
init: ?Expression;
|
||||
};
|
||||
|
||||
// Misc
|
||||
|
||||
export type Decorator = NodeBase & {
|
||||
type: "Decorator";
|
||||
expression: Expression;
|
||||
};
|
||||
|
||||
export type Directive = NodeBase & {
|
||||
type: "Directive";
|
||||
value: DirectiveLiteral;
|
||||
};
|
||||
|
||||
export type DirectiveLiteral = StringLiteral & { type: "DirectiveLiteral" };
|
||||
|
||||
// Expressions
|
||||
|
||||
export type Super = NodeBase & { type: "Super" };
|
||||
|
||||
export type Import = NodeBase & { type: "Import" };
|
||||
|
||||
export type ThisExpression = NodeBase & { type: "ThisExpression" };
|
||||
|
||||
export type ArrowFunctionExpression = FunctionBase & {
|
||||
type: "ArrowFunctionExpression";
|
||||
body: BlockStatement | Expression;
|
||||
};
|
||||
|
||||
export type YieldExpression = NodeBase & {
|
||||
type: "YieldExpression";
|
||||
argument: ?Expression;
|
||||
delegate: boolean;
|
||||
};
|
||||
|
||||
export type AwaitExpression = NodeBase & {
|
||||
type: "AwaitExpression";
|
||||
argument: ?Expression;
|
||||
};
|
||||
|
||||
export type ArrayExpression = NodeBase & {
|
||||
type: "ArrayExpression";
|
||||
elements: $ReadOnlyArray<?(Expression | SpreadElement)>;
|
||||
};
|
||||
|
||||
export type ObjectExpression = NodeBase & {
|
||||
type: "ObjectExpression";
|
||||
properties: $ReadOnlyArray<ObjectProperty | ObjectMethod | SpreadElement>;
|
||||
};
|
||||
|
||||
export type ObjectOrClassMember = ClassMethod | ClassProperty | ClassPrivateProperty | ObjectMember;
|
||||
|
||||
export type ObjectMember = ObjectProperty | ObjectMethod;
|
||||
|
||||
export type ObjectMemberBase = NodeBase & {
|
||||
key: Expression;
|
||||
computed: boolean;
|
||||
value: Expression;
|
||||
decorators: $ReadOnlyArray<Decorator>;
|
||||
kind?: "get" | "set" | "method";
|
||||
method: boolean; // TODO: Not in spec
|
||||
|
||||
variance?: ?FlowVariance; // TODO: Not in spec
|
||||
};
|
||||
|
||||
export type ObjectProperty = ObjectMemberBase & {
|
||||
type: "ObjectProperty";
|
||||
shorthand: boolean;
|
||||
};
|
||||
|
||||
export type ObjectMethod = ObjectMemberBase & MethodBase & {
|
||||
type: "ObjectMethod";
|
||||
kind: "get" | "set" | "method"; // Never "constructor"
|
||||
};
|
||||
|
||||
export type FunctionExpression = MethodBase & {
|
||||
kind?: void; // never set
|
||||
type: "FunctionExpression";
|
||||
};
|
||||
|
||||
// Unary operations
|
||||
|
||||
export type UnaryExpression = NodeBase & {
|
||||
type: "UnaryExpression";
|
||||
operator: UnaryOperator;
|
||||
prefix: boolean;
|
||||
argument: Expression;
|
||||
};
|
||||
|
||||
export type UnaryOperator = "-" | "+" | "!" | "~" | "typeof" | "void" | "delete";
|
||||
|
||||
export type UpdateExpression = NodeBase & {
|
||||
type: "UpdateExpression";
|
||||
operator: UpdateOperator;
|
||||
argument: Expression;
|
||||
prefix: boolean;
|
||||
};
|
||||
|
||||
export type UpdateOperator = "++" | "--";
|
||||
|
||||
// Binary operations
|
||||
|
||||
export type BinaryExpression = NodeBase & {
|
||||
type: "BinaryExpression";
|
||||
operator: BinaryOperator;
|
||||
left: Expression;
|
||||
right: Expression;
|
||||
};
|
||||
|
||||
export type BinaryOperator =
|
||||
| "==" | "!=" | "===" | "!=="
|
||||
| "<" | "<=" | ">" | ">="
|
||||
| "<<" | ">>" | ">>>"
|
||||
| "+" | "-" | "*" | "/" | "%"
|
||||
| "|" | "^" | "&" | "in"
|
||||
| "instanceof";
|
||||
|
||||
export type AssignmentExpression = NodeBase & {
|
||||
type: "AssignmentExpression";
|
||||
operator: AssignmentOperator;
|
||||
left: Pattern | Expression;
|
||||
right: Expression;
|
||||
};
|
||||
|
||||
export type AssignmentOperator =
|
||||
| "=" | "+=" | "-=" | "*=" | "/=" | "%="
|
||||
| "<<=" | ">>=" | ">>>="
|
||||
| "|=" | "^=" | "&=";
|
||||
|
||||
export type LogicalExpression = NodeBase & {
|
||||
type: "LogicalExpression";
|
||||
operator: LogicalOperator;
|
||||
left: Expression;
|
||||
right: Expression;
|
||||
};
|
||||
|
||||
export type LogicalOperator = "||" | "&&";
|
||||
|
||||
export type SpreadElement = NodeBase & {
|
||||
type: "SpreadElement";
|
||||
argument: Expression;
|
||||
};
|
||||
|
||||
export type MemberExpression = NodeBase & {
|
||||
type: "MemberExpression";
|
||||
object: Expression | Super;
|
||||
property: Expression;
|
||||
computed: boolean;
|
||||
}
|
||||
|
||||
export type BindExpression = NodeBase & {
|
||||
type: "BindExpression";
|
||||
object: $ReadOnlyArray<?Expression>;
|
||||
callee: $ReadOnlyArray<Expression>;
|
||||
};
|
||||
|
||||
export type ConditionalExpression = NodeBase & {
|
||||
type: "ConditionalExpression";
|
||||
test: Expression;
|
||||
alternate: Expression;
|
||||
consequent: Expression;
|
||||
};
|
||||
|
||||
export type CallOrNewBase = NodeBase & {
|
||||
callee: Expression | Super | Import;
|
||||
arguments: Array<Expression | SpreadElement>; // TODO: $ReadOnlyArray
|
||||
};
|
||||
|
||||
export type CallExpression = CallOrNewBase & {
|
||||
type: "CallExpression";
|
||||
};
|
||||
|
||||
export type NewExpression = CallOrNewBase & {
|
||||
type: "NewExpression";
|
||||
};
|
||||
|
||||
export type SequenceExpression = NodeBase & {
|
||||
type: "SequenceExpression";
|
||||
expressions: $ReadOnlyArray<Expression>;
|
||||
};
|
||||
|
||||
// Template Literals
|
||||
|
||||
export type TemplateLiteral = NodeBase & {
|
||||
type: "TemplateLiteral";
|
||||
quasis: $ReadOnlyArray<TemplateElement>;
|
||||
expressions: $ReadOnlyArray<Expression>;
|
||||
};
|
||||
|
||||
export type TaggedTmplateExpression = NodeBase & {
|
||||
type: "TaggedTemplateExpression";
|
||||
tag: Expression;
|
||||
quasi: TemplateLiteral;
|
||||
};
|
||||
|
||||
export type TemplateElement = NodeBase & {
|
||||
type: "TemplateElement";
|
||||
tail: boolean;
|
||||
value: {
|
||||
cooked: string;
|
||||
raw: string;
|
||||
}
|
||||
};
|
||||
|
||||
// Patterns
|
||||
|
||||
export type PatternBase = HasDecorators & {
|
||||
// Flow only:
|
||||
optional?: true;
|
||||
typeAnnotation?: ?FlowTypeAnnotation;
|
||||
};
|
||||
|
||||
export type AssignmentProperty = ObjectProperty & {
|
||||
value: Pattern;
|
||||
};
|
||||
|
||||
export type ObjectPattern = PatternBase & {
|
||||
type: "ObjectPattern";
|
||||
properties: $ReadOnlyArray<AssignmentProperty | RestElement>;
|
||||
};
|
||||
|
||||
export type ArrayPattern = PatternBase & {
|
||||
type: "ArrayPattern";
|
||||
elements: $ReadOnlyArray<?Pattern>;
|
||||
};
|
||||
|
||||
export type RestElement = PatternBase & {
|
||||
type: "RestElement";
|
||||
argument: Pattern;
|
||||
};
|
||||
|
||||
export type AssignmentPattern = PatternBase & {
|
||||
type: "AssignmentPattern";
|
||||
left: Pattern;
|
||||
right: Expression;
|
||||
};
|
||||
|
||||
// Classes
|
||||
|
||||
export type Class = ClassDeclaration | ClassExpression;
|
||||
|
||||
export type ClassBase = HasDecorators & {
|
||||
id: ?Identifier;
|
||||
superClass: ?Expression;
|
||||
body: ClassBody;
|
||||
decorators: $ReadOnlyArray<Decorator>;
|
||||
|
||||
typeParameters?: ?FlowTypeParameterDeclaration; // TODO: Not in spec
|
||||
superTypeParameters?: ?FlowTypeParameterInstantiation; // TODO: Not in spec
|
||||
implements?: $ReadOnlyArray<FlowClassImplements>;
|
||||
};
|
||||
|
||||
export type ClassBody = NodeBase & {
|
||||
type: "ClassBody";
|
||||
body: Array<ClassMember>; // TODO: $ReadOnlyArray
|
||||
};
|
||||
|
||||
export type ClassMemberBase = NodeBase & HasDecorators & {
|
||||
static: boolean;
|
||||
computed: boolean;
|
||||
// TypeScript only:
|
||||
access?: ?Accessibility;
|
||||
abstract?: ?true;
|
||||
optional?: ?true;
|
||||
}
|
||||
|
||||
export type Accessibility = "public" | "protected" | "private";
|
||||
|
||||
export type ClassMember = ClassMethod | ClassProperty | ClassPrivateProperty;
|
||||
|
||||
export type MethodLike = ObjectMethod | FunctionExpression | ClassMethod;
|
||||
|
||||
export type MethodBase = FunctionBase & {
|
||||
+kind?: MethodKind;
|
||||
};
|
||||
|
||||
export type MethodKind = "constructor" | "method" | "get" | "set";
|
||||
|
||||
export type ClassMethod = MethodBase & ClassMemberBase & {
|
||||
type: "ClassMethod";
|
||||
key: Expression;
|
||||
kind: MethodKind;
|
||||
static: boolean;
|
||||
decorators: $ReadOnlyArray<Decorator>;
|
||||
|
||||
variance?: ?FlowVariance; // TODO: Not in spec
|
||||
};
|
||||
|
||||
export type ClassProperty = ClassMemberBase & {
|
||||
type: "ClassProperty";
|
||||
key: Identifier;
|
||||
value: ?Expression; // TODO: Not in spec that this is nullable.
|
||||
|
||||
typeAnnotation?: ?FlowTypeAnnotation; // TODO: Not in spec
|
||||
variance?: ?FlowVariance; // TODO: Not in spec
|
||||
|
||||
// TypeScript only: (TODO: Not in spec)
|
||||
readonly?: true;
|
||||
};
|
||||
|
||||
export type ClassPrivateProperty = NodeBase & {
|
||||
type: "ClassPrivateProperty";
|
||||
key: Identifier;
|
||||
value: ?Expression; // TODO: Not in spec that this is nullable.
|
||||
};
|
||||
|
||||
export type OptClassDeclaration = ClassBase & DeclarationBase & HasDecorators & {
|
||||
type: "ClassDeclaration";
|
||||
// TypeScript only
|
||||
abstract?: ?true;
|
||||
};
|
||||
|
||||
export type ClassDeclaration = OptClassDeclaration & {
|
||||
id: Identifier;
|
||||
};
|
||||
|
||||
export type ClassExpression = ClassBase & { type: "ClassExpression" };
|
||||
|
||||
export type MetaProperty = NodeBase & {
|
||||
type: "MetaProperty";
|
||||
meta: Identifier;
|
||||
property: Identifier;
|
||||
};
|
||||
|
||||
// Modules
|
||||
|
||||
export type ModuleDeclaration = AnyImport | AnyExport;
|
||||
|
||||
export type AnyImport = ImportDeclaration;
|
||||
|
||||
export type AnyExport =
|
||||
| ExportNamedDeclaration
|
||||
| ExportDefaultDeclaration
|
||||
| ExportAllDeclaration;
|
||||
|
||||
export type ModuleSpecifier = NodeBase & {
|
||||
local: Identifier;
|
||||
};
|
||||
|
||||
// Imports
|
||||
|
||||
export type ImportDeclaration = NodeBase & {
|
||||
type: "ImportDeclaration";
|
||||
// TODO: $ReadOnlyArray
|
||||
specifiers: Array<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>;
|
||||
source: Literal;
|
||||
|
||||
importKind?: "type" | "typeof" | "value"; // TODO: Not in spec
|
||||
};
|
||||
|
||||
export type ImportSpecifier = ModuleSpecifier & {
|
||||
type: "ImportSpecifier";
|
||||
imported: Identifier;
|
||||
};
|
||||
|
||||
export type ImportDefaultSpecifier = ModuleSpecifier & {
|
||||
type: "ImportDefaultSpecifier"
|
||||
};
|
||||
|
||||
export type ImportNamespaceSpecifier = ModuleSpecifier & {
|
||||
type: "ImportNamespaceSpecifier"
|
||||
};
|
||||
|
||||
// Exports
|
||||
|
||||
export type ExportNamedDeclaration = NodeBase & {
|
||||
type: "ExportNamedDeclaration";
|
||||
declaration: ?Declaration;
|
||||
specifiers: $ReadOnlyArray<ExportSpecifier>;
|
||||
source: ?Literal;
|
||||
|
||||
exportKind?: "type" | "value"; // TODO: Not in spec
|
||||
};
|
||||
|
||||
export type ExportSpecifier = NodeBase & {
|
||||
type: "ExportSpecifier";
|
||||
exported: Identifier;
|
||||
};
|
||||
|
||||
export type ExportDefaultDeclaration = NodeBase & {
|
||||
type: "ExportDefaultDeclaration";
|
||||
declaration: OptFunctionDeclaration | OptClassDeclaration | Expression;
|
||||
};
|
||||
|
||||
export type ExportAllDeclaration = NodeBase & {
|
||||
type: "ExportAllDeclaration";
|
||||
source: Literal;
|
||||
};
|
||||
|
||||
// JSX (TODO: Not in spec)
|
||||
|
||||
export type JSXIdentifier = Node;
|
||||
export type JSXNamespacedName = Node;
|
||||
export type JSXMemberExpression = Node;
|
||||
export type JSXEmptyExpression = Node;
|
||||
export type JSXSpreadChild = Node;
|
||||
export type JSXExpressionContainer = Node;
|
||||
export type JSXAttribute = Node;
|
||||
export type JSXOpeningElement = Node;
|
||||
export type JSXClosingElement = Node;
|
||||
export type JSXElement = Node;
|
||||
|
||||
// Flow (TODO: Not in spec)
|
||||
|
||||
export type FlowType = Node;
|
||||
export type FlowPredicate = Node;
|
||||
export type FlowDeclare = Node;
|
||||
export type FlowDeclareClass = Node;
|
||||
export type FlowDeclareExportDeclaration = Node;
|
||||
export type FlowDeclareFunction = Node;
|
||||
export type FlowDeclareVariable = Node;
|
||||
export type FlowDeclareModule = Node;
|
||||
export type FlowDeclareModuleExports = Node;
|
||||
export type FlowDeclareTypeAlias = Node;
|
||||
export type FlowDeclareInterface = Node;
|
||||
export type FlowInterface = Node;
|
||||
export type FlowInterfaceExtends = Node;
|
||||
export type FlowTypeAlias = Node;
|
||||
export type FlowTypeParameter = Node;
|
||||
export type FlowTypeParameterDeclaration = Node;
|
||||
export type FlowTypeParameterInstantiation = Node;
|
||||
export type FlowObjectTypeIndexer = Node;
|
||||
export type FlowFunctionTypeAnnotation = Node;
|
||||
export type FlowObjectTypeProperty = Node;
|
||||
export type FlowObjectTypeSpreadProperty = Node;
|
||||
export type FlowObjectTypeCallProperty = Node;
|
||||
export type FlowObjectTypeAnnotation = Node;
|
||||
export type FlowQualifiedTypeIdentifier = Node;
|
||||
export type FlowGenericTypeAnnotation = Node;
|
||||
export type FlowTypeofTypeAnnotation = Node;
|
||||
export type FlowTupleTypeAnnotation = Node;
|
||||
export type FlowFunctionTypeParam = Node;
|
||||
export type FlowTypeAnnotation = Node;
|
||||
export type FlowVariance = Node;
|
||||
export type FlowClassImplements = Node;
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint max-len: 0 */
|
||||
|
||||
// @flow
|
||||
|
||||
// This is a trick taken from Esprima. It turns out that, on
|
||||
// non-Chrome browsers, to check whether a string is in a set, a
|
||||
// predicate containing a big ugly `switch` statement is faster than
|
||||
@@ -9,17 +11,17 @@
|
||||
//
|
||||
// It starts by sorting the words by length.
|
||||
|
||||
function makePredicate(words) {
|
||||
words = words.split(" ");
|
||||
function makePredicate(words: string): (str: string) => boolean {
|
||||
const wordsArr = words.split(" ");
|
||||
return function (str) {
|
||||
return words.indexOf(str) >= 0;
|
||||
return wordsArr.indexOf(str) >= 0;
|
||||
};
|
||||
}
|
||||
|
||||
// Reserved word lists for various dialects of the language
|
||||
|
||||
export const reservedWords = {
|
||||
6: makePredicate("enum await"),
|
||||
"6": makePredicate("enum await"),
|
||||
strict: makePredicate("implements interface let package private protected public static yield"),
|
||||
strictBind: makePredicate("eval arguments")
|
||||
};
|
||||
@@ -57,7 +59,7 @@ const astralIdentifierCodes = [509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,1
|
||||
// This has a complexity linear to the value of the code. The
|
||||
// assumption is that looking up astral identifier characters is
|
||||
// rare.
|
||||
function isInAstralSet(code, set) {
|
||||
function isInAstralSet(code: number, set: $ReadOnlyArray<number>): boolean {
|
||||
let pos = 0x10000;
|
||||
for (let i = 0; i < set.length; i += 2) {
|
||||
pos += set[i];
|
||||
@@ -66,11 +68,12 @@ function isInAstralSet(code, set) {
|
||||
pos += set[i + 1];
|
||||
if (pos >= code) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test whether a given character code starts an identifier.
|
||||
|
||||
export function isIdentifierStart(code) {
|
||||
export function isIdentifierStart(code: number): boolean {
|
||||
if (code < 65) return code === 36;
|
||||
if (code < 91) return true;
|
||||
if (code < 97) return code === 95;
|
||||
@@ -81,7 +84,7 @@ export function isIdentifierStart(code) {
|
||||
|
||||
// Test whether a given character is part of an identifier.
|
||||
|
||||
export function isIdentifierChar(code) {
|
||||
export function isIdentifierChar(code: number): boolean {
|
||||
if (code < 48) return code === 36;
|
||||
if (code < 58) return true;
|
||||
if (code < 65) return false;
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
// @flow
|
||||
|
||||
import { lineBreakG } from "./whitespace";
|
||||
|
||||
export type Pos = {
|
||||
start: number;
|
||||
}
|
||||
|
||||
// These are used when `options.locations` is on, for the
|
||||
// `startLoc` and `endLoc` properties.
|
||||
|
||||
export class Position {
|
||||
line: number;
|
||||
column: number;
|
||||
|
||||
constructor(line: number, col: number) {
|
||||
this.line = line;
|
||||
this.column = col;
|
||||
@@ -11,8 +20,13 @@ export class Position {
|
||||
}
|
||||
|
||||
export class SourceLocation {
|
||||
start: Position;
|
||||
end: Position;
|
||||
filename: string;
|
||||
|
||||
constructor(start: Position, end?: Position) {
|
||||
this.start = start;
|
||||
// $FlowIgnore (may start as null, but initialized later)
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +37,7 @@ export class SourceLocation {
|
||||
// offset. `input` should be the code string that the offset refers
|
||||
// into.
|
||||
|
||||
export function getLineInfo(input, offset) {
|
||||
export function getLineInfo(input: string, offset: number): Position {
|
||||
for (let line = 1, cur = 0; ;) {
|
||||
lineBreakG.lastIndex = cur;
|
||||
const match = lineBreakG.exec(input);
|
||||
@@ -34,4 +48,6 @@ export function getLineInfo(input, offset) {
|
||||
return new Position(line, offset - cur);
|
||||
}
|
||||
}
|
||||
// istanbul ignore next
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
// Matches a whole line break (where CRLF is considered a single
|
||||
// line break). Used to count lines.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user