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