| var assert = require("assert"); |
| var types = require("./types"); |
| var getFieldValue = types.getFieldValue; |
| var n = types.namedTypes; |
| var sourceMap = require("source-map"); |
| var SourceMapConsumer = sourceMap.SourceMapConsumer; |
| var SourceMapGenerator = sourceMap.SourceMapGenerator; |
| var hasOwn = Object.prototype.hasOwnProperty; |
| var util = exports; |
| |
| function getUnionOfKeys() { |
| var result = {}; |
| var argc = arguments.length; |
| for (var i = 0; i < argc; ++i) { |
| var keys = Object.keys(arguments[i]); |
| var keyCount = keys.length; |
| for (var j = 0; j < keyCount; ++j) { |
| result[keys[j]] = true; |
| } |
| } |
| return result; |
| } |
| util.getUnionOfKeys = getUnionOfKeys; |
| |
| function comparePos(pos1, pos2) { |
| return (pos1.line - pos2.line) || (pos1.column - pos2.column); |
| } |
| util.comparePos = comparePos; |
| |
| function copyPos(pos) { |
| return { |
| line: pos.line, |
| column: pos.column |
| }; |
| } |
| util.copyPos = copyPos; |
| |
| util.composeSourceMaps = function(formerMap, latterMap) { |
| if (formerMap) { |
| if (!latterMap) { |
| return formerMap; |
| } |
| } else { |
| return latterMap || null; |
| } |
| |
| var smcFormer = new SourceMapConsumer(formerMap); |
| var smcLatter = new SourceMapConsumer(latterMap); |
| var smg = new SourceMapGenerator({ |
| file: latterMap.file, |
| sourceRoot: latterMap.sourceRoot |
| }); |
| |
| var sourcesToContents = {}; |
| |
| smcLatter.eachMapping(function(mapping) { |
| var origPos = smcFormer.originalPositionFor({ |
| line: mapping.originalLine, |
| column: mapping.originalColumn |
| }); |
| |
| var sourceName = origPos.source; |
| if (sourceName === null) { |
| return; |
| } |
| |
| smg.addMapping({ |
| source: sourceName, |
| original: copyPos(origPos), |
| generated: { |
| line: mapping.generatedLine, |
| column: mapping.generatedColumn |
| }, |
| name: mapping.name |
| }); |
| |
| var sourceContent = smcFormer.sourceContentFor(sourceName); |
| if (sourceContent && !hasOwn.call(sourcesToContents, sourceName)) { |
| sourcesToContents[sourceName] = sourceContent; |
| smg.setSourceContent(sourceName, sourceContent); |
| } |
| }); |
| |
| return smg.toJSON(); |
| }; |
| |
| util.getTrueLoc = function(node, lines) { |
| // It's possible that node is newly-created (not parsed by Esprima), |
| // in which case it probably won't have a .loc property (or an |
| // .original property for that matter). That's fine; we'll just |
| // pretty-print it as usual. |
| if (!node.loc) { |
| return null; |
| } |
| |
| var result = { |
| start: node.loc.start, |
| end: node.loc.end |
| }; |
| |
| function include(node) { |
| expandLoc(result, node.loc); |
| } |
| |
| // If the node has any comments, their locations might contribute to |
| // the true start/end positions of the node. |
| if (node.comments) { |
| node.comments.forEach(include); |
| } |
| |
| // If the node is an export declaration and its .declaration has any |
| // decorators, their locations might contribute to the true start/end |
| // positions of the export declaration node. |
| if (node.declaration && util.isExportDeclaration(node) && |
| node.declaration.decorators) { |
| node.declaration.decorators.forEach(include); |
| } |
| |
| if (comparePos(result.start, result.end) < 0) { |
| // Trim leading whitespace. |
| result.start = copyPos(result.start); |
| lines.skipSpaces(result.start, false, true); |
| |
| if (comparePos(result.start, result.end) < 0) { |
| // Trim trailing whitespace, if the end location is not already the |
| // same as the start location. |
| result.end = copyPos(result.end); |
| lines.skipSpaces(result.end, true, true); |
| } |
| } |
| |
| return result; |
| }; |
| |
| function expandLoc(parentLoc, childLoc) { |
| if (parentLoc && childLoc) { |
| if (comparePos(childLoc.start, parentLoc.start) < 0) { |
| parentLoc.start = childLoc.start; |
| } |
| |
| if (comparePos(parentLoc.end, childLoc.end) < 0) { |
| parentLoc.end = childLoc.end; |
| } |
| } |
| } |
| |
| util.fixFaultyLocations = function(node, lines) { |
| var loc = node.loc; |
| if (loc) { |
| if (loc.start.line < 1) { |
| loc.start.line = 1; |
| } |
| |
| if (loc.end.line < 1) { |
| loc.end.line = 1; |
| } |
| } |
| |
| if (node.type === "File") { |
| // Babylon returns File nodes whose .loc.{start,end} do not include |
| // leading or trailing whitespace. |
| loc.start = lines.firstPos(); |
| loc.end = lines.lastPos(); |
| } |
| |
| if (node.type === "TemplateLiteral") { |
| fixTemplateLiteral(node, lines); |
| |
| } else if (loc && node.decorators) { |
| // Expand the .loc of the node responsible for printing the decorators |
| // (here, the decorated node) so that it includes node.decorators. |
| node.decorators.forEach(function (decorator) { |
| expandLoc(loc, decorator.loc); |
| }); |
| |
| } else if (node.declaration && util.isExportDeclaration(node)) { |
| // Nullify .loc information for the child declaration so that we never |
| // try to reprint it without also reprinting the export declaration. |
| node.declaration.loc = null; |
| |
| // Expand the .loc of the node responsible for printing the decorators |
| // (here, the export declaration) so that it includes node.decorators. |
| var decorators = node.declaration.decorators; |
| if (decorators) { |
| decorators.forEach(function (decorator) { |
| expandLoc(loc, decorator.loc); |
| }); |
| } |
| |
| } else 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; |
| |
| if (n.FunctionExpression.check(node.value)) { |
| // FunctionExpression method values should be anonymous, |
| // because their .id fields are ignored anyway. |
| node.value.id = null; |
| } |
| |
| } else if (node.type === "ObjectTypeProperty") { |
| var loc = node.loc; |
| var end = loc && loc.end; |
| if (end) { |
| end = copyPos(end); |
| if (lines.prevPos(end) && |
| lines.charAt(end) === ",") { |
| // Some parsers accidentally include trailing commas in the |
| // .loc.end information for ObjectTypeProperty nodes. |
| if ((end = lines.skipSpaces(end, true, true))) { |
| loc.end = end; |
| } |
| } |
| } |
| } |
| }; |
| |
| function fixTemplateLiteral(node, lines) { |
| assert.strictEqual(node.type, "TemplateLiteral"); |
| |
| if (node.quasis.length === 0) { |
| // If there are no quasi elements, then there is nothing to fix. |
| return; |
| } |
| |
| // First we need to exclude the opening ` from the .loc of the first |
| // quasi element, in case the parser accidentally decided to include it. |
| var afterLeftBackTickPos = copyPos(node.loc.start); |
| assert.strictEqual(lines.charAt(afterLeftBackTickPos), "`"); |
| assert.ok(lines.nextPos(afterLeftBackTickPos)); |
| var firstQuasi = node.quasis[0]; |
| if (comparePos(firstQuasi.loc.start, afterLeftBackTickPos) < 0) { |
| firstQuasi.loc.start = afterLeftBackTickPos; |
| } |
| |
| // Next we need to exclude the closing ` from the .loc of the last quasi |
| // element, in case the parser accidentally decided to include it. |
| var rightBackTickPos = copyPos(node.loc.end); |
| assert.ok(lines.prevPos(rightBackTickPos)); |
| assert.strictEqual(lines.charAt(rightBackTickPos), "`"); |
| var lastQuasi = node.quasis[node.quasis.length - 1]; |
| if (comparePos(rightBackTickPos, lastQuasi.loc.end) < 0) { |
| lastQuasi.loc.end = rightBackTickPos; |
| } |
| |
| // Now we need to exclude ${ and } characters from the .loc's of all |
| // quasi elements, since some parsers accidentally include them. |
| node.expressions.forEach(function (expr, i) { |
| // Rewind from expr.loc.start over any whitespace and the ${ that |
| // precedes the expression. The position of the $ should be the same |
| // as the .loc.end of the preceding quasi element, but some parsers |
| // accidentally include the ${ in the .loc of the quasi element. |
| var dollarCurlyPos = lines.skipSpaces(expr.loc.start, true, false); |
| if (lines.prevPos(dollarCurlyPos) && |
| lines.charAt(dollarCurlyPos) === "{" && |
| lines.prevPos(dollarCurlyPos) && |
| lines.charAt(dollarCurlyPos) === "$") { |
| var quasiBefore = node.quasis[i]; |
| if (comparePos(dollarCurlyPos, quasiBefore.loc.end) < 0) { |
| quasiBefore.loc.end = dollarCurlyPos; |
| } |
| } |
| |
| // Likewise, some parsers accidentally include the } that follows |
| // the expression in the .loc of the following quasi element. |
| var rightCurlyPos = lines.skipSpaces(expr.loc.end, false, false); |
| if (lines.charAt(rightCurlyPos) === "}") { |
| assert.ok(lines.nextPos(rightCurlyPos)); |
| // Now rightCurlyPos is technically the position just after the }. |
| var quasiAfter = node.quasis[i + 1]; |
| if (comparePos(quasiAfter.loc.start, rightCurlyPos) < 0) { |
| quasiAfter.loc.start = rightCurlyPos; |
| } |
| } |
| }); |
| } |
| |
| util.isExportDeclaration = function (node) { |
| if (node) switch (node.type) { |
| case "ExportDeclaration": |
| case "ExportDefaultDeclaration": |
| case "ExportDefaultSpecifier": |
| case "DeclareExportDeclaration": |
| case "ExportNamedDeclaration": |
| case "ExportAllDeclaration": |
| return true; |
| } |
| |
| return false; |
| }; |
| |
| util.getParentExportDeclaration = function (path) { |
| var parentNode = path.getParentNode(); |
| if (path.getName() === "declaration" && |
| util.isExportDeclaration(parentNode)) { |
| return parentNode; |
| } |
| |
| return null; |
| }; |
| |
| util.isTrailingCommaEnabled = function(options, context) { |
| var trailingComma = options.trailingComma; |
| if (typeof trailingComma === "object") { |
| return !!trailingComma[context]; |
| } |
| return !!trailingComma; |
| }; |