Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80629 views
1
var fs = require('fs');
2
var esprima = require('./contrib/esprima');
3
var escodegen = require('./contrib/escodegen');
4
var EventEmitter = require('events').EventEmitter;
5
6
esprima.Syntax = {
7
AssignmentExpression: 'AssignmentExpression',
8
ArrayExpression: 'ArrayExpression',
9
BlockStatement: 'BlockStatement',
10
BinaryExpression: 'BinaryExpression',
11
BreakStatement: 'BreakStatement',
12
CallExpression: 'CallExpression',
13
CatchClause: 'CatchClause',
14
ConditionalExpression: 'ConditionalExpression',
15
ContinueStatement: 'ContinueStatement',
16
DoWhileStatement: 'DoWhileStatement',
17
DebuggerStatement: 'DebuggerStatement',
18
EmptyStatement: 'EmptyStatement',
19
ExpressionStatement: 'ExpressionStatement',
20
ForStatement: 'ForStatement',
21
ForInStatement: 'ForInStatement',
22
FunctionDeclaration: 'FunctionDeclaration',
23
FunctionExpression: 'FunctionExpression',
24
Identifier: 'Identifier',
25
IfStatement: 'IfStatement',
26
Literal: 'Literal',
27
LabeledStatement: 'LabeledStatement',
28
LogicalExpression: 'LogicalExpression',
29
MemberExpression: 'MemberExpression',
30
NewExpression: 'NewExpression',
31
ObjectExpression: 'ObjectExpression',
32
Program: 'Program',
33
Property: 'Property',
34
ReturnStatement: 'ReturnStatement',
35
SequenceExpression: 'SequenceExpression',
36
SwitchStatement: 'SwitchStatement',
37
SwitchCase: 'SwitchCase',
38
ThisExpression: 'ThisExpression',
39
ThrowStatement: 'ThrowStatement',
40
TryStatement: 'TryStatement',
41
UnaryExpression: 'UnaryExpression',
42
UpdateExpression: 'UpdateExpression',
43
VariableDeclaration: 'VariableDeclaration',
44
VariableDeclarator: 'VariableDeclarator',
45
WhileStatement: 'WhileStatement',
46
WithStatement: 'WithStatement'
47
};
48
49
module.exports = function(src) {
50
var instrumentor = new Instrumentor(src);
51
return instrumentor;
52
};
53
54
module.exports.Instrumentor = Instrumentor;
55
56
function Instrumentor(src) {
57
// Setup our names
58
this.names = {
59
statement: this.generateName(6, "__statement_"),
60
expression: this.generateName(6, "__expression_"),
61
block: this.generateName(6, "__block_")
62
};
63
64
// Setup the node store
65
this.nodes = {};
66
67
// Setup the counters
68
this.blockCounter = 0;
69
this.nodeCounter = 0;
70
71
if (src) {
72
this.instrumentedSource = this.instrument(src);
73
}
74
};
75
76
Instrumentor.prototype = new EventEmitter;
77
78
Instrumentor.prototype.objectify = function() {
79
var obj = {};
80
obj.blockCounter = this.blockCounter;
81
obj.nodeCounter = this.nodeCounter;
82
obj.source = this.source;
83
84
obj.nodes = {};
85
for(var key in this.nodes) {
86
if (this.nodes.hasOwnProperty(key)) {
87
var node = this.nodes[key];
88
obj.nodes[key] = { loc: node.loc, id: node.id };
89
}
90
}
91
92
return obj;
93
}
94
95
Instrumentor.prototype.filter = function(action) {
96
action = action || function() {};
97
98
var filtered = [];
99
for(var key in this.nodes) {
100
if (this.nodes.hasOwnProperty(key)) {
101
var node = this.nodes[key];
102
if (action(node)) {
103
filtered.push(node);
104
}
105
}
106
}
107
108
return filtered;
109
};
110
111
Instrumentor.prototype.instrument = function(code) {
112
this.source = code;
113
114
// We wrap the code with a function to make sure it is compliant with node
115
var header = "(function() {";
116
var footer = "\n})();"
117
var wrappedCode = header + code + footer;
118
119
// Parse the wrapped code
120
var tree = esprima.parse(wrappedCode, {range: true, loc: true, comment: true});
121
122
var ignoredLines = {};
123
var ignoreRe = /^\s*cover\s*:\s*false\s*$/
124
tree.comments.
125
filter(function(commentNode) {
126
return ignoreRe.test(commentNode.value);
127
}).
128
forEach(function(commentNode) {
129
ignoredLines[commentNode.loc.start.line] = true;
130
});
131
132
// We only "instrument" the original part, which is in this sub-statement
133
this.wrap(tree.body[0].expression.callee.body.body, ignoredLines);
134
135
// We need to adjust the nodes for everything on the first line,
136
// such that their location statements will start at 1 and not at header.length
137
for(var nodeKey in this.nodes) {
138
if (this.nodes.hasOwnProperty(nodeKey)) {
139
var node = this.nodes[nodeKey];
140
141
if (node.loc.start.line === 1 || node.loc.end.line === 1) {
142
// Copy over the location data, as these are shared across
143
// nodes. We only do it for things on the first line
144
node.loc = {
145
start: {
146
line: node.loc.start.line,
147
column: node.loc.start.column
148
},
149
end: {
150
line: node.loc.end.line,
151
column: node.loc.end.column
152
}
153
}
154
155
// Adjust the columns
156
if (node.loc.start.line == 1) {
157
node.loc.start.column = node.loc.start.column - header.length;
158
}
159
if (node.loc.end.line == 1) {
160
node.loc.end.column = node.loc.end.column - header.length;
161
}
162
}
163
}
164
}
165
166
return escodegen.generate(tree);
167
};
168
169
Instrumentor.prototype.addToContext = function(context) {
170
context = context || {};
171
172
var that = this;
173
174
context[that.names.expression] = function (i) {
175
var node = that.nodes[i];
176
that.emit('node', node);
177
178
return function (expr) {
179
return expr;
180
};
181
};
182
183
context[that.names.statement] = function (i) {
184
var node = that.nodes[i];
185
that.emit('node', node);
186
};
187
188
context[that.names.block] = function (i) {
189
that.emit('block', i);
190
};
191
192
return context;
193
};
194
195
Instrumentor.prototype.generateName = function (len, prefix) {
196
var name = '';
197
var lower = '$'.charCodeAt(0);
198
var upper = 'z'.charCodeAt(0);
199
200
while (name.length < len) {
201
var c = String.fromCharCode(Math.floor(
202
Math.random() * (upper - lower + 1) + lower
203
));
204
if ((name + c).match(/^[A-Za-z_$][A-Za-z0-9_$]*$/)) name += c;
205
}
206
207
return prefix + name;
208
};
209
210
Instrumentor.prototype.traverseAndWrap = function(object, visitor, master) {
211
var key, child, parent, path;
212
213
parent = (typeof master === 'undefined') ? [] : master;
214
215
var returned = visitor.call(null, object, parent);
216
if (returned === false) {
217
return;
218
}
219
220
for (key in object) {
221
if (object.hasOwnProperty(key)) {
222
child = object[key];
223
path = [ object ];
224
path.push(parent);
225
var newNode;
226
if (typeof child === 'object' && child !== null && !object.noCover) {
227
newNode = this.traverseAndWrap(child, visitor, path);
228
}
229
230
if (newNode) {
231
object[key] = newNode;
232
newNode = null;
233
}
234
}
235
}
236
237
return object.noCover ? undefined : returned;
238
};
239
240
Instrumentor.prototype.wrap = function(tree, ignoredLines) {
241
var that = this;
242
this.traverseAndWrap(tree, function(node, path) {
243
if (node.noCover) {
244
return;
245
}
246
if (node.loc && node.loc.start.line in ignoredLines) {
247
return false;
248
}
249
250
parent = path[0];
251
switch(node.type) {
252
case esprima.Syntax.ExpressionStatement:
253
case esprima.Syntax.ThrowStatement:
254
case esprima.Syntax.VariableDeclaration: {
255
if (parent && (
256
(parent.type === esprima.Syntax.ForInStatement) ||
257
(parent.type === esprima.Syntax.ForStatement)
258
)) {
259
return;
260
}
261
262
var newNode = {
263
"type": "BlockStatement",
264
"body": [
265
{
266
"type": "ExpressionStatement",
267
"expression": {
268
"type": "CallExpression",
269
"callee": {
270
"type": "Identifier",
271
"name": that.names.statement
272
},
273
"arguments": [
274
{
275
"type": "Literal",
276
"value": that.nodeCounter++
277
}
278
]
279
}
280
},
281
node
282
]
283
}
284
that.nodes[that.nodeCounter - 1] = node;
285
node.id = that.nodeCounter - 1;
286
287
return newNode;
288
}
289
case esprima.Syntax.ReturnStatement: {
290
var newNode = {
291
"type": "SequenceExpression",
292
"expressions": [
293
{
294
"type": "CallExpression",
295
"callee": {
296
"type": "Identifier",
297
"name": that.names.expression
298
},
299
"arguments": [
300
{
301
"type": "Identifier",
302
"name": that.nodeCounter++
303
}
304
],
305
noCover: true
306
}
307
],
308
}
309
310
if (node.argument) {
311
newNode.expressions.push(node.argument);
312
}
313
314
that.nodes[that.nodeCounter - 1] = node;
315
node.id = that.nodeCounter - 1;
316
317
node.argument = newNode
318
break;
319
}
320
case esprima.Syntax.ConditionalExpression: {
321
var newConsequentNode = {
322
"type": "SequenceExpression",
323
"expressions": [
324
{
325
"type": "CallExpression",
326
"callee": {
327
"type": "Identifier",
328
"name": that.names.expression
329
},
330
"arguments": [
331
{
332
"type": "Identifier",
333
"name": that.nodeCounter++
334
}
335
],
336
noCover: true
337
},
338
node.consequent
339
],
340
}
341
that.nodes[that.nodeCounter - 1] = node.consequent;
342
node.consequent.id = that.nodeCounter - 1
343
344
var newAlternateNode = {
345
"type": "SequenceExpression",
346
"expressions": [
347
{
348
"type": "CallExpression",
349
"callee": {
350
"type": "Identifier",
351
"name": that.names.expression
352
},
353
"arguments": [
354
{
355
"type": "Identifier",
356
"name": that.nodeCounter++
357
}
358
],
359
noCover: true
360
},
361
node.alternate
362
],
363
}
364
that.nodes[that.nodeCounter - 1] = node.alternate;
365
node.alternate.id = that.nodeCounter - 1
366
367
node.consequent = newConsequentNode;
368
node.alternate = newAlternateNode;
369
break;
370
}
371
case esprima.Syntax.BinaryExpression:
372
case esprima.Syntax.UpdateExpression:
373
case esprima.Syntax.LogicalExpression:
374
case esprima.Syntax.CallExpression:
375
case esprima.Syntax.UnaryExpression:
376
case esprima.Syntax.Identifier: {
377
// Only instrument Identifier in certain context.
378
if (node.type === esprima.Syntax.Identifier) {
379
if (!(parent && (parent.type == 'UnaryExpression' ||
380
parent.type == 'BinaryExpression' ||
381
parent.type == 'LogicalExpression' ||
382
parent.type == 'ConditionalExpression' ||
383
parent.type == 'SwitchStatement' ||
384
parent.type == 'SwitchCase' ||
385
parent.type == 'ForStatement' ||
386
parent.type == 'IfStatement' ||
387
parent.type == 'WhileStatement' ||
388
parent.type == 'DoWhileStatement'))) {
389
return;
390
}
391
// Do not instrument Identifier when preceded by typeof
392
if (parent.operator == 'typeof') {
393
return;
394
}
395
396
}
397
var newNode = {
398
"type": "SequenceExpression",
399
"expressions": [
400
{
401
"type": "CallExpression",
402
"callee": {
403
"type": "Identifier",
404
"name": that.names.expression
405
},
406
"arguments": [
407
{
408
"type": "Identifier",
409
"name": that.nodeCounter++
410
}
411
]
412
},
413
node
414
]
415
}
416
417
that.nodes[that.nodeCounter - 1] = node;
418
node.id = that.nodeCounter - 1;
419
420
return newNode
421
}
422
case esprima.Syntax.BlockStatement: {
423
var newNode = {
424
"type": "ExpressionStatement",
425
"expression": {
426
"type": "CallExpression",
427
"callee": {
428
"type": "Identifier",
429
"name": that.names.block
430
},
431
"arguments": [
432
{
433
"type": "Literal",
434
"value": that.blockCounter++
435
}
436
]
437
},
438
"noCover": true
439
}
440
441
node.body.unshift(newNode)
442
break;
443
}
444
case esprima.Syntax.ForStatement:
445
case esprima.Syntax.ForInStatement:
446
case esprima.Syntax.LabeledStatement:
447
case esprima.Syntax.WhileStatement:
448
case esprima.Syntax.WithStatement:
449
case esprima.Syntax.CatchClause:
450
case esprima.Syntax.DoWhileStatement: {
451
if (node.body && node.body.type !== esprima.Syntax.BlockStatement) {
452
var newNode = {
453
"type": "BlockStatement",
454
"body": [
455
node.body
456
]
457
}
458
459
node.body = newNode;
460
}
461
break;
462
}
463
case esprima.Syntax.TryStatement: {
464
if (node.block && node.block.type !== esprima.Syntax.BlockStatement) {
465
var newNode = {
466
"type": "BlockStatement",
467
"body": [
468
node.block
469
]
470
}
471
472
node.block = newNode;
473
}
474
if (node.finalizer && node.finalizer.type !== esprima.Syntax.BlockStatement) {
475
var newNode = {
476
"type": "BlockStatement",
477
"body": [
478
node.block
479
]
480
}
481
482
node.finalizer = newNode;
483
}
484
break;
485
}
486
case esprima.Syntax.SwitchCase: {
487
if (node.consequent && node.consequent.length > 0 &&
488
(node.consequent.length != 1 || node.consequent[0].type !== esprima.Syntax.BlockStatement)) {
489
var newNode = {
490
"type": "BlockStatement",
491
"body": node.consequent
492
}
493
494
node.consequent = [newNode];
495
}
496
break;
497
}
498
case esprima.Syntax.IfStatement: {
499
if (node.consequent && node.consequent.type !== esprima.Syntax.BlockStatement) {
500
var newNode = {
501
"type": "BlockStatement",
502
"body": [
503
node.consequent
504
]
505
}
506
507
node.consequent = newNode;
508
}
509
if (node.alternate && node.alternate.type !== esprima.Syntax.BlockStatement
510
&& node.alternate.type !== esprima.Syntax.IfStatement) {
511
var newNode = {
512
"type": "BlockStatement",
513
"body": [
514
node.alternate
515
]
516
}
517
518
node.alternate = newNode;
519
}
520
break;
521
}
522
}
523
});
524
}
525
526