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 + 1, 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, 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);
|
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 | } else if (generatedColumnOffsetLine === line) {
|
199 | generatedColumnOffset -= chunk.length;
|
200 | } else {
|
201 | generatedColumnOffset = -chunk.length;
|
202 | generatedColumnOffsetLine = line;
|
203 | }
|
204 | pos = endPos;
|
205 | return;
|
206 | }
|
207 |
|
208 |
|
209 | chunkPos = replacmentEnd - pos;
|
210 | if (
|
211 | checkOriginalContent(
|
212 | sourceIndex,
|
213 | originalLine,
|
214 | originalColumn,
|
215 | chunk.slice(0, chunkPos)
|
216 | )
|
217 | ) {
|
218 | originalColumn += chunkPos;
|
219 | }
|
220 | pos += chunkPos;
|
221 | const line = generatedLine + generatedLineOffset;
|
222 | if (generatedColumnOffsetLine === line) {
|
223 | generatedColumnOffset -= chunkPos;
|
224 | } else {
|
225 | generatedColumnOffset = -chunkPos;
|
226 | generatedColumnOffsetLine = line;
|
227 | }
|
228 | generatedColumn += chunkPos;
|
229 | }
|
230 |
|
231 |
|
232 | if (nextReplacement < endPos) {
|
233 | do {
|
234 | let line = generatedLine + generatedLineOffset;
|
235 | if (nextReplacement > pos) {
|
236 |
|
237 | const offset = nextReplacement - pos;
|
238 | const chunkSlice = chunk.slice(chunkPos, chunkPos + offset);
|
239 | onChunk(
|
240 | chunkSlice,
|
241 | line,
|
242 | generatedColumn +
|
243 | (line === generatedColumnOffsetLine
|
244 | ? generatedColumnOffset
|
245 | : 0),
|
246 | sourceIndex,
|
247 | originalLine,
|
248 | originalColumn,
|
249 | nameIndex < 0 ? -1 : nameIndexMapping[nameIndex]
|
250 | );
|
251 | generatedColumn += offset;
|
252 | chunkPos += offset;
|
253 | pos = nextReplacement;
|
254 | if (
|
255 | checkOriginalContent(
|
256 | sourceIndex,
|
257 | originalLine,
|
258 | originalColumn,
|
259 | chunkSlice
|
260 | )
|
261 | ) {
|
262 | originalColumn += chunkSlice.length;
|
263 | }
|
264 | }
|
265 |
|
266 |
|
267 | const regexp = /[^\n]+\n?|\n/g;
|
268 | const { content, name } = repls[i];
|
269 | let match = regexp.exec(content);
|
270 | let replacementNameIndex = nameIndex;
|
271 | if (name) {
|
272 | let globalIndex = nameMapping.get(name);
|
273 | if (globalIndex === undefined) {
|
274 | globalIndex = nameMapping.size;
|
275 | nameMapping.set(name, globalIndex);
|
276 | onName(globalIndex, name);
|
277 | }
|
278 | replacementNameIndex = globalIndex;
|
279 | }
|
280 | while (match !== null) {
|
281 | const contentLine = match[0];
|
282 | onChunk(
|
283 | contentLine,
|
284 | line,
|
285 | generatedColumn +
|
286 | (line === generatedColumnOffsetLine
|
287 | ? generatedColumnOffset
|
288 | : 0),
|
289 | sourceIndex,
|
290 | originalLine,
|
291 | originalColumn,
|
292 | replacementNameIndex
|
293 | );
|
294 |
|
295 |
|
296 | replacementNameIndex = -1;
|
297 |
|
298 | match = regexp.exec(content);
|
299 | if (match === null && !contentLine.endsWith("\n")) {
|
300 | if (generatedColumnOffsetLine === line) {
|
301 | generatedColumnOffset += contentLine.length;
|
302 | } else {
|
303 | generatedColumnOffset = contentLine.length;
|
304 | generatedColumnOffsetLine = line;
|
305 | }
|
306 | } else {
|
307 | generatedLineOffset++;
|
308 | line++;
|
309 | generatedColumnOffset = -generatedColumn;
|
310 | generatedColumnOffsetLine = line;
|
311 | }
|
312 | }
|
313 |
|
314 |
|
315 | replacmentEnd = Math.max(replacmentEnd, Math.floor(repls[i].end));
|
316 |
|
317 |
|
318 | i++;
|
319 | nextReplacement =
|
320 | i < repls.length
|
321 | ? Math.floor(repls[i].start)
|
322 | : MAX_SOURCE_POSITION;
|
323 |
|
324 |
|
325 | const offset = chunk.length - endPos + replacmentEnd - chunkPos;
|
326 | if (offset > 0) {
|
327 |
|
328 | if (replacmentEnd >= endPos) {
|
329 | let line = generatedLine + generatedLineOffset;
|
330 | if (chunk.endsWith("\n")) {
|
331 | generatedLineOffset--;
|
332 | if (generatedColumnOffsetLine === line) {
|
333 | generatedColumnOffset += generatedColumn;
|
334 | } else {
|
335 | generatedColumnOffset = generatedColumn;
|
336 | generatedColumnOffsetLine = line;
|
337 | }
|
338 | } else if (generatedColumnOffsetLine === line) {
|
339 | generatedColumnOffset -= chunk.length - chunkPos;
|
340 | } else {
|
341 | generatedColumnOffset = chunkPos - chunk.length;
|
342 | generatedColumnOffsetLine = line;
|
343 | }
|
344 | pos = endPos;
|
345 | return;
|
346 | }
|
347 |
|
348 |
|
349 | const line = generatedLine + generatedLineOffset;
|
350 | if (
|
351 | checkOriginalContent(
|
352 | sourceIndex,
|
353 | originalLine,
|
354 | originalColumn,
|
355 | chunk.slice(chunkPos, chunkPos + offset)
|
356 | )
|
357 | ) {
|
358 | originalColumn += offset;
|
359 | }
|
360 | chunkPos += offset;
|
361 | pos += offset;
|
362 | if (generatedColumnOffsetLine === line) {
|
363 | generatedColumnOffset -= offset;
|
364 | } else {
|
365 | generatedColumnOffset = -offset;
|
366 | generatedColumnOffsetLine = line;
|
367 | }
|
368 | generatedColumn += offset;
|
369 | }
|
370 | } while (nextReplacement < endPos);
|
371 | }
|
372 |
|
373 |
|
374 | if (chunkPos < chunk.length) {
|
375 | const chunkSlice = chunkPos === 0 ? chunk : chunk.slice(chunkPos);
|
376 | const line = generatedLine + generatedLineOffset;
|
377 | onChunk(
|
378 | chunkSlice,
|
379 | line,
|
380 | generatedColumn +
|
381 | (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
|
382 | sourceIndex,
|
383 | originalLine,
|
384 | originalColumn,
|
385 | nameIndex < 0 ? -1 : nameIndexMapping[nameIndex]
|
386 | );
|
387 | }
|
388 | pos = endPos;
|
389 | },
|
390 | (sourceIndex, source, sourceContent) => {
|
391 | while (sourceContents.length < sourceIndex)
|
392 | sourceContents.push(undefined);
|
393 | sourceContents[sourceIndex] = sourceContent;
|
394 | onSource(sourceIndex, source, sourceContent);
|
395 | },
|
396 | (nameIndex, name) => {
|
397 | let globalIndex = nameMapping.get(name);
|
398 | if (globalIndex === undefined) {
|
399 | globalIndex = nameMapping.size;
|
400 | nameMapping.set(name, globalIndex);
|
401 | onName(globalIndex, name);
|
402 | }
|
403 | nameIndexMapping[nameIndex] = globalIndex;
|
404 | }
|
405 | );
|
406 |
|
407 |
|
408 | let remainer = "";
|
409 | for (; i < repls.length; i++) {
|
410 | remainer += repls[i].content;
|
411 | }
|
412 |
|
413 |
|
414 | let line = generatedLine + generatedLineOffset;
|
415 | const regexp = /[^\n]+\n?|\n/g;
|
416 | let match = regexp.exec(remainer);
|
417 | while (match !== null) {
|
418 | const contentLine = match[0];
|
419 | onChunk(
|
420 | contentLine,
|
421 | line,
|
422 | generatedColumn +
|
423 | (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
|
424 | -1,
|
425 | -1,
|
426 | -1,
|
427 | -1
|
428 | );
|
429 |
|
430 | match = regexp.exec(remainer);
|
431 | if (match === null && !contentLine.endsWith("\n")) {
|
432 | if (generatedColumnOffsetLine === line) {
|
433 | generatedColumnOffset += contentLine.length;
|
434 | } else {
|
435 | generatedColumnOffset = contentLine.length;
|
436 | generatedColumnOffsetLine = line;
|
437 | }
|
438 | } else {
|
439 | generatedLineOffset++;
|
440 | line++;
|
441 | generatedColumnOffset = -generatedColumn;
|
442 | generatedColumnOffsetLine = line;
|
443 | }
|
444 | }
|
445 |
|
446 | return {
|
447 | generatedLine: line,
|
448 | generatedColumn:
|
449 | generatedColumn +
|
450 | (line === generatedColumnOffsetLine ? generatedColumnOffset : 0)
|
451 | };
|
452 | }
|
453 |
|
454 | updateHash(hash) {
|
455 | this._sortReplacements();
|
456 | hash.update("ReplaceSource");
|
457 | this._source.updateHash(hash);
|
458 | hash.update(this._name || "");
|
459 | for (const repl of this._replacements) {
|
460 | hash.update(`${repl.start}`);
|
461 | hash.update(`${repl.end}`);
|
462 | hash.update(`${repl.content}`);
|
463 | hash.update(`${repl.insertIndex}`);
|
464 | hash.update(`${repl.name}`);
|
465 | }
|
466 | }
|
467 | }
|
468 |
|
469 | module.exports = ReplaceSource;
|