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