ReplaceSource.js 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. // write out result array in reverse order
  57. let resultStr = "";
  58. for(let i = result.length - 1; i >= 0; --i) {
  59. resultStr += result[i];
  60. }
  61. return resultStr;
  62. }
  63. node(options) {
  64. this._sortReplacements();
  65. var result = [this._source.node(options)];
  66. this.replacements.forEach(function(repl) {
  67. var remSource = result.pop();
  68. var splitted1 = this._splitSourceNode(remSource, Math.floor(repl[1] + 1));
  69. var splitted2;
  70. if(Array.isArray(splitted1)) {
  71. splitted2 = this._splitSourceNode(splitted1[0], Math.floor(repl[0]));
  72. if(Array.isArray(splitted2)) {
  73. result.push(splitted1[1], this._replacementToSourceNode(splitted2[1], repl[2]), splitted2[0]);
  74. } else {
  75. result.push(splitted1[1], this._replacementToSourceNode(splitted1[1], repl[2]), splitted1[0]);
  76. }
  77. } else {
  78. splitted2 = this._splitSourceNode(remSource, Math.floor(repl[0]));
  79. if(Array.isArray(splitted2)) {
  80. result.push(this._replacementToSourceNode(splitted2[1], repl[2]), splitted2[0]);
  81. } else {
  82. result.push(repl[2], remSource);
  83. }
  84. }
  85. }, this);
  86. result = result.reverse();
  87. return new SourceNode(null, null, null, result);
  88. }
  89. listMap(options) {
  90. this._sortReplacements();
  91. var map = this._source.listMap(options);
  92. var currentIndex = 0;
  93. var replacements = this.replacements;
  94. var idxReplacement = replacements.length - 1;
  95. var removeChars = 0;
  96. map = map.mapGeneratedCode(function(str) {
  97. var newCurrentIndex = currentIndex + str.length;
  98. if(removeChars > str.length) {
  99. removeChars -= str.length;
  100. str = "";
  101. } else {
  102. if(removeChars > 0) {
  103. str = str.substr(removeChars);
  104. currentIndex += removeChars;
  105. removeChars = 0;
  106. }
  107. var finalStr = "";
  108. while(idxReplacement >= 0 && replacements[idxReplacement][0] < newCurrentIndex) {
  109. var repl = replacements[idxReplacement];
  110. var start = Math.floor(repl[0]);
  111. var end = Math.floor(repl[1] + 1);
  112. var before = str.substr(0, Math.max(0, start - currentIndex));
  113. if(end <= newCurrentIndex) {
  114. var after = str.substr(Math.max(0, end - currentIndex));
  115. finalStr += before + repl[2];
  116. str = after;
  117. currentIndex = Math.max(currentIndex, end);
  118. } else {
  119. finalStr += before + repl[2];
  120. str = "";
  121. removeChars = end - newCurrentIndex;
  122. }
  123. idxReplacement--;
  124. }
  125. str = finalStr + str;
  126. }
  127. currentIndex = newCurrentIndex;
  128. return str;
  129. });
  130. var extraCode = "";
  131. while(idxReplacement >= 0) {
  132. extraCode += replacements[idxReplacement][2];
  133. idxReplacement--;
  134. }
  135. if(extraCode) {
  136. map.add(extraCode);
  137. }
  138. return map;
  139. }
  140. _replacementToSourceNode(oldNode, newString) {
  141. var map = oldNode.toStringWithSourceMap({
  142. file: "?"
  143. }).map;
  144. var original = new SourceMapConsumer(map.toJSON()).originalPositionFor({
  145. line: 1,
  146. column: 0
  147. });
  148. if(original) {
  149. return new SourceNode(original.line, original.column, original.source, newString);
  150. } else {
  151. return newString;
  152. }
  153. }
  154. _splitSourceNode(node, position) {
  155. if(typeof node === "string") {
  156. if(node.length <= position) return position - node.length;
  157. return position <= 0 ? ["", node] : [node.substr(0, position), node.substr(position)];
  158. } else {
  159. for(var i = 0; i < node.children.length; i++) {
  160. position = this._splitSourceNode(node.children[i], position);
  161. if(Array.isArray(position)) {
  162. var leftNode = new SourceNode(
  163. node.line,
  164. node.column,
  165. node.source,
  166. node.children.slice(0, i).concat([position[0]]),
  167. node.name
  168. );
  169. var rightNode = new SourceNode(
  170. node.line,
  171. node.column,
  172. node.source, [position[1]].concat(node.children.slice(i + 1)),
  173. node.name
  174. );
  175. leftNode.sourceContents = node.sourceContents;
  176. return [leftNode, rightNode];
  177. }
  178. }
  179. return position;
  180. }
  181. }
  182. _splitString(str, position) {
  183. return position <= 0 ? ["", str] : [str.substr(0, position), str.substr(position)];
  184. }
  185. }
  186. require("./SourceAndMapMixin")(ReplaceSource.prototype);
  187. module.exports = ReplaceSource;