1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
|
8 | const streamChunks = require("./helpers/streamChunks");
|
9 | const Source = require("./Source");
|
10 |
|
11 |
|
12 | const hasStableSort =
|
13 | typeof process === "object" &&
|
14 | process.versions &&
|
15 | typeof process.versions.v8 === "string" &&
|
16 | !/^[0-6]\./.test(process.versions.v8);
|
17 |
|
18 |
|
19 | const MAX_SOURCE_POSITION = 0x20000000;
|
20 |
|
21 | const SPLIT_LINES_REGEX = /[^\n]+\n?|\n/g;
|
22 |
|
23 | class Replacement {
|
24 | constructor(start, end, content, name) {
|
25 | this.start = start;
|
26 | this.end = end;
|
27 | this.content = content;
|
28 | this.name = name;
|
29 | if (!hasStableSort) {
|
30 | this.index = -1;
|
31 | }
|
32 | }
|
33 | }
|
34 |
|
35 | class ReplaceSource extends Source {
|
36 | constructor(source, name) {
|
37 | super();
|
38 | this._source = source;
|
39 | this._name = name;
|
40 |
|
41 | this._replacements = [];
|
42 | this._isSorted = true;
|
43 | }
|
44 |
|
45 | getName() {
|
46 | return this._name;
|
47 | }
|
48 |
|
49 | getReplacements() {
|
50 | this._sortReplacements();
|
51 | return this._replacements;
|
52 | }
|
53 |
|
54 | replace(start, end, newValue, name) {
|
55 | if (typeof newValue !== "string")
|
56 | throw new Error(
|
57 | "insertion must be a string, but is a " + typeof newValue
|
58 | );
|
59 | this._replacements.push(new Replacement(start, end, newValue, name));
|
60 | this._isSorted = false;
|
61 | }
|
62 |
|
63 | insert(pos, newValue, name) {
|
64 | if (typeof newValue !== "string")
|
65 | throw new Error(
|
66 | "insertion must be a string, but is a " +
|
67 | typeof newValue +
|
68 | ": " +
|
69 | newValue
|
70 | );
|
71 | this._replacements.push(new Replacement(pos, pos - 1, newValue, name));
|
72 | this._isSorted = false;
|
73 | }
|
74 |
|
75 | source() {
|
76 | if (this._replacements.length === 0) {
|
77 | return this._source.source();
|
78 | }
|
79 | let current = this._source.source();
|
80 | let pos = 0;
|
81 | const result = [];
|
82 |
|
83 | this._sortReplacements();
|
84 | for (const replacement of this._replacements) {
|
85 | const start = Math.floor(replacement.start);
|
86 | const end = Math.floor(replacement.end + 1);
|
87 | if (pos < start) {
|
88 | const offset = start - pos;
|
89 | result.push(current.slice(0, offset));
|
90 | current = current.slice(offset);
|
91 | pos = start;
|
92 | }
|
93 | result.push(replacement.content);
|
94 | if (pos < end) {
|
95 | const offset = end - pos;
|
96 | current = current.slice(offset);
|
97 | pos = end;
|
98 | }
|
99 | }
|
100 | result.push(current);
|
101 | return result.join("");
|
102 | }
|
103 |
|
104 | map(options) {
|
105 | if (this._replacements.length === 0) {
|
106 | return this._source.map(options);
|
107 | }
|
108 | return getMap(this, options);
|
109 | }
|
110 |
|
111 | sourceAndMap(options) {
|
112 | if (this._replacements.length === 0) {
|
113 | return this._source.sourceAndMap(options);
|
114 | }
|
115 | return getSourceAndMap(this, options);
|
116 | }
|
117 |
|
118 | original() {
|
119 | return this._source;
|
120 | }
|
121 |
|
122 | _sortReplacements() {
|
123 | if (this._isSorted) return;
|
124 | if (hasStableSort) {
|
125 | this._replacements.sort(function (a, b) {
|
126 | const diff1 = a.start - b.start;
|
127 | if (diff1 !== 0) return diff1;
|
128 | const diff2 = a.end - b.end;
|
129 | if (diff2 !== 0) return diff2;
|
130 | return 0;
|
131 | });
|
132 | } else {
|
133 | this._replacements.forEach((repl, i) => (repl.index = i));
|
134 | this._replacements.sort(function (a, b) {
|
135 | const diff1 = a.start - b.start;
|
136 | if (diff1 !== 0) return diff1;
|
137 | const diff2 = a.end - b.end;
|
138 | if (diff2 !== 0) return diff2;
|
139 | return a.index - b.index;
|
140 | });
|
141 | }
|
142 | this._isSorted = true;
|
143 | }
|
144 |
|
145 | streamChunks(options, onChunk, onSource, onName) {
|
146 | this._sortReplacements();
|
147 | const repls = this._replacements;
|
148 | let pos = 0;
|
149 | let i = 0;
|
150 | let replacmentEnd = -1;
|
151 | let nextReplacement =
|
152 | i < repls.length ? Math.floor(repls[i].start) : MAX_SOURCE_POSITION;
|
153 | let generatedLineOffset = 0;
|
154 | let generatedColumnOffset = 0;
|
155 | let generatedColumnOffsetLine = 0;
|
156 | const sourceContents = [];
|
157 | const nameMapping = new Map();
|
158 | const nameIndexMapping = [];
|
159 | const checkOriginalContent = (sourceIndex, line, column, expectedChunk) => {
|
160 | let content =
|
161 | sourceIndex < sourceContents.length
|
162 | ? sourceContents[sourceIndex]
|
163 | : undefined;
|
164 | if (content === undefined) return false;
|
165 | if (typeof content === "string") {
|
166 | content = content.match(SPLIT_LINES_REGEX) || [];
|
167 | sourceContents[sourceIndex] = content;
|
168 | }
|
169 | const contentLine = line <= content.length ? content[line - 1] : null;
|
170 | if (contentLine === null) return false;
|
171 | return (
|
172 | contentLine.slice(column, column + expectedChunk.length) ===
|
173 | expectedChunk
|
174 | );
|
175 | };
|
176 | let { generatedLine, generatedColumn } = streamChunks(
|
177 | this._source,
|
178 | Object.assign({}, options, { finalSource: false }),
|
179 | (
|
180 | chunk,
|
181 | generatedLine,
|
182 | generatedColumn,
|
183 | sourceIndex,
|
184 | originalLine,
|
185 | originalColumn,
|
186 | nameIndex
|
187 | ) => {
|
188 | let chunkPos = 0;
|
189 | let endPos = pos + chunk.length;
|
190 |
|
191 |
|
192 | if (replacmentEnd > pos) {
|
193 |
|
194 | if (replacmentEnd >= endPos) {
|
195 | const line = generatedLine + generatedLineOffset;
|
196 | if (chunk.endsWith("\n")) {
|
197 | generatedLineOffset--;
|
198 | if (generatedColumnOffsetLine === line) {
|
199 |
|
200 | generatedColumnOffset += generatedColumn;
|
201 | }
|
202 | } else if (generatedColumnOffsetLine === line) {
|
203 | generatedColumnOffset -= chunk.length;
|
204 | } else {
|
205 | generatedColumnOffset = -chunk.length;
|
206 | generatedColumnOffsetLine = line;
|
207 | }
|
208 | pos = endPos;
|
209 | return;
|
210 | }
|
211 |
|
212 |
|
213 | chunkPos = replacmentEnd - pos;
|
214 | if (
|
215 | checkOriginalContent(
|
216 | sourceIndex,
|
217 | originalLine,
|
218 | originalColumn,
|
219 | chunk.slice(0, chunkPos)
|
220 | )
|
221 | ) {
|
222 | originalColumn += chunkPos;
|
223 | }
|
224 | pos += chunkPos;
|
225 | const line = generatedLine + generatedLineOffset;
|
226 | if (generatedColumnOffsetLine === line) {
|
227 | generatedColumnOffset -= chunkPos;
|
228 | } else {
|
229 | generatedColumnOffset = -chunkPos;
|
230 | generatedColumnOffsetLine = line;
|
231 | }
|
232 | generatedColumn += chunkPos;
|
233 | }
|
234 |
|
235 |
|
236 | if (nextReplacement < endPos) {
|
237 | do {
|
238 | let line = generatedLine + generatedLineOffset;
|
239 | if (nextReplacement > pos) {
|
240 |
|
241 | const offset = nextReplacement - pos;
|
242 | const chunkSlice = chunk.slice(chunkPos, chunkPos + offset);
|
243 | onChunk(
|
244 | chunkSlice,
|
245 | line,
|
246 | generatedColumn +
|
247 | (line === generatedColumnOffsetLine
|
248 | ? generatedColumnOffset
|
249 | : 0),
|
250 | sourceIndex,
|
251 | originalLine,
|
252 | originalColumn,
|
253 | nameIndex < 0 ? -1 : nameIndexMapping[nameIndex]
|
254 | );
|
255 | generatedColumn += offset;
|
256 | chunkPos += offset;
|
257 | pos = nextReplacement;
|
258 | if (
|
259 | checkOriginalContent(
|
260 | sourceIndex,
|
261 | originalLine,
|
262 | originalColumn,
|
263 | chunkSlice
|
264 | )
|
265 | ) {
|
266 | originalColumn += chunkSlice.length;
|
267 | }
|
268 | }
|
269 |
|
270 |
|
271 | const regexp = /[^\n]+\n?|\n/g;
|
272 | const { content, name } = repls[i];
|
273 | let match = regexp.exec(content);
|
274 | let replacementNameIndex = nameIndex;
|
275 | if (sourceIndex >= 0 && name) {
|
276 | let globalIndex = nameMapping.get(name);
|
277 | if (globalIndex === undefined) {
|
278 | globalIndex = nameMapping.size;
|
279 | nameMapping.set(name, globalIndex);
|
280 | onName(globalIndex, name);
|
281 | }
|
282 | replacementNameIndex = globalIndex;
|
283 | }
|
284 | while (match !== null) {
|
285 | const contentLine = match[0];
|
286 | onChunk(
|
287 | contentLine,
|
288 | line,
|
289 | generatedColumn +
|
290 | (line === generatedColumnOffsetLine
|
291 | ? generatedColumnOffset
|
292 | : 0),
|
293 | sourceIndex,
|
294 | originalLine,
|
295 | originalColumn,
|
296 | replacementNameIndex
|
297 | );
|
298 |
|
299 |
|
300 | replacementNameIndex = -1;
|
301 |
|
302 | match = regexp.exec(content);
|
303 | if (match === null && !contentLine.endsWith("\n")) {
|
304 | if (generatedColumnOffsetLine === line) {
|
305 | generatedColumnOffset += contentLine.length;
|
306 | } else {
|
307 | generatedColumnOffset = contentLine.length;
|
308 | generatedColumnOffsetLine = line;
|
309 | }
|
310 | } else {
|
311 | generatedLineOffset++;
|
312 | line++;
|
313 | generatedColumnOffset = -generatedColumn;
|
314 | generatedColumnOffsetLine = line;
|
315 | }
|
316 | }
|
317 |
|
318 |
|
319 | replacmentEnd = Math.max(
|
320 | replacmentEnd,
|
321 | Math.floor(repls[i].end + 1)
|
322 | );
|
323 |
|
324 |
|
325 | i++;
|
326 | nextReplacement =
|
327 | i < repls.length
|
328 | ? Math.floor(repls[i].start)
|
329 | : MAX_SOURCE_POSITION;
|
330 |
|
331 |
|
332 | const offset = chunk.length - endPos + replacmentEnd - chunkPos;
|
333 | if (offset > 0) {
|
334 |
|
335 | if (replacmentEnd >= endPos) {
|
336 | let line = generatedLine + generatedLineOffset;
|
337 | if (chunk.endsWith("\n")) {
|
338 | generatedLineOffset--;
|
339 | if (generatedColumnOffsetLine === line) {
|
340 |
|
341 | generatedColumnOffset += generatedColumn;
|
342 | }
|
343 | } else if (generatedColumnOffsetLine === line) {
|
344 | generatedColumnOffset -= chunk.length - chunkPos;
|
345 | } else {
|
346 | generatedColumnOffset = chunkPos - chunk.length;
|
347 | generatedColumnOffsetLine = line;
|
348 | }
|
349 | pos = endPos;
|
350 | return;
|
351 | }
|
352 |
|
353 |
|
354 | const line = generatedLine + generatedLineOffset;
|
355 | if (
|
356 | checkOriginalContent(
|
357 | sourceIndex,
|
358 | originalLine,
|
359 | originalColumn,
|
360 | chunk.slice(chunkPos, chunkPos + offset)
|
361 | )
|
362 | ) {
|
363 | originalColumn += offset;
|
364 | }
|
365 | chunkPos += offset;
|
366 | pos += offset;
|
367 | if (generatedColumnOffsetLine === line) {
|
368 | generatedColumnOffset -= offset;
|
369 | } else {
|
370 | generatedColumnOffset = -offset;
|
371 | generatedColumnOffsetLine = line;
|
372 | }
|
373 | generatedColumn += offset;
|
374 | }
|
375 | } while (nextReplacement < endPos);
|
376 | }
|
377 |
|
378 |
|
379 | if (chunkPos < chunk.length) {
|
380 | const chunkSlice = chunkPos === 0 ? chunk : chunk.slice(chunkPos);
|
381 | const line = generatedLine + generatedLineOffset;
|
382 | onChunk(
|
383 | chunkSlice,
|
384 | line,
|
385 | generatedColumn +
|
386 | (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
|
387 | sourceIndex,
|
388 | originalLine,
|
389 | originalColumn,
|
390 | nameIndex < 0 ? -1 : nameIndexMapping[nameIndex]
|
391 | );
|
392 | }
|
393 | pos = endPos;
|
394 | },
|
395 | (sourceIndex, source, sourceContent) => {
|
396 | while (sourceContents.length < sourceIndex)
|
397 | sourceContents.push(undefined);
|
398 | sourceContents[sourceIndex] = sourceContent;
|
399 | onSource(sourceIndex, source, sourceContent);
|
400 | },
|
401 | (nameIndex, name) => {
|
402 | let globalIndex = nameMapping.get(name);
|
403 | if (globalIndex === undefined) {
|
404 | globalIndex = nameMapping.size;
|
405 | nameMapping.set(name, globalIndex);
|
406 | onName(globalIndex, name);
|
407 | }
|
408 | nameIndexMapping[nameIndex] = globalIndex;
|
409 | }
|
410 | );
|
411 |
|
412 |
|
413 | let remainer = "";
|
414 | for (; i < repls.length; i++) {
|
415 | remainer += repls[i].content;
|
416 | }
|
417 |
|
418 |
|
419 | let line = generatedLine + generatedLineOffset;
|
420 | const regexp = /[^\n]+\n?|\n/g;
|
421 | let match = regexp.exec(remainer);
|
422 | while (match !== null) {
|
423 | const contentLine = match[0];
|
424 | onChunk(
|
425 | contentLine,
|
426 | line,
|
427 | generatedColumn +
|
428 | (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
|
429 | -1,
|
430 | -1,
|
431 | -1,
|
432 | -1
|
433 | );
|
434 |
|
435 | match = regexp.exec(remainer);
|
436 | if (match === null && !contentLine.endsWith("\n")) {
|
437 | if (generatedColumnOffsetLine === line) {
|
438 | generatedColumnOffset += contentLine.length;
|
439 | } else {
|
440 | generatedColumnOffset = contentLine.length;
|
441 | generatedColumnOffsetLine = line;
|
442 | }
|
443 | } else {
|
444 | generatedLineOffset++;
|
445 | line++;
|
446 | generatedColumnOffset = -generatedColumn;
|
447 | generatedColumnOffsetLine = line;
|
448 | }
|
449 | }
|
450 |
|
451 | return {
|
452 | generatedLine: line,
|
453 | generatedColumn:
|
454 | generatedColumn +
|
455 | (line === generatedColumnOffsetLine ? generatedColumnOffset : 0)
|
456 | };
|
457 | }
|
458 |
|
459 | updateHash(hash) {
|
460 | this._sortReplacements();
|
461 | hash.update("ReplaceSource");
|
462 | this._source.updateHash(hash);
|
463 | hash.update(this._name || "");
|
464 | for (const repl of this._replacements) {
|
465 | hash.update(`${repl.start}${repl.end}${repl.content}${repl.name}`);
|
466 | }
|
467 | }
|
468 | }
|
469 |
|
470 | module.exports = ReplaceSource;
|