123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- "use strict";
- const os = require("os");
- const convertSourceMap = require("convert-source-map");
- const SourceMapConsumer = require("source-map").SourceMapConsumer;
- const SourceMapGenerator = require("source-map").SourceMapGenerator;
- const stableSort = require("stable");
- function SourceMapper(src, nodePositions, fragments, inFile, sourceRoot) {
- this.generator = new SourceMapGenerator({ sourceRoot: sourceRoot });
- this.src = src;
- // stableSort does not mutate input array so no need to copy it
- this.nodePositions = stableSort(nodePositions, compareLoc);
- this.fragments = stableSort(fragments, function(a, b) { return a.start - b.start });
- this.inFile = inFile || "source.js";
- this.generator.setSourceContent(this.inFile, src);
- }
- SourceMapper.prototype.calculateMappings = function() {
- const self = this;
- // These offsets represent the difference in coordinates between a node in the source
- // and the corresponding position in the output.
- let lineOffset = 0;
- let columnOffset = 0;
- // Since the column position resets to zero after each newline, we have to keep track
- // of the current line that columnOffset refers to in order to know whether to reset it
- let currentLine = 0;
- let frag = 0;
- let pos = 0;
- while (pos < self.nodePositions.length) {
- while (frag < self.fragments.length &&
- compareLoc(self.fragments[frag].loc.start, self.nodePositions[pos]) < 1) {
- const fragmentLines = self.fragments[frag].str.split("\n");
- const addedNewlines = fragmentLines.length - 1;
- const replacedLines = self.fragments[frag].loc.end.line - self.fragments[frag].loc.start.line;
- const replacedColumns = self.fragments[frag].loc.end.column - self.fragments[frag].loc.start.column;
- // If there were any lines added by the fragment string, the line offset should increase;
- // If there were any lines removed by the fragment replacement then the line offset should decrease
- lineOffset = lineOffset + addedNewlines - replacedLines;
- // The column position needs to reset after each newline. So if the fragment added any
- // newlines then the column offset is the difference between the column of the last line of
- // the fragment, and the column of the end of the replaced section of the source.
- // Otherwise we increment or decrement the column offset just like how the line offset works.
- // Note that "replacedColumns" might be negative in some cases (if the beginning of the source
- // was further right than the end due to a newline); the math still works out.
- columnOffset = fragmentLines.length > 1 ?
- fragmentLines[fragmentLines.length - 1].length - self.fragments[frag].loc.end.column :
- columnOffset + self.fragments[frag].str.length - replacedColumns;
- currentLine = self.fragments[frag].loc.end.line;
- // Skip creating mappings for any source nodes that were replaced by this fragment (and are thus
- // no longer a part of the output)
- while (pos < self.nodePositions.length &&
- compareLoc(self.fragments[frag].loc.end, self.nodePositions[pos]) > 0) {
- ++pos;
- }
- ++frag;
- }
- if (pos < self.nodePositions.length) {
- if (currentLine < self.nodePositions[pos].line)
- columnOffset = 0;
- self.addMapping(self.nodePositions[pos], {
- line: self.nodePositions[pos].line + lineOffset,
- column: self.nodePositions[pos].column + columnOffset
- });
- ++pos;
- }
- }
- }
- SourceMapper.prototype.addMapping = function(input, output) {
- this.generator.addMapping({
- source: this.inFile,
- original: input,
- generated: output
- });
- }
- SourceMapper.prototype.applySourceMap = function (consumer) {
- this.generator.applySourceMap(consumer);
- }
- SourceMapper.prototype.generate = function () {
- return this.generator.toString();
- }
- function compareLoc(a, b) {
- return (a.line - b.line) || (a.column - b.column);
- }
- module.exports = function generateSourcemap(result, src, nodePositions, fragments, mapOpts) {
- const existingMap = convertSourceMap.fromSource(src);
- src = convertSourceMap.removeMapFileComments(src);
- const mapper = new SourceMapper(src, nodePositions, fragments, mapOpts.inFile, mapOpts.sourceRoot);
- mapper.calculateMappings();
- if (mapOpts.inline) {
- if (existingMap)
- mapper.applySourceMap(new SourceMapConsumer(existingMap.toObject()));
- result.src = convertSourceMap.removeMapFileComments(result.src) +
- os.EOL +
- convertSourceMap.fromJSON(mapper.generate()).toComment();
- } else {
- result.map = mapper.generate();
- }
- }
|