801 lines
25 KiB
JavaScript
801 lines
25 KiB
JavaScript
|
/**
|
||
|
* @fileoverview Main Espree file that converts Acorn into Esprima output.
|
||
|
*
|
||
|
* This file contains code from the following MIT-licensed projects:
|
||
|
* 1. Acorn
|
||
|
* 2. Babylon
|
||
|
* 3. Babel-ESLint
|
||
|
*
|
||
|
* This file also contains code from Esprima, which is BSD licensed.
|
||
|
*
|
||
|
* Acorn is Copyright 2012-2015 Acorn Contributors (https://github.com/marijnh/acorn/blob/master/AUTHORS)
|
||
|
* Babylon is Copyright 2014-2015 various contributors (https://github.com/babel/babel/blob/master/packages/babylon/AUTHORS)
|
||
|
* Babel-ESLint is Copyright 2014-2015 Sebastian McKenzie <sebmck@gmail.com>
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
*
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
* ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
* Esprima is Copyright (c) jQuery Foundation, Inc. and Contributors, All Rights Reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
*
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
* ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*/
|
||
|
/* eslint no-undefined:0, no-use-before-define: 0 */
|
||
|
|
||
|
"use strict";
|
||
|
|
||
|
var astNodeTypes = require("./lib/ast-node-types"),
|
||
|
commentAttachment = require("./lib/comment-attachment"),
|
||
|
TokenTranslator = require("./lib/token-translator"),
|
||
|
acornJSX = require("acorn-jsx/inject"),
|
||
|
rawAcorn = require("acorn");
|
||
|
|
||
|
|
||
|
var acorn = acornJSX(rawAcorn);
|
||
|
var DEFAULT_ECMA_VERSION = 5;
|
||
|
var lookahead,
|
||
|
extra,
|
||
|
lastToken;
|
||
|
|
||
|
/**
|
||
|
* Object.assign polyfill for Node < 4
|
||
|
* @param {Object} target The target object
|
||
|
* @param {...Object} sources Sources for the object
|
||
|
* @returns {Object} `target` after being mutated
|
||
|
*/
|
||
|
var assign = Object.assign || function assign(target) {
|
||
|
for (var argIndex = 1; argIndex < arguments.length; argIndex++) {
|
||
|
if (arguments[argIndex] !== null && typeof arguments[argIndex] === "object") {
|
||
|
var keys = Object.keys(arguments[argIndex]);
|
||
|
|
||
|
for (var keyIndex = 0; keyIndex < keys.length; keyIndex++) {
|
||
|
target[keys[keyIndex]] = arguments[argIndex][keys[keyIndex]];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return target;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Resets the extra object to its default.
|
||
|
* @returns {void}
|
||
|
* @private
|
||
|
*/
|
||
|
function resetExtra() {
|
||
|
extra = {
|
||
|
tokens: null,
|
||
|
range: false,
|
||
|
loc: false,
|
||
|
comment: false,
|
||
|
comments: [],
|
||
|
tolerant: false,
|
||
|
errors: [],
|
||
|
strict: false,
|
||
|
ecmaFeatures: {},
|
||
|
ecmaVersion: DEFAULT_ECMA_VERSION,
|
||
|
isModule: false
|
||
|
};
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
var tt = acorn.tokTypes,
|
||
|
getLineInfo = acorn.getLineInfo;
|
||
|
|
||
|
// custom type for JSX attribute values
|
||
|
tt.jsxAttrValueToken = {};
|
||
|
|
||
|
/**
|
||
|
* Normalize ECMAScript version from the initial config
|
||
|
* @param {number} ecmaVersion ECMAScript version from the initial config
|
||
|
* @returns {number} normalized ECMAScript version
|
||
|
*/
|
||
|
function normalizeEcmaVersion(ecmaVersion) {
|
||
|
if (typeof ecmaVersion === "number") {
|
||
|
var version = ecmaVersion;
|
||
|
|
||
|
// Calculate ECMAScript edition number from official year version starting with
|
||
|
// ES2015, which corresponds with ES6 (or a difference of 2009).
|
||
|
if (version >= 2015) {
|
||
|
version -= 2009;
|
||
|
}
|
||
|
|
||
|
switch (version) {
|
||
|
case 3:
|
||
|
case 5:
|
||
|
case 6:
|
||
|
case 7:
|
||
|
case 8:
|
||
|
case 9:
|
||
|
return version;
|
||
|
|
||
|
default:
|
||
|
throw new Error("Invalid ecmaVersion.");
|
||
|
}
|
||
|
} else {
|
||
|
return DEFAULT_ECMA_VERSION;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines if a node is valid given the set of ecmaFeatures.
|
||
|
* @param {ASTNode} node The node to check.
|
||
|
* @returns {boolean} True if the node is allowed, false if not.
|
||
|
* @private
|
||
|
*/
|
||
|
function isValidNode(node) {
|
||
|
var ecma = extra.ecmaFeatures;
|
||
|
|
||
|
switch (node.type) {
|
||
|
case "ExperimentalSpreadProperty":
|
||
|
case "ExperimentalRestProperty":
|
||
|
return ecma.experimentalObjectRestSpread;
|
||
|
|
||
|
case "ImportDeclaration":
|
||
|
case "ExportNamedDeclaration":
|
||
|
case "ExportDefaultDeclaration":
|
||
|
case "ExportAllDeclaration":
|
||
|
return extra.isModule;
|
||
|
|
||
|
default:
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Performs last-minute Esprima-specific compatibility checks and fixes.
|
||
|
* @param {ASTNode} result The node to check.
|
||
|
* @returns {ASTNode} The finished node.
|
||
|
* @private
|
||
|
* @this acorn.Parser
|
||
|
*/
|
||
|
function esprimaFinishNode(result) {
|
||
|
// ensure that parsed node was allowed through ecmaFeatures
|
||
|
if (!isValidNode(result)) {
|
||
|
this.unexpected(result.start);
|
||
|
}
|
||
|
|
||
|
// https://github.com/marijnh/acorn/issues/323
|
||
|
if (result.type === "TryStatement") {
|
||
|
delete result.guardedHandlers;
|
||
|
} else if (result.type === "CatchClause") {
|
||
|
delete result.guard;
|
||
|
}
|
||
|
|
||
|
// Acorn doesn't count the opening and closing backticks as part of templates
|
||
|
// so we have to adjust ranges/locations appropriately.
|
||
|
if (result.type === "TemplateElement") {
|
||
|
|
||
|
// additional adjustment needed if ${ is the last token
|
||
|
var terminalDollarBraceL = this.input.slice(result.end, result.end + 2) === "${";
|
||
|
|
||
|
if (result.range) {
|
||
|
result.range[0]--;
|
||
|
result.range[1] += (terminalDollarBraceL ? 2 : 1);
|
||
|
}
|
||
|
|
||
|
if (result.loc) {
|
||
|
result.loc.start.column--;
|
||
|
result.loc.end.column += (terminalDollarBraceL ? 2 : 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Acorn uses undefined instead of null, which affects serialization
|
||
|
if (result.type === "Literal" && result.value === undefined) {
|
||
|
result.value = null;
|
||
|
}
|
||
|
|
||
|
if (extra.attachComment) {
|
||
|
commentAttachment.processComment(result);
|
||
|
}
|
||
|
|
||
|
if (result.type.indexOf("Function") > -1 && !result.generator) {
|
||
|
result.generator = false;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines if a token is valid given the set of ecmaFeatures.
|
||
|
* @param {acorn.Parser} parser The parser to check.
|
||
|
* @returns {boolean} True if the token is allowed, false if not.
|
||
|
* @private
|
||
|
*/
|
||
|
function isValidToken(parser) {
|
||
|
var ecma = extra.ecmaFeatures;
|
||
|
var type = parser.type;
|
||
|
|
||
|
switch (type) {
|
||
|
case tt.jsxName:
|
||
|
case tt.jsxText:
|
||
|
case tt.jsxTagStart:
|
||
|
case tt.jsxTagEnd:
|
||
|
return ecma.jsx;
|
||
|
|
||
|
// https://github.com/ternjs/acorn/issues/363
|
||
|
case tt.regexp:
|
||
|
if (extra.ecmaVersion < 6 && parser.value.flags && parser.value.flags.indexOf("y") > -1) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
|
||
|
default:
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Injects esprimaFinishNode into the finishNode process.
|
||
|
* @param {Function} finishNode Original finishNode function.
|
||
|
* @returns {ASTNode} The finished node.
|
||
|
* @private
|
||
|
*/
|
||
|
function wrapFinishNode(finishNode) {
|
||
|
return /** @this acorn.Parser */ function(node, type, pos, loc) {
|
||
|
var result = finishNode.call(this, node, type, pos, loc);
|
||
|
return esprimaFinishNode.call(this, result);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
acorn.plugins.espree = function(instance) {
|
||
|
|
||
|
instance.extend("finishNode", wrapFinishNode);
|
||
|
|
||
|
instance.extend("finishNodeAt", wrapFinishNode);
|
||
|
|
||
|
instance.extend("next", function(next) {
|
||
|
return /** @this acorn.Parser */ function() {
|
||
|
if (!isValidToken(this)) {
|
||
|
this.unexpected();
|
||
|
}
|
||
|
return next.call(this);
|
||
|
};
|
||
|
});
|
||
|
|
||
|
// needed for experimental object rest/spread
|
||
|
instance.extend("checkLVal", function(checkLVal) {
|
||
|
|
||
|
return /** @this acorn.Parser */ function(expr, isBinding, checkClashes) {
|
||
|
|
||
|
if (extra.ecmaFeatures.experimentalObjectRestSpread && expr.type === "ObjectPattern") {
|
||
|
for (var i = 0; i < expr.properties.length; i++) {
|
||
|
if (expr.properties[i].type.indexOf("Experimental") === -1) {
|
||
|
this.checkLVal(expr.properties[i].value, isBinding, checkClashes);
|
||
|
}
|
||
|
}
|
||
|
return undefined;
|
||
|
}
|
||
|
|
||
|
return checkLVal.call(this, expr, isBinding, checkClashes);
|
||
|
};
|
||
|
});
|
||
|
|
||
|
instance.extend("parseTopLevel", function(parseTopLevel) {
|
||
|
return /** @this acorn.Parser */ function(node) {
|
||
|
if (extra.ecmaFeatures.impliedStrict && this.options.ecmaVersion >= 5) {
|
||
|
this.strict = true;
|
||
|
}
|
||
|
return parseTopLevel.call(this, node);
|
||
|
};
|
||
|
});
|
||
|
|
||
|
instance.extend("toAssignable", function(toAssignable) {
|
||
|
|
||
|
return /** @this acorn.Parser */ function(node, isBinding, refDestructuringErrors) {
|
||
|
|
||
|
if (extra.ecmaFeatures.experimentalObjectRestSpread &&
|
||
|
node.type === "ObjectExpression"
|
||
|
) {
|
||
|
node.type = "ObjectPattern";
|
||
|
|
||
|
for (var i = 0; i < node.properties.length; i++) {
|
||
|
var prop = node.properties[i];
|
||
|
|
||
|
if (prop.type === "ExperimentalSpreadProperty") {
|
||
|
prop.type = "ExperimentalRestProperty";
|
||
|
} else if (prop.kind !== "init") {
|
||
|
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
|
||
|
} else {
|
||
|
this.toAssignable(prop.value, isBinding);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return node;
|
||
|
} else {
|
||
|
return toAssignable.call(this, node, isBinding, refDestructuringErrors);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Method to parse an object rest or object spread.
|
||
|
* @returns {ASTNode} The node representing object rest or object spread.
|
||
|
* @this acorn.Parser
|
||
|
*/
|
||
|
instance.parseObjectRest = function() {
|
||
|
var node = this.startNode();
|
||
|
this.next();
|
||
|
node.argument = this.parseIdent();
|
||
|
|
||
|
if (this.type === tt.comma) {
|
||
|
this.raise(this.start, "Unexpected trailing comma after rest property");
|
||
|
}
|
||
|
|
||
|
return this.finishNode(node, "ExperimentalRestProperty");
|
||
|
};
|
||
|
|
||
|
instance.extend("parseProperty", function(parseProperty) {
|
||
|
/**
|
||
|
* Override `parseProperty` method to parse rest/spread properties.
|
||
|
* @param {boolean} isPattern True if the object is a destructuring pattern.
|
||
|
* @param {Object} refDestructuringErrors ?
|
||
|
* @returns {ASTNode} The node representing a rest/spread property.
|
||
|
* @this acorn.Parser
|
||
|
*/
|
||
|
return function(isPattern, refDestructuringErrors) {
|
||
|
if (extra.ecmaFeatures.experimentalObjectRestSpread && this.type === tt.ellipsis) {
|
||
|
var prop;
|
||
|
|
||
|
if (isPattern) {
|
||
|
prop = this.parseObjectRest();
|
||
|
} else {
|
||
|
prop = this.parseSpread();
|
||
|
prop.type = "ExperimentalSpreadProperty";
|
||
|
}
|
||
|
|
||
|
return prop;
|
||
|
}
|
||
|
|
||
|
return parseProperty.call(this, isPattern, refDestructuringErrors);
|
||
|
};
|
||
|
});
|
||
|
|
||
|
instance.extend("checkPropClash", function(checkPropClash) {
|
||
|
/**
|
||
|
* Override `checkPropClash` method to avoid clash on rest/spread properties.
|
||
|
* @param {ASTNode} prop A property node to check.
|
||
|
* @param {Object} propHash Names map.
|
||
|
* @param {Object} refDestructuringErrors Destructuring error information.
|
||
|
* @returns {void}
|
||
|
* @this acorn.Parser
|
||
|
*/
|
||
|
return function(prop, propHash, refDestructuringErrors) {
|
||
|
if (prop.type === "ExperimentalRestProperty" || prop.type === "ExperimentalSpreadProperty") {
|
||
|
return;
|
||
|
}
|
||
|
checkPropClash.call(this, prop, propHash, refDestructuringErrors);
|
||
|
};
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Overwrites the default raise method to throw Esprima-style errors.
|
||
|
* @param {int} pos The position of the error.
|
||
|
* @param {string} message The error message.
|
||
|
* @throws {SyntaxError} A syntax error.
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
instance.raise = instance.raiseRecoverable = function(pos, message) {
|
||
|
var loc = getLineInfo(this.input, pos);
|
||
|
var err = new SyntaxError(message);
|
||
|
err.index = pos;
|
||
|
err.lineNumber = loc.line;
|
||
|
err.column = loc.column + 1; // acorn uses 0-based columns
|
||
|
throw err;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Overwrites the default unexpected method to throw Esprima-style errors.
|
||
|
* @param {int} pos The position of the error.
|
||
|
* @throws {SyntaxError} A syntax error.
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
instance.unexpected = function(pos) {
|
||
|
var message = "Unexpected token";
|
||
|
|
||
|
if (pos !== null && pos !== undefined) {
|
||
|
this.pos = pos;
|
||
|
|
||
|
if (this.options.locations) {
|
||
|
while (this.pos < this.lineStart) {
|
||
|
this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1;
|
||
|
--this.curLine;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.nextToken();
|
||
|
}
|
||
|
|
||
|
if (this.end > this.start) {
|
||
|
message += " " + this.input.slice(this.start, this.end);
|
||
|
}
|
||
|
|
||
|
this.raise(this.start, message);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Esprima-FB represents JSX strings as tokens called "JSXText", but Acorn-JSX
|
||
|
* uses regular tt.string without any distinction between this and regular JS
|
||
|
* strings. As such, we intercept an attempt to read a JSX string and set a flag
|
||
|
* on extra so that when tokens are converted, the next token will be switched
|
||
|
* to JSXText via onToken.
|
||
|
*/
|
||
|
instance.extend("jsx_readString", function(jsxReadString) {
|
||
|
return /** @this acorn.Parser */ function(quote) {
|
||
|
var result = jsxReadString.call(this, quote);
|
||
|
if (this.type === tt.string) {
|
||
|
extra.jsxAttrValueToken = true;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
});
|
||
|
};
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Tokenizer
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Tokenizes the given code.
|
||
|
* @param {string} code The code to tokenize.
|
||
|
* @param {Object} options Options defining how to tokenize.
|
||
|
* @returns {Token[]} An array of tokens.
|
||
|
* @throws {SyntaxError} If the input code is invalid.
|
||
|
* @private
|
||
|
*/
|
||
|
function tokenize(code, options) {
|
||
|
var toString,
|
||
|
tokens,
|
||
|
impliedStrict,
|
||
|
translator = new TokenTranslator(tt, code);
|
||
|
|
||
|
toString = String;
|
||
|
if (typeof code !== "string" && !(code instanceof String)) {
|
||
|
code = toString(code);
|
||
|
}
|
||
|
|
||
|
lookahead = null;
|
||
|
|
||
|
// Options matching.
|
||
|
options = assign({}, options);
|
||
|
|
||
|
var acornOptions = {
|
||
|
ecmaVersion: DEFAULT_ECMA_VERSION,
|
||
|
plugins: {
|
||
|
espree: true
|
||
|
}
|
||
|
};
|
||
|
|
||
|
resetExtra();
|
||
|
|
||
|
// Of course we collect tokens here.
|
||
|
options.tokens = true;
|
||
|
extra.tokens = [];
|
||
|
|
||
|
extra.range = (typeof options.range === "boolean") && options.range;
|
||
|
acornOptions.ranges = extra.range;
|
||
|
|
||
|
extra.loc = (typeof options.loc === "boolean") && options.loc;
|
||
|
acornOptions.locations = extra.loc;
|
||
|
|
||
|
extra.comment = typeof options.comment === "boolean" && options.comment;
|
||
|
|
||
|
if (extra.comment) {
|
||
|
acornOptions.onComment = function() {
|
||
|
var comment = convertAcornCommentToEsprimaComment.apply(this, arguments);
|
||
|
extra.comments.push(comment);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
extra.tolerant = typeof options.tolerant === "boolean" && options.tolerant;
|
||
|
|
||
|
acornOptions.ecmaVersion = extra.ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);
|
||
|
|
||
|
// apply parsing flags
|
||
|
if (options.ecmaFeatures && typeof options.ecmaFeatures === "object") {
|
||
|
extra.ecmaFeatures = assign({}, options.ecmaFeatures);
|
||
|
impliedStrict = extra.ecmaFeatures.impliedStrict;
|
||
|
extra.ecmaFeatures.impliedStrict = typeof impliedStrict === "boolean" && impliedStrict;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
var tokenizer = acorn.tokenizer(code, acornOptions);
|
||
|
while ((lookahead = tokenizer.getToken()).type !== tt.eof) {
|
||
|
translator.onToken(lookahead, extra);
|
||
|
}
|
||
|
|
||
|
// filterTokenLocation();
|
||
|
tokens = extra.tokens;
|
||
|
|
||
|
if (extra.comment) {
|
||
|
tokens.comments = extra.comments;
|
||
|
}
|
||
|
if (extra.tolerant) {
|
||
|
tokens.errors = extra.errors;
|
||
|
}
|
||
|
} catch (e) {
|
||
|
throw e;
|
||
|
}
|
||
|
return tokens;
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Parser
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Converts an Acorn comment to a Esprima comment.
|
||
|
* @param {boolean} block True if it's a block comment, false if not.
|
||
|
* @param {string} text The text of the comment.
|
||
|
* @param {int} start The index at which the comment starts.
|
||
|
* @param {int} end The index at which the comment ends.
|
||
|
* @param {Location} startLoc The location at which the comment starts.
|
||
|
* @param {Location} endLoc The location at which the comment ends.
|
||
|
* @returns {Object} The comment object.
|
||
|
* @private
|
||
|
*/
|
||
|
function convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc) {
|
||
|
var comment = {
|
||
|
type: block ? "Block" : "Line",
|
||
|
value: text
|
||
|
};
|
||
|
|
||
|
if (typeof start === "number") {
|
||
|
comment.start = start;
|
||
|
comment.end = end;
|
||
|
comment.range = [start, end];
|
||
|
}
|
||
|
|
||
|
if (typeof startLoc === "object") {
|
||
|
comment.loc = {
|
||
|
start: startLoc,
|
||
|
end: endLoc
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return comment;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parses the given code.
|
||
|
* @param {string} code The code to tokenize.
|
||
|
* @param {Object} options Options defining how to tokenize.
|
||
|
* @returns {ASTNode} The "Program" AST node.
|
||
|
* @throws {SyntaxError} If the input code is invalid.
|
||
|
* @private
|
||
|
*/
|
||
|
function parse(code, options) {
|
||
|
var program,
|
||
|
toString = String,
|
||
|
translator,
|
||
|
impliedStrict,
|
||
|
acornOptions = {
|
||
|
ecmaVersion: DEFAULT_ECMA_VERSION,
|
||
|
plugins: {
|
||
|
espree: true
|
||
|
}
|
||
|
};
|
||
|
|
||
|
lastToken = null;
|
||
|
|
||
|
if (typeof code !== "string" && !(code instanceof String)) {
|
||
|
code = toString(code);
|
||
|
}
|
||
|
|
||
|
resetExtra();
|
||
|
commentAttachment.reset();
|
||
|
|
||
|
if (typeof options !== "undefined") {
|
||
|
extra.range = (typeof options.range === "boolean") && options.range;
|
||
|
extra.loc = (typeof options.loc === "boolean") && options.loc;
|
||
|
extra.attachComment = (typeof options.attachComment === "boolean") && options.attachComment;
|
||
|
|
||
|
if (extra.loc && options.source !== null && options.source !== undefined) {
|
||
|
extra.source = toString(options.source);
|
||
|
}
|
||
|
|
||
|
if (typeof options.tokens === "boolean" && options.tokens) {
|
||
|
extra.tokens = [];
|
||
|
translator = new TokenTranslator(tt, code);
|
||
|
}
|
||
|
if (typeof options.comment === "boolean" && options.comment) {
|
||
|
extra.comment = true;
|
||
|
extra.comments = [];
|
||
|
}
|
||
|
if (typeof options.tolerant === "boolean" && options.tolerant) {
|
||
|
extra.errors = [];
|
||
|
}
|
||
|
if (extra.attachComment) {
|
||
|
extra.range = true;
|
||
|
extra.comments = [];
|
||
|
commentAttachment.reset();
|
||
|
}
|
||
|
|
||
|
acornOptions.ecmaVersion = extra.ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);
|
||
|
|
||
|
if (options.sourceType === "module") {
|
||
|
extra.isModule = true;
|
||
|
|
||
|
// modules must be in 6 at least
|
||
|
if (acornOptions.ecmaVersion < 6) {
|
||
|
acornOptions.ecmaVersion = 6;
|
||
|
extra.ecmaVersion = 6;
|
||
|
}
|
||
|
|
||
|
acornOptions.sourceType = "module";
|
||
|
}
|
||
|
|
||
|
// apply parsing flags after sourceType to allow overriding
|
||
|
if (options.ecmaFeatures && typeof options.ecmaFeatures === "object") {
|
||
|
extra.ecmaFeatures = assign({}, options.ecmaFeatures);
|
||
|
impliedStrict = extra.ecmaFeatures.impliedStrict;
|
||
|
extra.ecmaFeatures.impliedStrict = typeof impliedStrict === "boolean" && impliedStrict;
|
||
|
if (options.ecmaFeatures.globalReturn) {
|
||
|
acornOptions.allowReturnOutsideFunction = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
acornOptions.onToken = function(token) {
|
||
|
if (extra.tokens) {
|
||
|
translator.onToken(token, extra);
|
||
|
}
|
||
|
if (token.type !== tt.eof) {
|
||
|
lastToken = token;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (extra.attachComment || extra.comment) {
|
||
|
acornOptions.onComment = function() {
|
||
|
var comment = convertAcornCommentToEsprimaComment.apply(this, arguments);
|
||
|
extra.comments.push(comment);
|
||
|
|
||
|
if (extra.attachComment) {
|
||
|
commentAttachment.addComment(comment);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (extra.range) {
|
||
|
acornOptions.ranges = true;
|
||
|
}
|
||
|
|
||
|
if (extra.loc) {
|
||
|
acornOptions.locations = true;
|
||
|
}
|
||
|
|
||
|
if (extra.ecmaFeatures.jsx) {
|
||
|
// Should process jsx plugin before espree plugin.
|
||
|
acornOptions.plugins = {
|
||
|
jsx: true,
|
||
|
espree: true
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
program = acorn.parse(code, acornOptions);
|
||
|
program.sourceType = extra.isModule ? "module" : "script";
|
||
|
|
||
|
if (extra.comment || extra.attachComment) {
|
||
|
program.comments = extra.comments;
|
||
|
}
|
||
|
|
||
|
if (extra.tokens) {
|
||
|
program.tokens = extra.tokens;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Adjust opening and closing position of program to match Esprima.
|
||
|
* Acorn always starts programs at range 0 whereas Esprima starts at the
|
||
|
* first AST node's start (the only real difference is when there's leading
|
||
|
* whitespace or leading comments). Acorn also counts trailing whitespace
|
||
|
* as part of the program whereas Esprima only counts up to the last token.
|
||
|
*/
|
||
|
if (program.range) {
|
||
|
program.range[0] = program.body.length ? program.body[0].range[0] : program.range[0];
|
||
|
program.range[1] = lastToken ? lastToken.range[1] : program.range[1];
|
||
|
}
|
||
|
|
||
|
if (program.loc) {
|
||
|
program.loc.start = program.body.length ? program.body[0].loc.start : program.loc.start;
|
||
|
program.loc.end = lastToken ? lastToken.loc.end : program.loc.end;
|
||
|
}
|
||
|
|
||
|
return program;
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Public
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
exports.version = require("./package.json").version;
|
||
|
|
||
|
exports.tokenize = tokenize;
|
||
|
|
||
|
exports.parse = parse;
|
||
|
|
||
|
// Deep copy.
|
||
|
/* istanbul ignore next */
|
||
|
exports.Syntax = (function() {
|
||
|
var name, types = {};
|
||
|
|
||
|
if (typeof Object.create === "function") {
|
||
|
types = Object.create(null);
|
||
|
}
|
||
|
|
||
|
for (name in astNodeTypes) {
|
||
|
if (astNodeTypes.hasOwnProperty(name)) {
|
||
|
types[name] = astNodeTypes[name];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (typeof Object.freeze === "function") {
|
||
|
Object.freeze(types);
|
||
|
}
|
||
|
|
||
|
return types;
|
||
|
}());
|
||
|
|
||
|
/* istanbul ignore next */
|
||
|
exports.VisitorKeys = (function() {
|
||
|
var visitorKeys = require("./lib/visitor-keys");
|
||
|
var name,
|
||
|
keys = {};
|
||
|
|
||
|
if (typeof Object.create === "function") {
|
||
|
keys = Object.create(null);
|
||
|
}
|
||
|
|
||
|
for (name in visitorKeys) {
|
||
|
if (visitorKeys.hasOwnProperty(name)) {
|
||
|
keys[name] = visitorKeys[name];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (typeof Object.freeze === "function") {
|
||
|
Object.freeze(keys);
|
||
|
}
|
||
|
|
||
|
return keys;
|
||
|
}());
|