1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 |
|
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
8 |
|
9 | var _sourceCoverage = require('./source-coverage');
|
10 |
|
11 | var _crypto = require('crypto');
|
12 |
|
13 | var _babelTemplate = require('babel-template');
|
14 |
|
15 | var _babelTemplate2 = _interopRequireDefault(_babelTemplate);
|
16 |
|
17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
18 |
|
19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
20 |
|
21 |
|
22 | var SHA = 'sha1';
|
23 |
|
24 | var COMMENT_RE = /^\s*istanbul\s+ignore\s+(if|else|next)(?=\W|$)/;
|
25 |
|
26 | var SOURCE_MAP_RE = /[#@]\s*sourceMappingURL=(.*)\s*$/m;
|
27 |
|
28 |
|
29 | function genVar(filename) {
|
30 | var hash = (0, _crypto.createHash)(SHA),
|
31 | suffix;
|
32 | hash.update(filename);
|
33 | suffix = hash.digest('base64');
|
34 |
|
35 | suffix = suffix.replace(new RegExp('=', 'g'), '').replace(new RegExp('\\+', 'g'), '_').replace(new RegExp('/', 'g'), '$');
|
36 | return '__cov_' + suffix;
|
37 | }
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | var VisitState = function () {
|
43 | function VisitState(types, sourceFilePath) {
|
44 | _classCallCheck(this, VisitState);
|
45 |
|
46 | this.varName = genVar(sourceFilePath);
|
47 | this.attrs = {};
|
48 | this.nextIgnore = null;
|
49 | this.cov = new _sourceCoverage.SourceCoverage(sourceFilePath);
|
50 | this.types = types;
|
51 | this.sourceMappingURL = null;
|
52 | }
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | _createClass(VisitState, [{
|
59 | key: 'shouldIgnore',
|
60 | value: function shouldIgnore(path) {
|
61 | return this.nextIgnore || !path.node.loc;
|
62 | }
|
63 |
|
64 |
|
65 |
|
66 | }, {
|
67 | key: 'hintFor',
|
68 | value: function hintFor(node) {
|
69 | var hint = null;
|
70 | if (node.leadingComments) {
|
71 | node.leadingComments.forEach(function (c) {
|
72 | var v = (c.value || "").trim();
|
73 | var groups = v.match(COMMENT_RE);
|
74 | if (groups) {
|
75 | hint = groups[1];
|
76 | }
|
77 | });
|
78 | }
|
79 | return hint;
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 | }, {
|
85 | key: 'maybeAssignSourceMapURL',
|
86 | value: function maybeAssignSourceMapURL(node) {
|
87 | var that = this;
|
88 | var extractURL = function extractURL(comments) {
|
89 | if (!comments) {
|
90 | return;
|
91 | }
|
92 | comments.forEach(function (c) {
|
93 | var v = (c.value || "").trim();
|
94 | var groups = v.match(SOURCE_MAP_RE);
|
95 | if (groups) {
|
96 | that.sourceMappingURL = groups[1];
|
97 | }
|
98 | });
|
99 | };
|
100 | extractURL(node.leadingComments);
|
101 | extractURL(node.trailingComments);
|
102 | }
|
103 |
|
104 |
|
105 |
|
106 | }, {
|
107 | key: 'onEnter',
|
108 | value: function onEnter(path) {
|
109 | var n = path.node;
|
110 |
|
111 | this.maybeAssignSourceMapURL(n);
|
112 |
|
113 |
|
114 | if (this.nextIgnore !== null) {
|
115 | return;
|
116 | }
|
117 |
|
118 | var hint = this.hintFor(n);
|
119 | if (hint === 'next') {
|
120 | this.nextIgnore = n;
|
121 | return;
|
122 | }
|
123 |
|
124 | if (this.getAttr(path.node, 'skip-all')) {
|
125 | this.nextIgnore = n;
|
126 | }
|
127 | }
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | }, {
|
133 | key: 'onExit',
|
134 | value: function onExit(path) {
|
135 |
|
136 | if (path.node === this.nextIgnore) {
|
137 | this.nextIgnore = null;
|
138 | }
|
139 |
|
140 | delete path.node.__cov__;
|
141 | }
|
142 |
|
143 |
|
144 |
|
145 | }, {
|
146 | key: 'setAttr',
|
147 | value: function setAttr(node, name, value) {
|
148 | node.__cov__ = node.__cov__ || {};
|
149 | node.__cov__[name] = value;
|
150 | }
|
151 |
|
152 |
|
153 |
|
154 | }, {
|
155 | key: 'getAttr',
|
156 | value: function getAttr(node, name) {
|
157 | var c = node.__cov__;
|
158 | if (!c) {
|
159 | return null;
|
160 | }
|
161 | return c[name];
|
162 | }
|
163 |
|
164 |
|
165 |
|
166 | }, {
|
167 | key: 'increase',
|
168 | value: function increase(type, id, index) {
|
169 | var T = this.types;
|
170 | var wrap = index !== null
|
171 |
|
172 | ? function (x) {
|
173 | return T.memberExpression(x, T.numericLiteral(index), true);
|
174 | } : function (x) {
|
175 | return x;
|
176 | };
|
177 | return T.unaryExpression('++', wrap(T.memberExpression(T.memberExpression(T.identifier(this.varName), T.identifier(type)), T.stringLiteral(String(id)), true)));
|
178 | }
|
179 | }, {
|
180 | key: 'insertCounter',
|
181 | value: function insertCounter(path, increment) {
|
182 | var T = this.types;
|
183 | if (path.isBlockStatement()) {
|
184 | path.node.body.unshift(T.expressionStatement(increment));
|
185 | } else if (path.isStatement()) {
|
186 | path.insertBefore(T.expressionStatement(increment));
|
187 | } else if (path.isExpression()) {
|
188 | path.replaceWith(T.sequenceExpression([increment, path.node]));
|
189 | } else {
|
190 | console.error('Unable to insert counter for node type:', path.node.type);
|
191 | }
|
192 | }
|
193 | }, {
|
194 | key: 'insertStatementCounter',
|
195 | value: function insertStatementCounter(path) {
|
196 |
|
197 | if (!(path.node && path.node.loc)) {
|
198 | return;
|
199 | }
|
200 | var index = this.cov.newStatement(path.node.loc);
|
201 | var increment = this.increase('s', index, null);
|
202 | this.insertCounter(path, increment);
|
203 | }
|
204 | }, {
|
205 | key: 'insertFunctionCounter',
|
206 | value: function insertFunctionCounter(path) {
|
207 | var T = this.types;
|
208 |
|
209 | if (!(path.node && path.node.loc)) {
|
210 | return;
|
211 | }
|
212 | var n = path.node;
|
213 | var dloc = null;
|
214 |
|
215 | switch (n.type) {
|
216 | case "FunctionDeclaration":
|
217 |
|
218 | if (n.id) {
|
219 | dloc = n.id.loc;
|
220 | }
|
221 | break;
|
222 | case "FunctionExpression":
|
223 | if (n.id) {
|
224 | dloc = n.id.loc;
|
225 | }
|
226 | break;
|
227 | }
|
228 | if (!dloc) {
|
229 | dloc = {
|
230 | start: n.loc.start,
|
231 | end: { line: n.loc.start.line, column: n.loc.start.column + 1 }
|
232 | };
|
233 | }
|
234 | var name = path.node.id ? path.node.id.name : path.node.name;
|
235 | var index = this.cov.newFunction(name, dloc, path.node.body.loc);
|
236 | var increment = this.increase('f', index, null);
|
237 | var body = path.get('body');
|
238 |
|
239 | if (body.isBlockStatement()) {
|
240 | body.node.body.unshift(T.expressionStatement(increment));
|
241 | } else {
|
242 | console.error('Unable to process function body node type:', path.node.type);
|
243 | }
|
244 | }
|
245 | }, {
|
246 | key: 'getBranchIncrement',
|
247 | value: function getBranchIncrement(branchName, loc) {
|
248 | var index = this.cov.addBranchPath(branchName, loc);
|
249 | return this.increase('b', branchName, index);
|
250 | }
|
251 | }, {
|
252 | key: 'insertBranchCounter',
|
253 | value: function insertBranchCounter(path, branchName, loc) {
|
254 | var increment = this.getBranchIncrement(branchName, loc || path.node.loc);
|
255 | this.insertCounter(path, increment);
|
256 | }
|
257 | }, {
|
258 | key: 'findLeaves',
|
259 | value: function findLeaves(node, accumulator, parent, property) {
|
260 | if (!node) {
|
261 | return;
|
262 | }
|
263 | if (node.type === "LogicalExpression") {
|
264 | var hint = this.hintFor(node);
|
265 | if (hint !== 'next') {
|
266 | this.findLeaves(node.left, accumulator, node, 'left');
|
267 | this.findLeaves(node.right, accumulator, node, 'right');
|
268 | }
|
269 | } else {
|
270 | accumulator.push({
|
271 | node: node,
|
272 | parent: parent,
|
273 | property: property
|
274 | });
|
275 | }
|
276 | }
|
277 | }]);
|
278 |
|
279 | return VisitState;
|
280 | }();
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 | function entries() {
|
294 | var enter = Array.prototype.slice.call(arguments);
|
295 |
|
296 | var wrappedEntry = function wrappedEntry(path, node) {
|
297 | this.onEnter(path);
|
298 | if (this.shouldIgnore(path)) {
|
299 | return;
|
300 | }
|
301 | var that = this;
|
302 | enter.forEach(function (e) {
|
303 | e.call(that, path, node);
|
304 | });
|
305 | };
|
306 | var exit = function exit(path, node) {
|
307 | this.onExit(path, node);
|
308 | };
|
309 | return {
|
310 | enter: wrappedEntry,
|
311 | exit: exit
|
312 | };
|
313 | }
|
314 |
|
315 | function coverStatement(path) {
|
316 | this.insertStatementCounter(path);
|
317 | }
|
318 |
|
319 |
|
320 | function coverAssignmentPattern(path) {
|
321 | var n = path.node;
|
322 | var b = this.cov.newBranch('default-arg', n.loc);
|
323 | this.insertBranchCounter(path.get('right'), b);
|
324 | }
|
325 |
|
326 | function coverFunction(path) {
|
327 | this.insertFunctionCounter(path);
|
328 | }
|
329 |
|
330 | function coverVariableDeclarator(path) {
|
331 | this.insertStatementCounter(path.get('init'));
|
332 | }
|
333 |
|
334 | function skipInit(path) {
|
335 | if (path.node.init) {
|
336 | this.setAttr(path.node.init, 'skip-all', true);
|
337 | }
|
338 | }
|
339 |
|
340 | function makeBlock(path) {
|
341 | var T = this.types;
|
342 | if (!path.node) {
|
343 | path.replaceWith(T.blockStatement([]));
|
344 | }
|
345 | if (!path.isBlockStatement()) {
|
346 | path.replaceWith(T.blockStatement([path.node]));
|
347 | path.node.loc = path.node.body[0].loc;
|
348 | }
|
349 | }
|
350 |
|
351 | function blockProp(prop) {
|
352 | return function (path) {
|
353 | makeBlock.call(this, path.get(prop));
|
354 | };
|
355 | }
|
356 |
|
357 | function convertArrowExpression(path) {
|
358 | var n = path.node;
|
359 | var T = this.types;
|
360 | if (n.expression) {
|
361 | var bloc = n.body.loc;
|
362 | n.expression = false;
|
363 | n.body = T.blockStatement([T.returnStatement(n.body)]);
|
364 |
|
365 | n.body.loc = bloc;
|
366 |
|
367 |
|
368 | n.body.body[0].loc = bloc;
|
369 | }
|
370 | }
|
371 |
|
372 | function coverIfBranches(path) {
|
373 | var n = path.node,
|
374 | hint = this.hintFor(n),
|
375 | ignoreIf = hint === 'if',
|
376 | ignoreElse = hint === 'else',
|
377 | branch = this.cov.newBranch('if', n.loc);
|
378 |
|
379 | if (ignoreIf) {
|
380 | this.setAttr(n.consequent, 'skip-all', true);
|
381 | } else {
|
382 | this.insertBranchCounter(path.get('consequent'), branch, n.loc);
|
383 | }
|
384 | if (ignoreElse) {
|
385 | this.setAttr(n.alternate, 'skip-all', true);
|
386 | } else {
|
387 | this.insertBranchCounter(path.get('alternate'), branch, n.loc);
|
388 | }
|
389 | }
|
390 |
|
391 | function createSwitchBranch(path) {
|
392 | var b = this.cov.newBranch('switch', path.node.loc);
|
393 | this.setAttr(path.node, 'branchName', b);
|
394 | }
|
395 |
|
396 | function coverSwitchCase(path) {
|
397 | var T = this.types;
|
398 | var b = this.getAttr(path.parentPath.node, 'branchName');
|
399 |
|
400 | if (!b) {
|
401 | throw new Error('Unable to get switch branch name');
|
402 | }
|
403 | var increment = this.getBranchIncrement(b, path.node.loc);
|
404 | path.node.consequent.unshift(T.expressionStatement(increment));
|
405 | }
|
406 |
|
407 | function coverTernary(path) {
|
408 | var n = path.node,
|
409 | branch = this.cov.newBranch('cond-expr', path.node.loc),
|
410 | cHint = this.hintFor(n.consequent),
|
411 | aHint = this.hintFor(n.alternate);
|
412 |
|
413 | if (cHint !== 'next') {
|
414 | this.insertBranchCounter(path.get('consequent'), branch);
|
415 | }
|
416 | if (aHint !== 'next') {
|
417 | this.insertBranchCounter(path.get('alternate'), branch);
|
418 | }
|
419 | }
|
420 |
|
421 | function coverLogicalExpression(path) {
|
422 | var T = this.types;
|
423 | if (path.parentPath.node.type === "LogicalExpression") {
|
424 | return;
|
425 | }
|
426 | var leaves = [];
|
427 | this.findLeaves(path.node, leaves);
|
428 | var b = this.cov.newBranch("binary-expr", path.node.loc);
|
429 | for (var i = 0; i < leaves.length; i += 1) {
|
430 | var leaf = leaves[i];
|
431 | var hint = this.hintFor(leaf.node);
|
432 | if (hint === 'next') {
|
433 | continue;
|
434 | }
|
435 | var increment = this.getBranchIncrement(b, leaf.node.loc);
|
436 | if (!increment) {
|
437 | continue;
|
438 | }
|
439 | leaf.parent[leaf.property] = T.sequenceExpression([increment, leaf.node]);
|
440 | }
|
441 | }
|
442 |
|
443 | var codeVisitor = {
|
444 | ArrowFunctionExpression: entries(convertArrowExpression, coverFunction),
|
445 | AssignmentPattern: entries(coverAssignmentPattern),
|
446 | BlockStatement: entries(),
|
447 | ClassMethod: entries(coverFunction),
|
448 | ExpressionStatement: entries(coverStatement),
|
449 | BreakStatement: entries(coverStatement),
|
450 | ContinueStatement: entries(coverStatement),
|
451 | DebuggerStatement: entries(coverStatement),
|
452 | ReturnStatement: entries(coverStatement),
|
453 | ThrowStatement: entries(coverStatement),
|
454 | TryStatement: entries(coverStatement),
|
455 | VariableDeclaration: entries(),
|
456 | VariableDeclarator: entries(coverVariableDeclarator),
|
457 | IfStatement: entries(blockProp('consequent'), blockProp('alternate'), coverStatement, coverIfBranches),
|
458 | ForStatement: entries(blockProp('body'), skipInit, coverStatement),
|
459 | ForInStatement: entries(blockProp('body'), skipInit, coverStatement),
|
460 | ForOfStatement: entries(blockProp('body'), skipInit, coverStatement),
|
461 | WhileStatement: entries(blockProp('body'), coverStatement),
|
462 | DoWhileStatement: entries(blockProp('body'), coverStatement),
|
463 | SwitchStatement: entries(createSwitchBranch, coverStatement),
|
464 | SwitchCase: entries(coverSwitchCase),
|
465 | WithStatement: entries(blockProp('body'), coverStatement),
|
466 | FunctionDeclaration: entries(coverFunction),
|
467 | FunctionExpression: entries(coverFunction),
|
468 | LabeledStatement: entries(coverStatement),
|
469 | ConditionalExpression: entries(coverTernary),
|
470 | LogicalExpression: entries(coverLogicalExpression)
|
471 | };
|
472 |
|
473 | var coverageTemplate = (0, _babelTemplate2.default)('\n var COVERAGE_VAR = (function () {\n var path = PATH, \n hash = HASH,\n global = (new Function(\'return this\'))(),\n gcv = GLOBAL_COVERAGE_VAR,\n coverageData = INITIAL,\n coverage = global[gcv] || (global[gcv] = {});\n if (coverage[path] && coverage[path].hash === hash) {\n return coverage[path];\n }\n coverageData.hash = hash;\n return coverage[path] = coverageData;\n })();\n');
|
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 |
|
492 | function programVisitor(types) {
|
493 | var sourceFilePath = arguments.length <= 1 || arguments[1] === undefined ? 'unknown.js' : arguments[1];
|
494 | var opts = arguments.length <= 2 || arguments[2] === undefined ? { coverageVariable: '__coverage__' } : arguments[2];
|
495 |
|
496 | var T = types;
|
497 | var visitState = new VisitState(types, sourceFilePath);
|
498 | return {
|
499 | enter: function enter(path) {
|
500 | path.traverse(codeVisitor, visitState);
|
501 | },
|
502 | exit: function exit(path) {
|
503 | visitState.cov.freeze();
|
504 | var coverageData = visitState.cov.toJSON();
|
505 | var hash = (0, _crypto.createHash)(SHA).update(JSON.stringify(coverageData)).digest('hex');
|
506 | var coverageNode = T.valueToNode(coverageData);
|
507 | var cv = coverageTemplate({
|
508 | GLOBAL_COVERAGE_VAR: T.stringLiteral(opts.coverageVariable),
|
509 | COVERAGE_VAR: T.identifier(visitState.varName),
|
510 | PATH: T.stringLiteral(sourceFilePath),
|
511 | INITIAL: coverageNode,
|
512 | HASH: T.stringLiteral(hash)
|
513 | });
|
514 | path.node.body.unshift(cv);
|
515 | return {
|
516 | fileCoverage: coverageData,
|
517 | sourceMappingURL: visitState.sourceMappingURL
|
518 | };
|
519 | }
|
520 | };
|
521 | }
|
522 |
|
523 | exports.default = programVisitor; |
\ | No newline at end of file |