ReplaceSource.js 5.27 KB
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
var Source = require("./Source");
var SourceNode = require("source-map").SourceNode;
var SourceListMap = require("source-list-map").SourceListMap;
var fromStringWithSourceMap = require("source-list-map").fromStringWithSourceMap;
var SourceMapConsumer = require("source-map").SourceMapConsumer;

function ReplaceSource(source, name) {
	Source.call(this);
	this._source = source;
	this._name = name;
	this.replacements = [];
}
module.exports = ReplaceSource;

ReplaceSource.prototype = Object.create(Source.prototype);
ReplaceSource.prototype.constructor = ReplaceSource;

ReplaceSource.prototype.replace = function(start, end, newValue) {
	if(typeof newValue !== "string")
		throw new Error("insertion must be a string, but is a " + typeof newValue);
	this.replacements.push([start, end, newValue, this.replacements.length]);
};

ReplaceSource.prototype.insert = function(pos, newValue) {
	if(typeof newValue !== "string")
		throw new Error("insertion must be a string, but is a " + typeof newValue + ": " + newValue);
	this.replacements.push([pos, pos - 1, newValue, this.replacements.length]);
};

ReplaceSource.prototype.source = function(options) {
	return this._replaceString(this._source.source());
};

ReplaceSource.prototype._sortReplacements = function() {
	this.replacements.sort(function(a, b) {
		var diff = b[1] - a[1];
		if(diff !== 0)
			return diff;
		diff = b[0] - a[0];
		if(diff !== 0)
			return diff;
		return b[3] - a[3];
	});
};

ReplaceSource.prototype._replaceString = function(str) {
	if(typeof str !== "string")
		throw new Error("str must be a string, but is a " + typeof str + ": " + str);
	this._sortReplacements();
	var result = [str];
	this.replacements.forEach(function(repl) {
		var remSource = result.pop();
		var splitted1 = this._splitString(remSource, Math.floor(repl[1] + 1));
		var splitted2 = this._splitString(splitted1[0], Math.floor(repl[0]));
		result.push(splitted1[1], repl[2], splitted2[0]);
	}, this);
	result = result.reverse();
	return result.join("");
};

require("./SourceAndMapMixin")(ReplaceSource.prototype);

ReplaceSource.prototype.node = function(options) {
	this._sortReplacements();
	var result = [this._source.node(options)];
	this.replacements.forEach(function(repl) {
		var remSource = result.pop();
		var splitted1 = this._splitSourceNode(remSource, Math.floor(repl[1] + 1));
		var splitted2;
		if(Array.isArray(splitted1)) {
			splitted2 = this._splitSourceNode(splitted1[0], Math.floor(repl[0]));
			if(Array.isArray(splitted2)) {
				result.push(splitted1[1], this._replacementToSourceNode(splitted2[1], repl[2]), splitted2[0]);
			} else {
				result.push(splitted1[1], this._replacementToSourceNode(splitted1[1], repl[2]), splitted1[0]);
			}
		} else {
			splitted2 = this._splitSourceNode(remSource, Math.floor(repl[0]));
			if(Array.isArray(splitted2)) {
				result.push(this._replacementToSourceNode(splitted2[1], repl[2]), splitted2[0]);
			} else {
				result.push(repl[2], remSource);
			}
		}
	}, this);
	result = result.reverse();
	return new SourceNode(null, null, null, result);
};

ReplaceSource.prototype.listMap = function(options) {
	var map = this._source.listMap(options);
	if(map.children.length !== 1) {
		var code = map.toString();
		code = this._replaceString(code).split("\n");
		var currentIndex = 0;
		map.mapGeneratedCode(function(str) {
			var idx = -1;
			var count = -1;
			do {
				count++;
				idx = str.indexOf("\n", idx + 1);
			} while (idx >= 0);
			if(!count) return "";
			var result = code.slice(currentIndex, currentIndex + count).join("\n") + "\n";
			currentIndex += count;
			return result;
		});
		map.add(code.slice(currentIndex).join("\n"));
	} else {
		map.mapGeneratedCode(this._replaceString.bind(this));
	}
	return map;
};

ReplaceSource.prototype._replacementToSourceNode = function(oldNode, newString) {
	var map = oldNode.toStringWithSourceMap({
		file: "?"
	}).map;
	var original = new SourceMapConsumer(map.toJSON()).originalPositionFor({
		line: 1,
		column: 0
	});
	if(original) {
		return new SourceNode(original.line, original.column, original.source, newString);
	} else {
		return newString;
	}
};

ReplaceSource.prototype._splitSourceNode = function(node, position) {
	if(typeof node === "string") {
		if(node.length <= position) return position - node.length;
		return position <= 0 ? ["", node] : [node.substr(0, position), node.substr(position)];
	} else {
		for(var i = 0; i < node.children.length; i++) {
			position = this._splitSourceNode(node.children[i], position);
			if(Array.isArray(position)) {
				var leftNode = new SourceNode(
					node.line,
					node.column,
					node.source,
					node.children.slice(0, i).concat([position[0]]),
					node.name
				);
				var rightNode = new SourceNode(
					node.line,
					node.column,
					node.source, [position[1]].concat(node.children.slice(i + 1)),
					node.name
				);
				leftNode.sourceContents = node.sourceContents;
				return [leftNode, rightNode];
			}
		}
		return position;
	}
};

ReplaceSource.prototype._splitString = function(str, position) {
	return position <= 0 ? ["", str] : [str.substr(0, position), str.substr(position)];
};