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