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