Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80559 views
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
});
415
416