ReplaceSource.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. var Source = require("./Source");
  7. var SourceNode = require("source-map").SourceNode;
  8. var SourceListMap = require("source-list-map").SourceListMap;
  9. var fromStringWithSourceMap = require("source-list-map").fromStringWithSourceMap;
  10. var SourceMapConsumer = require("source-map").SourceMapConsumer;
  11. class ReplaceSource extends Source {
  12. constructor(source, name) {
  13. super();
  14. this._source = source;
  15. this._name = name;
  16. this.replacements = [];
  17. }
  18. replace(start, end, newValue) {
  19. if(typeof newValue !== "string")
  20. throw new Error("insertion must be a string, but is a " + typeof newValue);
  21. this.replacements.push([start, end, newValue, this.replacements.length]);
  22. }
  23. insert(pos, newValue) {
  24. if(typeof newValue !== "string")
  25. throw new Error("insertion must be a string, but is a " + typeof newValue + ": " + newValue);
  26. this.replacements.push([pos, pos - 1, newValue, this.replacements.length]);
  27. }
  28. source(options) {
  29. return this._replaceString(this._source.source());
  30. }
  31. original() {
  32. return this._source;
  33. }
  34. _sortReplacements() {
  35. this.replacements.sort(function(a, b) {
  36. var diff = b[1] - a[1];
  37. if(diff !== 0)
  38. return diff;
  39. diff = b[0] - a[0];
  40. if(diff !== 0)
  41. return diff;
  42. return b[3] - a[3];
  43. });
  44. }
  45. _replaceString(str) {
  46. if(typeof str !== "string")
  47. throw new Error("str must be a string, but is a " + typeof str + ": " + str);
  48. this._sortReplacements();
  49. var result = [str];
  50. this.replacements.forEach(function(repl) {
  51. var remSource = result.pop();
  52. var splitted1 = this._splitString(remSource, Math.floor(repl[1] + 1));
  53. var splitted2 = this._splitString(splitted1[0], Math.floor(repl[0]));
  54. result.push(splitted1[1], repl[2], splitted2[0]);
  55. }, this);
  56. result = result.reverse();
  57. return result.join("");
  58. }
  59. node(options) {
  60. this._sortReplacements();
  61. var result = [this._source.node(options)];
  62. this.replacements.forEach(function(repl) {
  63. var remSource = result.pop();
  64. var splitted1 = this._splitSourceNode(remSource, Math.floor(repl[1] + 1));
  65. var splitted2;
  66. if(Array.isArray(splitted1)) {
  67. splitted2 = this._splitSourceNode(splitted1[0], Math.floor(repl[0]));
  68. if(Array.isArray(splitted2)) {
  69. result.push(splitted1[1], this._replacementToSourceNode(splitted2[1], repl[2]), splitted2[0]);
  70. } else {
  71. result.push(splitted1[1], this._replacementToSourceNode(splitted1[1], repl[2]), splitted1[0]);
  72. }
  73. } else {
  74. splitted2 = this._splitSourceNode(remSource, Math.floor(repl[0]));
  75. if(Array.isArray(splitted2)) {
  76. result.push(this._replacementToSourceNode(splitted2[1], repl[2]), splitted2[0]);
  77. } else {
  78. result.push(repl[2], remSource);
  79. }
  80. }
  81. }, this);
  82. result = result.reverse();
  83. return new SourceNode(null, null, null, result);
  84. }
  85. listMap(options) {
  86. this._sortReplacements();
  87. var map = this._source.listMap(options);
  88. var currentIndex = 0;
  89. var replacements = this.replacements;
  90. var idxReplacement = replacements.length - 1;
  91. var removeChars = 0;
  92. map = map.mapGeneratedCode(function(str) {
  93. var newCurrentIndex = currentIndex + str.length;
  94. if(removeChars > str.length) {
  95. removeChars -= str.length;
  96. str = "";
  97. } else {
  98. if(removeChars > 0) {
  99. str = str.substr(removeChars);
  100. currentIndex += removeChars;
  101. removeChars = 0;
  102. }
  103. var finalStr = "";
  104. while(idxReplacement >= 0 && replacements[idxReplacement][0] < newCurrentIndex) {
  105. var repl = replacements[idxReplacement];
  106. var start = Math.floor(repl[0]);
  107. var end = Math.floor(repl[1] + 1);
  108. var before = str.substr(0, Math.max(0, start - currentIndex));
  109. if(end <= newCurrentIndex) {
  110. var after = str.substr(Math.max(0, end - currentIndex));
  111. finalStr += before + repl[2];
  112. str = after;
  113. currentIndex = Math.max(currentIndex, end);
  114. } else {
  115. finalStr += before + repl[2];
  116. str = "";
  117. removeChars = end - newCurrentIndex;
  118. }
  119. idxReplacement--;
  120. }
  121. str = finalStr + str;
  122. }
  123. currentIndex = newCurrentIndex;
  124. return str;
  125. });
  126. var extraCode = "";
  127. while(idxReplacement >= 0) {
  128. extraCode += replacements[idxReplacement][2];
  129. idxReplacement--;
  130. }
  131. if(extraCode) {
  132. map.add(extraCode);
  133. }
  134. return map;
  135. }
  136. _replacementToSourceNode(oldNode, newString) {
  137. var map = oldNode.toStringWithSourceMap({
  138. file: "?"
  139. }).map;
  140. var original = new SourceMapConsumer(map.toJSON()).originalPositionFor({
  141. line: 1,
  142. column: 0
  143. });
  144. if(original) {
  145. return new SourceNode(original.line, original.column, original.source, newString);
  146. } else {
  147. return newString;
  148. }
  149. }
  150. _splitSourceNode(node, position) {
  151. if(typeof node === "string") {
  152. if(node.length <= position) return position - node.length;
  153. return position <= 0 ? ["", node] : [node.substr(0, position), node.substr(position)];
  154. } else {
  155. for(var i = 0; i < node.children.length; i++) {
  156. position = this._splitSourceNode(node.children[i], position);
  157. if(Array.isArray(position)) {
  158. var leftNode = new SourceNode(
  159. node.line,
  160. node.column,
  161. node.source,
  162. node.children.slice(0, i).concat([position[0]]),
  163. node.name
  164. );
  165. var rightNode = new SourceNode(
  166. node.line,
  167. node.column,
  168. node.source, [position[1]].concat(node.children.slice(i + 1)),
  169. node.name
  170. );
  171. leftNode.sourceContents = node.sourceContents;
  172. return [leftNode, rightNode];
  173. }
  174. }
  175. return position;
  176. }
  177. }
  178. _splitString(str, position) {
  179. return position <= 0 ? ["", str] : [str.substr(0, position), str.substr(position)];
  180. }
  181. }
  182. require("./SourceAndMapMixin")(ReplaceSource.prototype);
  183. module.exports = ReplaceSource;