source: imaps-frontend/node_modules/source-map-js/lib/source-node.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

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