source: frontend/node_modules/source-map/lib/source-node.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: 13.5 KB
RevLine 
[9af201e]1/* -*- Mode: js; js-indent-level: 2; -*- */
2/*
3 * Copyright 2011 Mozilla Foundation and contributors
4 * Licensed under the New BSD license. See LICENSE or:
5 * http://opensource.org/licenses/BSD-3-Clause
6 */
7
8const SourceMapGenerator = require("./source-map-generator").SourceMapGenerator;
9const util = require("./util");
10
11// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
12// operating systems these days (capturing the result).
13const REGEX_NEWLINE = /(\r?\n)/;
14
15// Newline character code for charCodeAt() comparisons
16const NEWLINE_CODE = 10;
17
18// Private symbol for identifying `SourceNode`s when multiple versions of
19// the source-map library are loaded. This MUST NOT CHANGE across
20// versions!
21const isSourceNode = "$$$isSourceNode$$$";
22
23/**
24 * SourceNodes provide a way to abstract over interpolating/concatenating
25 * snippets of generated JavaScript source code while maintaining the line and
26 * column information associated with the original source code.
27 *
28 * @param aLine The original line number.
29 * @param aColumn The original column number.
30 * @param aSource The original source's filename.
31 * @param aChunks Optional. An array of strings which are snippets of
32 * generated JS, or other SourceNodes.
33 * @param aName The original identifier.
34 */
35class SourceNode {
36 constructor(aLine, aColumn, aSource, aChunks, aName) {
37 this.children = [];
38 this.sourceContents = {};
39 this.line = aLine == null ? null : aLine;
40 this.column = aColumn == null ? null : aColumn;
41 this.source = aSource == null ? null : aSource;
42 this.name = aName == null ? null : aName;
43 this[isSourceNode] = true;
44 if (aChunks != null) this.add(aChunks);
45 }
46
47 /**
48 * Creates a SourceNode from generated code and a SourceMapConsumer.
49 *
50 * @param aGeneratedCode The generated code
51 * @param aSourceMapConsumer The SourceMap for the generated code
52 * @param aRelativePath Optional. The path that relative sources in the
53 * SourceMapConsumer should be relative to.
54 */
55 static fromStringWithSourceMap(
56 aGeneratedCode,
57 aSourceMapConsumer,
58 aRelativePath
59 ) {
60 // The SourceNode we want to fill with the generated code
61 // and the SourceMap
62 const node = new SourceNode();
63
64 // All even indices of this array are one line of the generated code,
65 // while all odd indices are the newlines between two adjacent lines
66 // (since `REGEX_NEWLINE` captures its match).
67 // Processed fragments are accessed by calling `shiftNextLine`.
68 const remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
69 let remainingLinesIndex = 0;
70 const shiftNextLine = function () {
71 const lineContents = getNextLine();
72 // The last line of a file might not have a newline.
73 const newLine = getNextLine() || "";
74 return lineContents + newLine;
75
76 function getNextLine() {
77 return remainingLinesIndex < remainingLines.length
78 ? remainingLines[remainingLinesIndex++]
79 : undefined;
80 }
81 };
82
83 // We need to remember the position of "remainingLines"
84 let lastGeneratedLine = 1,
85 lastGeneratedColumn = 0;
86
87 // The generate SourceNodes we need a code range.
88 // To extract it current and last mapping is used.
89 // Here we store the last mapping.
90 let lastMapping = null;
91 let nextLine;
92
93 aSourceMapConsumer.eachMapping(function (mapping) {
94 if (lastMapping !== null) {
95 // We add the code from "lastMapping" to "mapping":
96 // First check if there is a new line in between.
97 if (lastGeneratedLine < mapping.generatedLine) {
98 // Associate first line with "lastMapping"
99 addMappingWithCode(lastMapping, shiftNextLine());
100 lastGeneratedLine++;
101 lastGeneratedColumn = 0;
102 // The remaining code is added without mapping
103 } else {
104 // There is no new line in between.
105 // Associate the code between "lastGeneratedColumn" and
106 // "mapping.generatedColumn" with "lastMapping"
107 nextLine = remainingLines[remainingLinesIndex] || "";
108 const code = nextLine.substr(
109 0,
110 mapping.generatedColumn - lastGeneratedColumn
111 );
112 remainingLines[remainingLinesIndex] = nextLine.substr(
113 mapping.generatedColumn - lastGeneratedColumn
114 );
115 lastGeneratedColumn = mapping.generatedColumn;
116 addMappingWithCode(lastMapping, code);
117 // No more remaining code, continue
118 lastMapping = mapping;
119 return;
120 }
121 }
122 // We add the generated code until the first mapping
123 // to the SourceNode without any mapping.
124 // Each line is added as separate string.
125 while (lastGeneratedLine < mapping.generatedLine) {
126 node.add(shiftNextLine());
127 lastGeneratedLine++;
128 }
129 if (lastGeneratedColumn < mapping.generatedColumn) {
130 nextLine = remainingLines[remainingLinesIndex] || "";
131 node.add(nextLine.substr(0, mapping.generatedColumn));
132 remainingLines[remainingLinesIndex] = nextLine.substr(
133 mapping.generatedColumn
134 );
135 lastGeneratedColumn = mapping.generatedColumn;
136 }
137 lastMapping = mapping;
138 }, this);
139 // We have processed all mappings.
140 if (remainingLinesIndex < remainingLines.length) {
141 if (lastMapping) {
142 // Associate the remaining code in the current line with "lastMapping"
143 addMappingWithCode(lastMapping, shiftNextLine());
144 }
145 // and add the remaining lines without any mapping
146 node.add(remainingLines.splice(remainingLinesIndex).join(""));
147 }
148
149 // Copy sourcesContent into SourceNode
150 aSourceMapConsumer.sources.forEach(function (sourceFile) {
151 const content = aSourceMapConsumer.sourceContentFor(sourceFile);
152 if (content != null) {
153 if (aRelativePath != null) {
154 sourceFile = util.join(aRelativePath, sourceFile);
155 }
156 node.setSourceContent(sourceFile, content);
157 }
158 });
159
160 return node;
161
162 function addMappingWithCode(mapping, code) {
163 if (mapping === null || mapping.source === undefined) {
164 node.add(code);
165 } else {
166 const source = aRelativePath
167 ? util.join(aRelativePath, mapping.source)
168 : mapping.source;
169 node.add(
170 new SourceNode(
171 mapping.originalLine,
172 mapping.originalColumn,
173 source,
174 code,
175 mapping.name
176 )
177 );
178 }
179 }
180 }
181
182 /**
183 * Add a chunk of generated JS to this source node.
184 *
185 * @param aChunk A string snippet of generated JS code, another instance of
186 * SourceNode, or an array where each member is one of those things.
187 */
188 add(aChunk) {
189 if (Array.isArray(aChunk)) {
190 aChunk.forEach(function (chunk) {
191 this.add(chunk);
192 }, this);
193 } else if (aChunk[isSourceNode] || typeof aChunk === "string") {
194 if (aChunk) {
195 this.children.push(aChunk);
196 }
197 } else {
198 throw new TypeError(
199 "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " +
200 aChunk
201 );
202 }
203 return this;
204 }
205
206 /**
207 * Add a chunk of generated JS to the beginning of this source node.
208 *
209 * @param aChunk A string snippet of generated JS code, another instance of
210 * SourceNode, or an array where each member is one of those things.
211 */
212 prepend(aChunk) {
213 if (Array.isArray(aChunk)) {
214 for (let i = aChunk.length - 1; i >= 0; i--) {
215 this.prepend(aChunk[i]);
216 }
217 } else if (aChunk[isSourceNode] || typeof aChunk === "string") {
218 this.children.unshift(aChunk);
219 } else {
220 throw new TypeError(
221 "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " +
222 aChunk
223 );
224 }
225 return this;
226 }
227
228 /**
229 * Walk over the tree of JS snippets in this node and its children. The
230 * walking function is called once for each snippet of JS and is passed that
231 * snippet and the its original associated source's line/column location.
232 *
233 * @param aFn The traversal function.
234 */
235 walk(aFn) {
236 let chunk;
237 for (let i = 0, len = this.children.length; i < len; i++) {
238 chunk = this.children[i];
239 if (chunk[isSourceNode]) {
240 chunk.walk(aFn);
241 } else if (chunk !== "") {
242 aFn(chunk, {
243 source: this.source,
244 line: this.line,
245 column: this.column,
246 name: this.name,
247 });
248 }
249 }
250 }
251
252 /**
253 * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
254 * each of `this.children`.
255 *
256 * @param aSep The separator.
257 */
258 join(aSep) {
259 let newChildren;
260 let i;
261 const len = this.children.length;
262 if (len > 0) {
263 newChildren = [];
264 for (i = 0; i < len - 1; i++) {
265 newChildren.push(this.children[i]);
266 newChildren.push(aSep);
267 }
268 newChildren.push(this.children[i]);
269 this.children = newChildren;
270 }
271 return this;
272 }
273
274 /**
275 * Call String.prototype.replace on the very right-most source snippet. Useful
276 * for trimming whitespace from the end of a source node, etc.
277 *
278 * @param aPattern The pattern to replace.
279 * @param aReplacement The thing to replace the pattern with.
280 */
281 replaceRight(aPattern, aReplacement) {
282 const lastChild = this.children[this.children.length - 1];
283 if (lastChild[isSourceNode]) {
284 lastChild.replaceRight(aPattern, aReplacement);
285 } else if (typeof lastChild === "string") {
286 this.children[this.children.length - 1] = lastChild.replace(
287 aPattern,
288 aReplacement
289 );
290 } else {
291 this.children.push("".replace(aPattern, aReplacement));
292 }
293 return this;
294 }
295
296 /**
297 * Set the source content for a source file. This will be added to the SourceMapGenerator
298 * in the sourcesContent field.
299 *
300 * @param aSourceFile The filename of the source file
301 * @param aSourceContent The content of the source file
302 */
303 setSourceContent(aSourceFile, aSourceContent) {
304 this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
305 }
306
307 /**
308 * Walk over the tree of SourceNodes. The walking function is called for each
309 * source file content and is passed the filename and source content.
310 *
311 * @param aFn The traversal function.
312 */
313 walkSourceContents(aFn) {
314 for (let i = 0, len = this.children.length; i < len; i++) {
315 if (this.children[i][isSourceNode]) {
316 this.children[i].walkSourceContents(aFn);
317 }
318 }
319
320 const sources = Object.keys(this.sourceContents);
321 for (let i = 0, len = sources.length; i < len; i++) {
322 aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
323 }
324 }
325
326 /**
327 * Return the string representation of this source node. Walks over the tree
328 * and concatenates all the various snippets together to one string.
329 */
330 toString() {
331 let str = "";
332 this.walk(function (chunk) {
333 str += chunk;
334 });
335 return str;
336 }
337
338 /**
339 * Returns the string representation of this source node along with a source
340 * map.
341 */
342 toStringWithSourceMap(aArgs) {
343 const generated = {
344 code: "",
345 line: 1,
346 column: 0,
347 };
348 const map = new SourceMapGenerator(aArgs);
349 let sourceMappingActive = false;
350 let lastOriginalSource = null;
351 let lastOriginalLine = null;
352 let lastOriginalColumn = null;
353 let lastOriginalName = null;
354 this.walk(function (chunk, original) {
355 generated.code += chunk;
356 if (
357 original.source !== null &&
358 original.line !== null &&
359 original.column !== null
360 ) {
361 if (
362 lastOriginalSource !== original.source ||
363 lastOriginalLine !== original.line ||
364 lastOriginalColumn !== original.column ||
365 lastOriginalName !== original.name
366 ) {
367 map.addMapping({
368 source: original.source,
369 original: {
370 line: original.line,
371 column: original.column,
372 },
373 generated: {
374 line: generated.line,
375 column: generated.column,
376 },
377 name: original.name,
378 });
379 }
380 lastOriginalSource = original.source;
381 lastOriginalLine = original.line;
382 lastOriginalColumn = original.column;
383 lastOriginalName = original.name;
384 sourceMappingActive = true;
385 } else if (sourceMappingActive) {
386 map.addMapping({
387 generated: {
388 line: generated.line,
389 column: generated.column,
390 },
391 });
392 lastOriginalSource = null;
393 sourceMappingActive = false;
394 }
395 for (let idx = 0, length = chunk.length; idx < length; idx++) {
396 if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
397 generated.line++;
398 generated.column = 0;
399 // Mappings end at eol
400 if (idx + 1 === length) {
401 lastOriginalSource = null;
402 sourceMappingActive = false;
403 } else if (sourceMappingActive) {
404 map.addMapping({
405 source: original.source,
406 original: {
407 line: original.line,
408 column: original.column,
409 },
410 generated: {
411 line: generated.line,
412 column: generated.column,
413 },
414 name: original.name,
415 });
416 }
417 } else {
418 generated.column++;
419 }
420 }
421 });
422 this.walkSourceContents(function (sourceFile, sourceContent) {
423 map.setSourceContent(sourceFile, sourceContent);
424 });
425
426 return { code: generated.code, map };
427 }
428}
429
430exports.SourceNode = SourceNode;
Note: See TracBrowser for help on using the repository browser.