1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const Source = require("./Source");
|
8 | const { SourceNode } = require("source-map");
|
9 | const { getSourceAndMap, getMap, getNode, getListMap } = require("./helpers");
|
10 |
|
11 | class Replacement {
|
12 | constructor(start, end, content, insertIndex, name) {
|
13 | this.start = start;
|
14 | this.end = end;
|
15 | this.content = content;
|
16 | this.insertIndex = insertIndex;
|
17 | this.name = name;
|
18 | }
|
19 | }
|
20 |
|
21 | class ReplaceSource extends Source {
|
22 | constructor(source, name) {
|
23 | super();
|
24 | this._source = source;
|
25 | this._name = name;
|
26 |
|
27 | this._replacements = [];
|
28 | this._isSorted = true;
|
29 | }
|
30 |
|
31 | getName() {
|
32 | return this._name;
|
33 | }
|
34 |
|
35 | getReplacements() {
|
36 | const replacements = Array.from(this._replacements);
|
37 | replacements.sort((a, b) => {
|
38 | return a.insertIndex - b.insertIndex;
|
39 | });
|
40 | return replacements;
|
41 | }
|
42 |
|
43 | replace(start, end, newValue, name) {
|
44 | if (typeof newValue !== "string")
|
45 | throw new Error(
|
46 | "insertion must be a string, but is a " + typeof newValue
|
47 | );
|
48 | this._replacements.push(
|
49 | new Replacement(start, end, newValue, this._replacements.length, name)
|
50 | );
|
51 | this._isSorted = false;
|
52 | }
|
53 |
|
54 | insert(pos, newValue, name) {
|
55 | if (typeof newValue !== "string")
|
56 | throw new Error(
|
57 | "insertion must be a string, but is a " +
|
58 | typeof newValue +
|
59 | ": " +
|
60 | newValue
|
61 | );
|
62 | this._replacements.push(
|
63 | new Replacement(pos, pos - 1, newValue, this._replacements.length, name)
|
64 | );
|
65 | this._isSorted = false;
|
66 | }
|
67 |
|
68 | source() {
|
69 | return this._replaceString(this._source.source());
|
70 | }
|
71 |
|
72 | map(options) {
|
73 | if (this._replacements.length === 0) {
|
74 | return this._source.map(options);
|
75 | }
|
76 | return getMap(this, options);
|
77 | }
|
78 |
|
79 | sourceAndMap(options) {
|
80 | if (this._replacements.length === 0) {
|
81 | return this._source.sourceAndMap(options);
|
82 | }
|
83 | return getSourceAndMap(this, options);
|
84 | }
|
85 |
|
86 | original() {
|
87 | return this._source;
|
88 | }
|
89 |
|
90 | _sortReplacements() {
|
91 | if (this._isSorted) return;
|
92 | this._replacements.sort(function (a, b) {
|
93 | const diff1 = b.end - a.end;
|
94 | if (diff1 !== 0) return diff1;
|
95 | const diff2 = b.start - a.start;
|
96 | if (diff2 !== 0) return diff2;
|
97 | return b.insertIndex - a.insertIndex;
|
98 | });
|
99 | this._isSorted = true;
|
100 | }
|
101 |
|
102 | _replaceString(str) {
|
103 | if (typeof str !== "string")
|
104 | throw new Error(
|
105 | "str must be a string, but is a " + typeof str + ": " + str
|
106 | );
|
107 | this._sortReplacements();
|
108 | const result = [str];
|
109 | this._replacements.forEach(function (repl) {
|
110 | const remSource = result.pop();
|
111 | const splitted1 = this._splitString(remSource, Math.floor(repl.end + 1));
|
112 | const splitted2 = this._splitString(splitted1[0], Math.floor(repl.start));
|
113 | result.push(splitted1[1], repl.content, splitted2[0]);
|
114 | }, this);
|
115 |
|
116 |
|
117 | let resultStr = "";
|
118 | for (let i = result.length - 1; i >= 0; --i) {
|
119 | resultStr += result[i];
|
120 | }
|
121 | return resultStr;
|
122 | }
|
123 |
|
124 | node(options) {
|
125 | const node = getNode(this._source, options);
|
126 | if (this._replacements.length === 0) {
|
127 | return node;
|
128 | }
|
129 | this._sortReplacements();
|
130 | const replace = new ReplacementEnumerator(this._replacements);
|
131 | const output = [];
|
132 | let position = 0;
|
133 | const sources = Object.create(null);
|
134 | const sourcesInLines = Object.create(null);
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | const result = new SourceNode();
|
140 |
|
141 |
|
142 |
|
143 | node.walkSourceContents(function (sourceFile, sourceContent) {
|
144 | result.setSourceContent(sourceFile, sourceContent);
|
145 | sources["$" + sourceFile] = sourceContent;
|
146 | });
|
147 |
|
148 | const replaceInStringNode = this._replaceInStringNode.bind(
|
149 | this,
|
150 | output,
|
151 | replace,
|
152 | function getOriginalSource(mapping) {
|
153 | const key = "$" + mapping.source;
|
154 | let lines = sourcesInLines[key];
|
155 | if (!lines) {
|
156 | const source = sources[key];
|
157 | if (!source) return null;
|
158 | lines = source.split("\n").map(function (line) {
|
159 | return line + "\n";
|
160 | });
|
161 | sourcesInLines[key] = lines;
|
162 | }
|
163 |
|
164 | if (mapping.line > lines.length) return null;
|
165 | const line = lines[mapping.line - 1];
|
166 | return line.substr(mapping.column);
|
167 | }
|
168 | );
|
169 |
|
170 | node.walk(function (chunk, mapping) {
|
171 | position = replaceInStringNode(chunk, position, mapping);
|
172 | });
|
173 |
|
174 |
|
175 |
|
176 | const remaining = replace.footer();
|
177 | if (remaining) {
|
178 | output.push(remaining);
|
179 | }
|
180 |
|
181 | result.add(output);
|
182 |
|
183 | return result;
|
184 | }
|
185 |
|
186 | listMap(options) {
|
187 | let map = getListMap(this._source, options);
|
188 | this._sortReplacements();
|
189 | let currentIndex = 0;
|
190 | const replacements = this._replacements;
|
191 | let idxReplacement = replacements.length - 1;
|
192 | let removeChars = 0;
|
193 | map = map.mapGeneratedCode(function (str) {
|
194 | const newCurrentIndex = currentIndex + str.length;
|
195 | if (removeChars > str.length) {
|
196 | removeChars -= str.length;
|
197 | str = "";
|
198 | } else {
|
199 | if (removeChars > 0) {
|
200 | str = str.substr(removeChars);
|
201 | currentIndex += removeChars;
|
202 | removeChars = 0;
|
203 | }
|
204 | let finalStr = "";
|
205 | while (
|
206 | idxReplacement >= 0 &&
|
207 | replacements[idxReplacement].start < newCurrentIndex
|
208 | ) {
|
209 | const repl = replacements[idxReplacement];
|
210 | const start = Math.floor(repl.start);
|
211 | const end = Math.floor(repl.end + 1);
|
212 | const before = str.substr(0, Math.max(0, start - currentIndex));
|
213 | if (end <= newCurrentIndex) {
|
214 | const after = str.substr(Math.max(0, end - currentIndex));
|
215 | finalStr += before + repl.content;
|
216 | str = after;
|
217 | currentIndex = Math.max(currentIndex, end);
|
218 | } else {
|
219 | finalStr += before + repl.content;
|
220 | str = "";
|
221 | removeChars = end - newCurrentIndex;
|
222 | }
|
223 | idxReplacement--;
|
224 | }
|
225 | str = finalStr + str;
|
226 | }
|
227 | currentIndex = newCurrentIndex;
|
228 | return str;
|
229 | });
|
230 | let extraCode = "";
|
231 | while (idxReplacement >= 0) {
|
232 | extraCode += replacements[idxReplacement].content;
|
233 | idxReplacement--;
|
234 | }
|
235 | if (extraCode) {
|
236 | map.add(extraCode);
|
237 | }
|
238 | return map;
|
239 | }
|
240 |
|
241 | _splitString(str, position) {
|
242 | return position <= 0
|
243 | ? ["", str]
|
244 | : [str.substr(0, position), str.substr(position)];
|
245 | }
|
246 |
|
247 | _replaceInStringNode(
|
248 | output,
|
249 | replace,
|
250 | getOriginalSource,
|
251 | node,
|
252 | position,
|
253 | mapping
|
254 | ) {
|
255 | let original = undefined;
|
256 |
|
257 | do {
|
258 | let splitPosition = replace.position - position;
|
259 |
|
260 |
|
261 | if (splitPosition < 0) {
|
262 | splitPosition = 0;
|
263 | }
|
264 | if (splitPosition >= node.length || replace.done) {
|
265 | if (replace.emit) {
|
266 | const nodeEnd = new SourceNode(
|
267 | mapping.line,
|
268 | mapping.column,
|
269 | mapping.source,
|
270 | node,
|
271 | mapping.name
|
272 | );
|
273 | output.push(nodeEnd);
|
274 | }
|
275 | return position + node.length;
|
276 | }
|
277 |
|
278 | const originalColumn = mapping.column;
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 | let nodePart;
|
285 | if (splitPosition > 0) {
|
286 | nodePart = node.slice(0, splitPosition);
|
287 | if (original === undefined) {
|
288 | original = getOriginalSource(mapping);
|
289 | }
|
290 | if (
|
291 | original &&
|
292 | original.length >= splitPosition &&
|
293 | original.startsWith(nodePart)
|
294 | ) {
|
295 | mapping.column += splitPosition;
|
296 | original = original.substr(splitPosition);
|
297 | }
|
298 | }
|
299 |
|
300 | const emit = replace.next();
|
301 | if (!emit) {
|
302 |
|
303 |
|
304 | if (splitPosition > 0) {
|
305 | const nodeStart = new SourceNode(
|
306 | mapping.line,
|
307 | originalColumn,
|
308 | mapping.source,
|
309 | nodePart,
|
310 | mapping.name
|
311 | );
|
312 | output.push(nodeStart);
|
313 | }
|
314 |
|
315 |
|
316 | if (replace.value) {
|
317 | output.push(
|
318 | new SourceNode(
|
319 | mapping.line,
|
320 | mapping.column,
|
321 | mapping.source,
|
322 | replace.value,
|
323 | mapping.name || replace.name
|
324 | )
|
325 | );
|
326 | }
|
327 | }
|
328 |
|
329 |
|
330 | node = node.substr(splitPosition);
|
331 | position += splitPosition;
|
332 |
|
333 | } while (true);
|
334 | }
|
335 |
|
336 | updateHash(hash) {
|
337 | this._sortReplacements();
|
338 | hash.update("ReplaceSource");
|
339 | this._source.updateHash(hash);
|
340 | hash.update(this._name || "");
|
341 | for (const repl of this._replacements) {
|
342 | hash.update(`${repl.start}`);
|
343 | hash.update(`${repl.end}`);
|
344 | hash.update(`${repl.content}`);
|
345 | hash.update(`${repl.insertIndex}`);
|
346 | hash.update(`${repl.name}`);
|
347 | }
|
348 | }
|
349 | }
|
350 |
|
351 | class ReplacementEnumerator {
|
352 | |
353 |
|
354 |
|
355 | constructor(replacements) {
|
356 | this.replacements = replacements || [];
|
357 | this.index = this.replacements.length;
|
358 | this.done = false;
|
359 | this.emit = false;
|
360 |
|
361 | this.next();
|
362 | }
|
363 |
|
364 | next() {
|
365 | if (this.done) return true;
|
366 | if (this.emit) {
|
367 |
|
368 | const repl = this.replacements[this.index];
|
369 | const end = Math.floor(repl.end + 1);
|
370 | this.position = end;
|
371 | this.value = repl.content;
|
372 | this.name = repl.name;
|
373 | } else {
|
374 |
|
375 | this.index--;
|
376 | if (this.index < 0) {
|
377 | this.done = true;
|
378 | } else {
|
379 | const nextRepl = this.replacements[this.index];
|
380 | const start = Math.floor(nextRepl.start);
|
381 | this.position = start;
|
382 | }
|
383 | }
|
384 | if (this.position < 0) this.position = 0;
|
385 | this.emit = !this.emit;
|
386 | return this.emit;
|
387 | }
|
388 |
|
389 | footer() {
|
390 | if (!this.done && !this.emit) this.next();
|
391 | if (this.done) {
|
392 | return [];
|
393 | } else {
|
394 | let resultStr = "";
|
395 | for (let i = this.index; i >= 0; i--) {
|
396 | const repl = this.replacements[i];
|
397 |
|
398 |
|
399 | resultStr += repl.content;
|
400 | }
|
401 | return resultStr;
|
402 | }
|
403 | }
|
404 | }
|
405 |
|
406 | module.exports = ReplaceSource;
|