source: frontend/node_modules/webpack-sources/lib/ReplaceSource.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 16.1 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const Source = require("./Source");
9const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
10const splitIntoLines = require("./helpers/splitIntoLines");
11const streamChunks = require("./helpers/streamChunks");
12
13/** @typedef {import("./Source").HashLike} HashLike */
14/** @typedef {import("./Source").MapOptions} MapOptions */
15/** @typedef {import("./Source").RawSourceMap} RawSourceMap */
16/** @typedef {import("./Source").SourceAndMap} SourceAndMap */
17/** @typedef {import("./Source").SourceValue} SourceValue */
18/** @typedef {import("./helpers/getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */
19/** @typedef {import("./helpers/streamChunks").OnChunk} OnChunk */
20/** @typedef {import("./helpers/streamChunks").OnName} OnName */
21/** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
22/** @typedef {import("./helpers/streamChunks").Options} Options */
23
24// since v8 7.0, Array.prototype.sort is stable
25const hasStableSort =
26 typeof process === "object" &&
27 process.versions &&
28 typeof process.versions.v8 === "string" &&
29 !/^[0-6]\./.test(process.versions.v8);
30
31// This is larger than max string length
32const MAX_SOURCE_POSITION = 0x20000000;
33
34/**
35 * Stable comparator hoisted to module scope so each `_sortReplacements()`
36 * call doesn't allocate a fresh closure.
37 * @param {Replacement} a a
38 * @param {Replacement} b b
39 * @returns {number} order
40 */
41const compareStable = (a, b) => {
42 const diff1 = a.start - b.start;
43 if (diff1 !== 0) return diff1;
44 const diff2 = a.end - b.end;
45 if (diff2 !== 0) return diff2;
46 return 0;
47};
48
49/**
50 * Index-stabilising comparator for v8 < 7.0 (pre-stable Array.prototype.sort).
51 * @param {Replacement} a a
52 * @param {Replacement} b b
53 * @returns {number} order
54 */
55const compareUnstableFallback = (a, b) => {
56 const diff1 = a.start - b.start;
57 if (diff1 !== 0) return diff1;
58 const diff2 = a.end - b.end;
59 if (diff2 !== 0) return diff2;
60 return /** @type {number} */ (a.index) - /** @type {number} */ (b.index);
61};
62
63class Replacement {
64 /**
65 * @param {number} start start
66 * @param {number} end end
67 * @param {string} content content
68 * @param {string=} name name
69 */
70 constructor(start, end, content, name) {
71 this.start = start;
72 this.end = end;
73 this.content = content;
74 this.name = name;
75 if (!hasStableSort) {
76 this.index = -1;
77 }
78 }
79}
80
81class ReplaceSource extends Source {
82 /**
83 * @param {Source} source source
84 * @param {string=} name name
85 */
86 constructor(source, name) {
87 super();
88 /**
89 * @private
90 * @type {Source}
91 */
92 this._source = source;
93 /**
94 * @private
95 * @type {string | undefined}
96 */
97 this._name = name;
98 /** @type {Replacement[]} */
99 this._replacements = [];
100 /**
101 * @private
102 * @type {boolean}
103 */
104 this._isSorted = true;
105 }
106
107 getName() {
108 return this._name;
109 }
110
111 getReplacements() {
112 this._sortReplacements();
113 return this._replacements;
114 }
115
116 /**
117 * @param {number} start start
118 * @param {number} end end
119 * @param {string} newValue new value
120 * @param {string=} name name
121 * @returns {void}
122 */
123 replace(start, end, newValue, name) {
124 if (typeof newValue !== "string") {
125 throw new Error(
126 `insertion must be a string, but is a ${typeof newValue}`,
127 );
128 }
129 this._replacements.push(new Replacement(start, end, newValue, name));
130 this._isSorted = false;
131 }
132
133 /**
134 * @param {number} pos pos
135 * @param {string} newValue new value
136 * @param {string=} name name
137 * @returns {void}
138 */
139 insert(pos, newValue, name) {
140 if (typeof newValue !== "string") {
141 throw new Error(
142 `insertion must be a string, but is a ${typeof newValue}: ${newValue}`,
143 );
144 }
145 this._replacements.push(new Replacement(pos, pos - 1, newValue, name));
146 this._isSorted = false;
147 }
148
149 /**
150 * @returns {SourceValue} source
151 */
152 source() {
153 if (this._replacements.length === 0) {
154 return this._source.source();
155 }
156 const current = /** @type {string} */ (this._source.source());
157 let pos = 0;
158 const result = [];
159
160 this._sortReplacements();
161 for (const replacement of this._replacements) {
162 const start = Math.floor(replacement.start);
163 const end = Math.floor(replacement.end + 1);
164 if (pos < start) {
165 // slice directly from the original string rather than repeatedly
166 // producing smaller intermediate strings, which avoids O(n) copies.
167 result.push(current.slice(pos, start));
168 pos = start;
169 }
170 result.push(replacement.content);
171 if (pos < end) {
172 pos = end;
173 }
174 }
175 if (pos < current.length) {
176 result.push(pos === 0 ? current : current.slice(pos));
177 }
178 return result.join("");
179 }
180
181 /**
182 * @returns {Buffer} buffer
183 */
184 buffer() {
185 if (this._replacements.length === 0) {
186 return this._source.buffer();
187 }
188 return super.buffer();
189 }
190
191 /**
192 * @returns {Buffer[]} buffers
193 */
194 buffers() {
195 if (this._replacements.length === 0) {
196 // TODO remove in the next major release
197 return typeof this._source.buffers === "function"
198 ? this._source.buffers()
199 : [this._source.buffer()];
200 }
201 return [this.buffer()];
202 }
203
204 /**
205 * @param {MapOptions=} options map options
206 * @returns {RawSourceMap | null} map
207 */
208 map(options) {
209 if (this._replacements.length === 0) {
210 return this._source.map(options);
211 }
212 return getMap(this, options);
213 }
214
215 /**
216 * @param {MapOptions=} options map options
217 * @returns {SourceAndMap} source and map
218 */
219 sourceAndMap(options) {
220 if (this._replacements.length === 0) {
221 return this._source.sourceAndMap(options);
222 }
223 return getSourceAndMap(this, options);
224 }
225
226 original() {
227 return this._source;
228 }
229
230 _sortReplacements() {
231 if (this._isSorted) return;
232 if (hasStableSort) {
233 this._replacements.sort(compareStable);
234 } else {
235 for (const [i, repl] of this._replacements.entries()) repl.index = i;
236 this._replacements.sort(compareUnstableFallback);
237 }
238 this._isSorted = true;
239 }
240
241 /**
242 * @param {Options} options options
243 * @param {OnChunk} onChunk called for each chunk of code
244 * @param {OnSource} onSource called for each source
245 * @param {OnName} onName called for each name
246 * @returns {GeneratedSourceInfo} generated source info
247 */
248 streamChunks(options, onChunk, onSource, onName) {
249 this._sortReplacements();
250 const replacements = this._replacements;
251 let pos = 0;
252 let i = 0;
253 let replacementEnd = -1;
254 let nextReplacement =
255 i < replacements.length
256 ? Math.floor(replacements[i].start)
257 : MAX_SOURCE_POSITION;
258 let generatedLineOffset = 0;
259 let generatedColumnOffset = 0;
260 let generatedColumnOffsetLine = 0;
261 /** @type {(string | string[] | undefined)[]} */
262 const sourceContents = [];
263 /** @type {Map<string, number>} */
264 const nameMapping = new Map();
265 /** @type {number[]} */
266 const nameIndexMapping = [];
267 /**
268 * @param {number} sourceIndex source index
269 * @param {number} line line
270 * @param {number} column column
271 * @param {string} expectedChunk expected chunk
272 * @returns {boolean} result
273 */
274 const checkOriginalContent = (sourceIndex, line, column, expectedChunk) => {
275 /** @type {undefined | string | string[]} */
276 let content =
277 sourceIndex < sourceContents.length
278 ? sourceContents[sourceIndex]
279 : undefined;
280 if (content === undefined) return false;
281 if (typeof content === "string") {
282 content = splitIntoLines(content);
283 sourceContents[sourceIndex] = content;
284 }
285 const contentLine = line <= content.length ? content[line - 1] : null;
286 if (contentLine === null) return false;
287 return (
288 contentLine.slice(column, column + expectedChunk.length) ===
289 expectedChunk
290 );
291 };
292 const { generatedLine, generatedColumn } = streamChunks(
293 this._source,
294 { ...options, finalSource: false },
295 (
296 _chunk,
297 generatedLine,
298 generatedColumn,
299 sourceIndex,
300 originalLine,
301 originalColumn,
302 nameIndex,
303 ) => {
304 let chunkPos = 0;
305 const chunk = /** @type {string} */ (_chunk);
306 const endPos = pos + chunk.length;
307
308 // Skip over when it has been replaced
309 if (replacementEnd > pos) {
310 // Skip over the whole chunk
311 if (replacementEnd >= endPos) {
312 const line = generatedLine + generatedLineOffset;
313 if (chunk.endsWith("\n")) {
314 generatedLineOffset--;
315 if (generatedColumnOffsetLine === line) {
316 // undo exiting corrections form the current line
317 generatedColumnOffset += generatedColumn;
318 }
319 } else if (generatedColumnOffsetLine === line) {
320 generatedColumnOffset -= chunk.length;
321 } else {
322 generatedColumnOffset = -chunk.length;
323 generatedColumnOffsetLine = line;
324 }
325 pos = endPos;
326 return;
327 }
328
329 // Partially skip over chunk
330 chunkPos = replacementEnd - pos;
331 if (
332 checkOriginalContent(
333 sourceIndex,
334 originalLine,
335 originalColumn,
336 chunk.slice(0, chunkPos),
337 )
338 ) {
339 originalColumn += chunkPos;
340 }
341 pos += chunkPos;
342 const line = generatedLine + generatedLineOffset;
343 if (generatedColumnOffsetLine === line) {
344 generatedColumnOffset -= chunkPos;
345 } else {
346 generatedColumnOffset = -chunkPos;
347 generatedColumnOffsetLine = line;
348 }
349 generatedColumn += chunkPos;
350 }
351
352 // Is a replacement in the chunk?
353 if (nextReplacement < endPos) {
354 do {
355 let line = generatedLine + generatedLineOffset;
356 if (nextReplacement > pos) {
357 // Emit chunk until replacement
358 const offset = nextReplacement - pos;
359 const chunkSlice = chunk.slice(chunkPos, chunkPos + offset);
360 onChunk(
361 chunkSlice,
362 line,
363 generatedColumn +
364 (line === generatedColumnOffsetLine
365 ? generatedColumnOffset
366 : 0),
367 sourceIndex,
368 originalLine,
369 originalColumn,
370 nameIndex < 0 || nameIndex >= nameIndexMapping.length
371 ? -1
372 : nameIndexMapping[nameIndex],
373 );
374 generatedColumn += offset;
375 chunkPos += offset;
376 pos = nextReplacement;
377 if (
378 checkOriginalContent(
379 sourceIndex,
380 originalLine,
381 originalColumn,
382 chunkSlice,
383 )
384 ) {
385 originalColumn += chunkSlice.length;
386 }
387 }
388
389 // Insert replacement content splitted into chunks by lines
390 const { content, name } = replacements[i];
391 const matches = splitIntoLines(content);
392 let replacementNameIndex = nameIndex;
393 if (sourceIndex >= 0 && name) {
394 let globalIndex = nameMapping.get(name);
395 if (globalIndex === undefined) {
396 globalIndex = nameMapping.size;
397 nameMapping.set(name, globalIndex);
398 onName(globalIndex, name);
399 }
400 replacementNameIndex = globalIndex;
401 }
402 for (let m = 0; m < matches.length; m++) {
403 const contentLine = matches[m];
404 onChunk(
405 contentLine,
406 line,
407 generatedColumn +
408 (line === generatedColumnOffsetLine
409 ? generatedColumnOffset
410 : 0),
411 sourceIndex,
412 originalLine,
413 originalColumn,
414 replacementNameIndex,
415 );
416
417 // Only the first chunk has name assigned
418 replacementNameIndex = -1;
419
420 if (m === matches.length - 1 && !contentLine.endsWith("\n")) {
421 if (generatedColumnOffsetLine === line) {
422 generatedColumnOffset += contentLine.length;
423 } else {
424 generatedColumnOffset = contentLine.length;
425 generatedColumnOffsetLine = line;
426 }
427 } else {
428 generatedLineOffset++;
429 line++;
430 generatedColumnOffset = -generatedColumn;
431 generatedColumnOffsetLine = line;
432 }
433 }
434
435 // Remove replaced content by settings this variable
436 replacementEnd = Math.max(
437 replacementEnd,
438 Math.floor(replacements[i].end + 1),
439 );
440
441 // Move to next replacement
442 i++;
443 nextReplacement =
444 i < replacements.length
445 ? Math.floor(replacements[i].start)
446 : MAX_SOURCE_POSITION;
447
448 // Skip over when it has been replaced
449 const offset = chunk.length - endPos + replacementEnd - chunkPos;
450 if (offset > 0) {
451 // Skip over whole chunk
452 if (replacementEnd >= endPos) {
453 const line = generatedLine + generatedLineOffset;
454 if (chunk.endsWith("\n")) {
455 generatedLineOffset--;
456 if (generatedColumnOffsetLine === line) {
457 // undo exiting corrections form the current line
458 generatedColumnOffset += generatedColumn;
459 }
460 } else if (generatedColumnOffsetLine === line) {
461 generatedColumnOffset -= chunk.length - chunkPos;
462 } else {
463 generatedColumnOffset = chunkPos - chunk.length;
464 generatedColumnOffsetLine = line;
465 }
466 pos = endPos;
467 return;
468 }
469
470 // Partially skip over chunk
471 const line = generatedLine + generatedLineOffset;
472 if (
473 checkOriginalContent(
474 sourceIndex,
475 originalLine,
476 originalColumn,
477 chunk.slice(chunkPos, chunkPos + offset),
478 )
479 ) {
480 originalColumn += offset;
481 }
482 chunkPos += offset;
483 pos += offset;
484 if (generatedColumnOffsetLine === line) {
485 generatedColumnOffset -= offset;
486 } else {
487 generatedColumnOffset = -offset;
488 generatedColumnOffsetLine = line;
489 }
490 generatedColumn += offset;
491 }
492 } while (nextReplacement < endPos);
493 }
494
495 // Emit remaining chunk
496 if (chunkPos < chunk.length) {
497 const chunkSlice = chunkPos === 0 ? chunk : chunk.slice(chunkPos);
498 const line = generatedLine + generatedLineOffset;
499 onChunk(
500 chunkSlice,
501 line,
502 generatedColumn +
503 (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
504 sourceIndex,
505 originalLine,
506 originalColumn,
507 nameIndex < 0 ? -1 : nameIndexMapping[nameIndex],
508 );
509 }
510 pos = endPos;
511 },
512 (sourceIndex, source, sourceContent) => {
513 while (sourceContents.length < sourceIndex) {
514 sourceContents.push(undefined);
515 }
516 sourceContents[sourceIndex] = sourceContent;
517 onSource(sourceIndex, source, sourceContent);
518 },
519 (nameIndex, name) => {
520 let globalIndex = nameMapping.get(name);
521 if (globalIndex === undefined) {
522 globalIndex = nameMapping.size;
523 nameMapping.set(name, globalIndex);
524 onName(globalIndex, name);
525 }
526 nameIndexMapping[nameIndex] = globalIndex;
527 },
528 );
529
530 // Handle remaining replacements
531 let remainer = "";
532 for (; i < replacements.length; i++) {
533 remainer += replacements[i].content;
534 }
535
536 // Insert remaining replacements content splitted into chunks by lines
537 let line = /** @type {number} */ (generatedLine) + generatedLineOffset;
538 const matches = splitIntoLines(remainer);
539 for (let m = 0; m < matches.length; m++) {
540 const contentLine = matches[m];
541 onChunk(
542 contentLine,
543 line,
544 /** @type {number} */
545 (generatedColumn) +
546 (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
547 -1,
548 -1,
549 -1,
550 -1,
551 );
552
553 if (m === matches.length - 1 && !contentLine.endsWith("\n")) {
554 if (generatedColumnOffsetLine === line) {
555 generatedColumnOffset += contentLine.length;
556 } else {
557 generatedColumnOffset = contentLine.length;
558 generatedColumnOffsetLine = line;
559 }
560 } else {
561 generatedLineOffset++;
562 line++;
563 generatedColumnOffset = -(/** @type {number} */ (generatedColumn));
564 generatedColumnOffsetLine = line;
565 }
566 }
567
568 return {
569 generatedLine: line,
570 generatedColumn:
571 /** @type {number} */
572 (generatedColumn) +
573 (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
574 };
575 }
576
577 /**
578 * @param {HashLike} hash hash
579 * @returns {void}
580 */
581 updateHash(hash) {
582 this._sortReplacements();
583 hash.update("ReplaceSource");
584 this._source.updateHash(hash);
585 hash.update(this._name || "");
586 // Feed each replacement as multiple updates instead of building one
587 // combined template literal per replacement. The resulting digest is
588 // identical (hash.update is additive over bytes), but we avoid
589 // allocating a new string per replacement.
590 for (const repl of this._replacements) {
591 hash.update(`${repl.start}${repl.end}`);
592 hash.update(repl.content);
593 if (repl.name) hash.update(repl.name);
594 }
595 }
596}
597
598module.exports = ReplaceSource;
599module.exports.Replacement = Replacement;
Note: See TracBrowser for help on using the repository browser.