var assert = require(“assert”); var types = require(“./types”); var n = types.namedTypes; var b = types.builders; var isObject = types.builtInTypes.object; var isArray = types.builtInTypes.array; var isFunction = types.builtInTypes.function; var Patcher = require(“./patcher”).Patcher; var normalizeOptions = require(“./options”).normalize; var fromString = require(“./lines”).fromString; var addComments = require(“./comments”).add; var hasOwn = Object.prototype.hasOwnProperty;
exports.parse = function parse(source, options) {
options = normalizeOptions(options); var lines = fromString(source, options); var sourceWithoutTabs = lines.toString({ tabWidth: options.tabWidth, reuseWhitespace: false, useTabs: false }); var pure = options.esprima.parse(sourceWithoutTabs, { loc: true, range: options.range, comment: true, tolerant: options.tolerant }); new LocationFixer(lines).fix(pure); addComments(pure, lines); // In order to ensure we reprint leading and trailing program // comments, wrap the original Program node with a File node. pure = b.file(pure); pure.loc = { lines: lines, indent: 0, start: lines.firstPos(), end: lines.lastPos() }; // Return a copy of the original AST so that any changes made may be // compared to the original. return copyAst(pure);
};
function LocationFixer(lines) {
assert.ok(this instanceof LocationFixer); this.lines = lines; this.indent = 0;
}
var LFp = LocationFixer.prototype;
LFp.fix = function(node) {
if (isArray.check(node)) { node.forEach(this.fix, this); return; } if (!isObject.check(node)) { return; } var lines = this.lines; var loc = node && node.loc; var start = loc && loc.start; var end = loc && loc.end; var oldIndent = this.indent; var newIndent = oldIndent; if (start) { start.line = Math.max(start.line, 1); if (lines.isPrecededOnlyByWhitespace(start)) { // The indent returned by lines.getIndentAt is the column of // the first non-space character in the line, but start.column // may fall before that character, as when a file begins with // whitespace but its start.column nevertheless must be 0. assert.ok(start.column <= lines.getIndentAt(start.line)); newIndent = this.indent = start.column; } } var names = types.getFieldNames(node); for (var i = 0, len = names.length; i < len; ++i) { this.fix(node[names[i]]); } // Restore original value of this.indent after the recursive call. this.indent = oldIndent; if (loc) { loc.lines = lines; loc.indent = newIndent; } if (end) { end.line = Math.max(end.line, 1); var pos = { line: end.line, column: end.column }; // Negative columns might indicate an Esprima bug? // For now, treat them as reverse indices, a la Python. if (pos.column < 0) pos.column += lines.getLineLength(pos.line); while (lines.prevPos(pos)) { if (/\S/.test(lines.charAt(pos))) { assert.ok(lines.nextPos(pos)); end.line = pos.line; end.column = pos.column; break; } } } if ((n.MethodDefinition && n.MethodDefinition.check(node)) || (n.Property.check(node) && (node.method || node.shorthand))) { // If the node is a MethodDefinition or a .method or .shorthand // Property, then the location information stored in // node.value.loc is very likely untrustworthy (just the {body} // part of a method, or nothing in the case of shorthand // properties), so we null out that information to prevent // accidental reuse of bogus source code during reprinting. node.value.loc = null; }
};
function copyAst(node) {
if (typeof node !== "object") { return node; } if (isObject.check(node)) { var copy = Object.create(Object.getPrototypeOf(node), { original: { // Provide a link from the copy to the original. value: node, configurable: false, enumerable: false, writable: true } }); for (var key in node) { var val = node[key]; if (val && key === "loc") { copy.loc = { start: { line: val.start.line, column: val.start.column }, end: { line: val.end.line, column: val.end.column }, lines: val.lines }; } else if (hasOwn.call(node, key)) { copy[key] = copyAst(val); } } return copy; } if (isArray.check(node)) { return node.map(copyAst); } return node;
}