source: frontend/node_modules/istanbul-lib-instrument/src/visitor.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 28.2 KB
Line 
1const { createHash } = require('crypto');
2const { template } = require('@babel/core');
3const { defaults } = require('@istanbuljs/schema');
4const { SourceCoverage } = require('./source-coverage');
5const { SHA, MAGIC_KEY, MAGIC_VALUE } = require('./constants');
6
7// pattern for istanbul to ignore a section
8const COMMENT_RE = /^\s*istanbul\s+ignore\s+(if|else|next)(?=\W|$)/;
9// pattern for istanbul to ignore the whole file
10const COMMENT_FILE_RE = /^\s*istanbul\s+ignore\s+(file)(?=\W|$)/;
11// source map URL pattern
12const SOURCE_MAP_RE = /[#@]\s*sourceMappingURL=(.*)\s*$/m;
13
14// generate a variable name from hashing the supplied file path
15function genVar(filename) {
16 const hash = createHash(SHA);
17 hash.update(filename);
18 return 'cov_' + parseInt(hash.digest('hex').substr(0, 12), 16).toString(36);
19}
20
21// VisitState holds the state of the visitor, provides helper functions
22// and is the `this` for the individual coverage visitors.
23class VisitState {
24 constructor(
25 types,
26 sourceFilePath,
27 inputSourceMap,
28 ignoreClassMethods = [],
29 reportLogic = false
30 ) {
31 this.varName = genVar(sourceFilePath);
32 this.attrs = {};
33 this.nextIgnore = null;
34 this.cov = new SourceCoverage(sourceFilePath);
35
36 if (typeof inputSourceMap !== 'undefined') {
37 this.cov.inputSourceMap(inputSourceMap);
38 }
39 this.ignoreClassMethods = ignoreClassMethods;
40 this.types = types;
41 this.sourceMappingURL = null;
42 this.reportLogic = reportLogic;
43 }
44
45 // should we ignore the node? Yes, if specifically ignoring
46 // or if the node is generated.
47 shouldIgnore(path) {
48 return this.nextIgnore || !path.node.loc;
49 }
50
51 // extract the ignore comment hint (next|if|else) or null
52 hintFor(node) {
53 let hint = null;
54 if (node.leadingComments) {
55 node.leadingComments.forEach(c => {
56 const v = (
57 c.value || /* istanbul ignore next: paranoid check */ ''
58 ).trim();
59 const groups = v.match(COMMENT_RE);
60 if (groups) {
61 hint = groups[1];
62 }
63 });
64 }
65 return hint;
66 }
67
68 // extract a source map URL from comments and keep track of it
69 maybeAssignSourceMapURL(node) {
70 const extractURL = comments => {
71 if (!comments) {
72 return;
73 }
74 comments.forEach(c => {
75 const v = (
76 c.value || /* istanbul ignore next: paranoid check */ ''
77 ).trim();
78 const groups = v.match(SOURCE_MAP_RE);
79 if (groups) {
80 this.sourceMappingURL = groups[1];
81 }
82 });
83 };
84 extractURL(node.leadingComments);
85 extractURL(node.trailingComments);
86 }
87
88 // for these expressions the statement counter needs to be hoisted, so
89 // function name inference can be preserved
90 counterNeedsHoisting(path) {
91 return (
92 path.isFunctionExpression() ||
93 path.isArrowFunctionExpression() ||
94 path.isClassExpression()
95 );
96 }
97
98 // all the generic stuff that needs to be done on enter for every node
99 onEnter(path) {
100 const n = path.node;
101
102 this.maybeAssignSourceMapURL(n);
103
104 // if already ignoring, nothing more to do
105 if (this.nextIgnore !== null) {
106 return;
107 }
108 // check hint to see if ignore should be turned on
109 const hint = this.hintFor(n);
110 if (hint === 'next') {
111 this.nextIgnore = n;
112 return;
113 }
114 // else check custom node attribute set by a prior visitor
115 if (this.getAttr(path.node, 'skip-all') !== null) {
116 this.nextIgnore = n;
117 }
118
119 // else check for ignored class methods
120 if (
121 path.isFunctionExpression() &&
122 this.ignoreClassMethods.some(
123 name => path.node.id && name === path.node.id.name
124 )
125 ) {
126 this.nextIgnore = n;
127 return;
128 }
129 if (
130 path.isClassMethod() &&
131 this.ignoreClassMethods.some(name => name === path.node.key.name)
132 ) {
133 this.nextIgnore = n;
134 return;
135 }
136 }
137
138 // all the generic stuff on exit of a node,
139 // including reseting ignores and custom node attrs
140 onExit(path) {
141 // restore ignore status, if needed
142 if (path.node === this.nextIgnore) {
143 this.nextIgnore = null;
144 }
145 // nuke all attributes for the node
146 delete path.node.__cov__;
147 }
148
149 // set a node attribute for the supplied node
150 setAttr(node, name, value) {
151 node.__cov__ = node.__cov__ || {};
152 node.__cov__[name] = value;
153 }
154
155 // retrieve a node attribute for the supplied node or null
156 getAttr(node, name) {
157 const c = node.__cov__;
158 if (!c) {
159 return null;
160 }
161 return c[name];
162 }
163
164 //
165 increase(type, id, index) {
166 const T = this.types;
167 const wrap =
168 index !== null
169 ? // If `index` present, turn `x` into `x[index]`.
170 x => T.memberExpression(x, T.numericLiteral(index), true)
171 : x => x;
172 return T.updateExpression(
173 '++',
174 wrap(
175 T.memberExpression(
176 T.memberExpression(
177 T.callExpression(T.identifier(this.varName), []),
178 T.identifier(type)
179 ),
180 T.numericLiteral(id),
181 true
182 )
183 )
184 );
185 }
186
187 // Reads the logic expression conditions and conditionally increments truthy counter.
188 increaseTrue(type, id, index, node) {
189 const T = this.types;
190 const tempName = `${this.varName}_temp`;
191
192 return T.sequenceExpression([
193 T.assignmentExpression(
194 '=',
195 T.memberExpression(
196 T.callExpression(T.identifier(this.varName), []),
197 T.identifier(tempName)
198 ),
199 node // Only evaluates once.
200 ),
201 T.parenthesizedExpression(
202 T.conditionalExpression(
203 this.validateTrueNonTrivial(T, tempName),
204 this.increase(type, id, index),
205 T.nullLiteral()
206 )
207 ),
208 T.memberExpression(
209 T.callExpression(T.identifier(this.varName), []),
210 T.identifier(tempName)
211 )
212 ]);
213 }
214
215 validateTrueNonTrivial(T, tempName) {
216 return T.logicalExpression(
217 '&&',
218 T.memberExpression(
219 T.callExpression(T.identifier(this.varName), []),
220 T.identifier(tempName)
221 ),
222 T.logicalExpression(
223 '&&',
224 T.parenthesizedExpression(
225 T.logicalExpression(
226 '||',
227 T.unaryExpression(
228 '!',
229 T.callExpression(
230 T.memberExpression(
231 T.identifier('Array'),
232 T.identifier('isArray')
233 ),
234 [
235 T.memberExpression(
236 T.callExpression(
237 T.identifier(this.varName),
238 []
239 ),
240 T.identifier(tempName)
241 )
242 ]
243 )
244 ),
245 T.memberExpression(
246 T.memberExpression(
247 T.callExpression(
248 T.identifier(this.varName),
249 []
250 ),
251 T.identifier(tempName)
252 ),
253 T.identifier('length')
254 )
255 )
256 ),
257 T.parenthesizedExpression(
258 T.logicalExpression(
259 '||',
260 T.binaryExpression(
261 '!==',
262 T.callExpression(
263 T.memberExpression(
264 T.identifier('Object'),
265 T.identifier('getPrototypeOf')
266 ),
267 [
268 T.memberExpression(
269 T.callExpression(
270 T.identifier(this.varName),
271 []
272 ),
273 T.identifier(tempName)
274 )
275 ]
276 ),
277 T.memberExpression(
278 T.identifier('Object'),
279 T.identifier('prototype')
280 )
281 ),
282 T.memberExpression(
283 T.callExpression(
284 T.memberExpression(
285 T.identifier('Object'),
286 T.identifier('values')
287 ),
288 [
289 T.memberExpression(
290 T.callExpression(
291 T.identifier(this.varName),
292 []
293 ),
294 T.identifier(tempName)
295 )
296 ]
297 ),
298 T.identifier('length')
299 )
300 )
301 )
302 )
303 );
304 }
305
306 insertCounter(path, increment) {
307 const T = this.types;
308 if (path.isBlockStatement()) {
309 path.node.body.unshift(T.expressionStatement(increment));
310 } else if (path.isStatement()) {
311 path.insertBefore(T.expressionStatement(increment));
312 } else if (
313 this.counterNeedsHoisting(path) &&
314 T.isVariableDeclarator(path.parentPath)
315 ) {
316 // make an attempt to hoist the statement counter, so that
317 // function names are maintained.
318 const parent = path.parentPath.parentPath;
319 if (parent && T.isExportNamedDeclaration(parent.parentPath)) {
320 parent.parentPath.insertBefore(
321 T.expressionStatement(increment)
322 );
323 } else if (
324 parent &&
325 (T.isProgram(parent.parentPath) ||
326 T.isBlockStatement(parent.parentPath))
327 ) {
328 parent.insertBefore(T.expressionStatement(increment));
329 } else {
330 path.replaceWith(T.sequenceExpression([increment, path.node]));
331 }
332 } /* istanbul ignore else: not expected */ else if (
333 path.isExpression()
334 ) {
335 path.replaceWith(T.sequenceExpression([increment, path.node]));
336 } else {
337 console.error(
338 'Unable to insert counter for node type:',
339 path.node.type
340 );
341 }
342 }
343
344 insertStatementCounter(path) {
345 /* istanbul ignore if: paranoid check */
346 if (!(path.node && path.node.loc)) {
347 return;
348 }
349 const index = this.cov.newStatement(path.node.loc);
350 const increment = this.increase('s', index, null);
351 this.insertCounter(path, increment);
352 }
353
354 insertFunctionCounter(path) {
355 const T = this.types;
356 /* istanbul ignore if: paranoid check */
357 if (!(path.node && path.node.loc)) {
358 return;
359 }
360 const n = path.node;
361
362 let dloc = null;
363 // get location for declaration
364 switch (n.type) {
365 case 'FunctionDeclaration':
366 case 'FunctionExpression':
367 /* istanbul ignore else: paranoid check */
368 if (n.id) {
369 dloc = n.id.loc;
370 }
371 break;
372 }
373 if (!dloc) {
374 dloc = {
375 start: n.loc.start,
376 end: { line: n.loc.start.line, column: n.loc.start.column + 1 }
377 };
378 }
379
380 const name = path.node.id ? path.node.id.name : path.node.name;
381 const index = this.cov.newFunction(name, dloc, path.node.body.loc);
382 const increment = this.increase('f', index, null);
383 const body = path.get('body');
384 /* istanbul ignore else: not expected */
385 if (body.isBlockStatement()) {
386 body.node.body.unshift(T.expressionStatement(increment));
387 } else {
388 console.error(
389 'Unable to process function body node type:',
390 path.node.type
391 );
392 }
393 }
394
395 getBranchIncrement(branchName, loc) {
396 const index = this.cov.addBranchPath(branchName, loc);
397 return this.increase('b', branchName, index);
398 }
399
400 getBranchLogicIncrement(path, branchName, loc) {
401 const index = this.cov.addBranchPath(branchName, loc);
402 return [
403 this.increase('b', branchName, index),
404 this.increaseTrue('bT', branchName, index, path.node)
405 ];
406 }
407
408 insertBranchCounter(path, branchName, loc) {
409 const increment = this.getBranchIncrement(
410 branchName,
411 loc || path.node.loc
412 );
413 this.insertCounter(path, increment);
414 }
415
416 findLeaves(node, accumulator, parent, property) {
417 if (!node) {
418 return;
419 }
420 if (node.type === 'LogicalExpression') {
421 const hint = this.hintFor(node);
422 if (hint !== 'next') {
423 this.findLeaves(node.left, accumulator, node, 'left');
424 this.findLeaves(node.right, accumulator, node, 'right');
425 }
426 } else {
427 accumulator.push({
428 node,
429 parent,
430 property
431 });
432 }
433 }
434}
435
436// generic function that takes a set of visitor methods and
437// returns a visitor object with `enter` and `exit` properties,
438// such that:
439//
440// * standard entry processing is done
441// * the supplied visitors are called only when ignore is not in effect
442// This relieves them from worrying about ignore states and generated nodes.
443// * standard exit processing is done
444//
445function entries(...enter) {
446 // the enter function
447 const wrappedEntry = function(path, node) {
448 this.onEnter(path);
449 if (this.shouldIgnore(path)) {
450 return;
451 }
452 enter.forEach(e => {
453 e.call(this, path, node);
454 });
455 };
456 const exit = function(path, node) {
457 this.onExit(path, node);
458 };
459 return {
460 enter: wrappedEntry,
461 exit
462 };
463}
464
465function coverStatement(path) {
466 this.insertStatementCounter(path);
467}
468
469/* istanbul ignore next: no node.js support */
470function coverAssignmentPattern(path) {
471 const n = path.node;
472 const b = this.cov.newBranch('default-arg', n.loc);
473 this.insertBranchCounter(path.get('right'), b);
474}
475
476function coverFunction(path) {
477 this.insertFunctionCounter(path);
478}
479
480function coverVariableDeclarator(path) {
481 this.insertStatementCounter(path.get('init'));
482}
483
484function coverClassPropDeclarator(path) {
485 this.insertStatementCounter(path.get('value'));
486}
487
488function makeBlock(path) {
489 const T = this.types;
490 if (!path.node) {
491 path.replaceWith(T.blockStatement([]));
492 }
493 if (!path.isBlockStatement()) {
494 path.replaceWith(T.blockStatement([path.node]));
495 path.node.loc = path.node.body[0].loc;
496 path.node.body[0].leadingComments = path.node.leadingComments;
497 path.node.leadingComments = undefined;
498 }
499}
500
501function blockProp(prop) {
502 return function(path) {
503 makeBlock.call(this, path.get(prop));
504 };
505}
506
507function makeParenthesizedExpressionForNonIdentifier(path) {
508 const T = this.types;
509 if (path.node && !path.isIdentifier()) {
510 path.replaceWith(T.parenthesizedExpression(path.node));
511 }
512}
513
514function parenthesizedExpressionProp(prop) {
515 return function(path) {
516 makeParenthesizedExpressionForNonIdentifier.call(this, path.get(prop));
517 };
518}
519
520function convertArrowExpression(path) {
521 const n = path.node;
522 const T = this.types;
523 if (!T.isBlockStatement(n.body)) {
524 const bloc = n.body.loc;
525 if (n.expression === true) {
526 n.expression = false;
527 }
528 n.body = T.blockStatement([T.returnStatement(n.body)]);
529 // restore body location
530 n.body.loc = bloc;
531 // set up the location for the return statement so it gets
532 // instrumented
533 n.body.body[0].loc = bloc;
534 }
535}
536
537function coverIfBranches(path) {
538 const n = path.node;
539 const hint = this.hintFor(n);
540 const ignoreIf = hint === 'if';
541 const ignoreElse = hint === 'else';
542 const branch = this.cov.newBranch('if', n.loc);
543
544 if (ignoreIf) {
545 this.setAttr(n.consequent, 'skip-all', true);
546 } else {
547 this.insertBranchCounter(path.get('consequent'), branch, n.loc);
548 }
549 if (ignoreElse) {
550 this.setAttr(n.alternate, 'skip-all', true);
551 } else {
552 this.insertBranchCounter(path.get('alternate'), branch);
553 }
554}
555
556function createSwitchBranch(path) {
557 const b = this.cov.newBranch('switch', path.node.loc);
558 this.setAttr(path.node, 'branchName', b);
559}
560
561function coverSwitchCase(path) {
562 const T = this.types;
563 const b = this.getAttr(path.parentPath.node, 'branchName');
564 /* istanbul ignore if: paranoid check */
565 if (b === null) {
566 throw new Error('Unable to get switch branch name');
567 }
568 const increment = this.getBranchIncrement(b, path.node.loc);
569 path.node.consequent.unshift(T.expressionStatement(increment));
570}
571
572function coverTernary(path) {
573 const n = path.node;
574 const branch = this.cov.newBranch('cond-expr', path.node.loc);
575 const cHint = this.hintFor(n.consequent);
576 const aHint = this.hintFor(n.alternate);
577
578 if (cHint !== 'next') {
579 this.insertBranchCounter(path.get('consequent'), branch);
580 }
581 if (aHint !== 'next') {
582 this.insertBranchCounter(path.get('alternate'), branch);
583 }
584}
585
586function coverLogicalExpression(path) {
587 const T = this.types;
588 if (path.parentPath.node.type === 'LogicalExpression') {
589 return; // already processed
590 }
591 const leaves = [];
592 this.findLeaves(path.node, leaves);
593 const b = this.cov.newBranch(
594 'binary-expr',
595 path.node.loc,
596 this.reportLogic
597 );
598 for (let i = 0; i < leaves.length; i += 1) {
599 const leaf = leaves[i];
600 const hint = this.hintFor(leaf.node);
601 if (hint === 'next') {
602 continue;
603 }
604
605 if (this.reportLogic) {
606 const increment = this.getBranchLogicIncrement(
607 leaf,
608 b,
609 leaf.node.loc
610 );
611 if (!increment[0]) {
612 continue;
613 }
614 leaf.parent[leaf.property] = T.sequenceExpression([
615 increment[0],
616 increment[1]
617 ]);
618 continue;
619 }
620
621 const increment = this.getBranchIncrement(b, leaf.node.loc);
622 if (!increment) {
623 continue;
624 }
625 leaf.parent[leaf.property] = T.sequenceExpression([
626 increment,
627 leaf.node
628 ]);
629 }
630}
631
632const codeVisitor = {
633 ArrowFunctionExpression: entries(convertArrowExpression, coverFunction),
634 AssignmentPattern: entries(coverAssignmentPattern),
635 BlockStatement: entries(), // ignore processing only
636 ExportDefaultDeclaration: entries(), // ignore processing only
637 ExportNamedDeclaration: entries(), // ignore processing only
638 ClassMethod: entries(coverFunction),
639 ClassDeclaration: entries(parenthesizedExpressionProp('superClass')),
640 ClassProperty: entries(coverClassPropDeclarator),
641 ClassPrivateProperty: entries(coverClassPropDeclarator),
642 ObjectMethod: entries(coverFunction),
643 ExpressionStatement: entries(coverStatement),
644 BreakStatement: entries(coverStatement),
645 ContinueStatement: entries(coverStatement),
646 DebuggerStatement: entries(coverStatement),
647 ReturnStatement: entries(coverStatement),
648 ThrowStatement: entries(coverStatement),
649 TryStatement: entries(coverStatement),
650 VariableDeclaration: entries(), // ignore processing only
651 VariableDeclarator: entries(coverVariableDeclarator),
652 IfStatement: entries(
653 blockProp('consequent'),
654 blockProp('alternate'),
655 coverStatement,
656 coverIfBranches
657 ),
658 ForStatement: entries(blockProp('body'), coverStatement),
659 ForInStatement: entries(blockProp('body'), coverStatement),
660 ForOfStatement: entries(blockProp('body'), coverStatement),
661 WhileStatement: entries(blockProp('body'), coverStatement),
662 DoWhileStatement: entries(blockProp('body'), coverStatement),
663 SwitchStatement: entries(createSwitchBranch, coverStatement),
664 SwitchCase: entries(coverSwitchCase),
665 WithStatement: entries(blockProp('body'), coverStatement),
666 FunctionDeclaration: entries(coverFunction),
667 FunctionExpression: entries(coverFunction),
668 LabeledStatement: entries(coverStatement),
669 ConditionalExpression: entries(coverTernary),
670 LogicalExpression: entries(coverLogicalExpression)
671};
672const globalTemplateAlteredFunction = template(`
673 var Function = (function(){}).constructor;
674 var global = (new Function(GLOBAL_COVERAGE_SCOPE))();
675`);
676const globalTemplateFunction = template(`
677 var global = (new Function(GLOBAL_COVERAGE_SCOPE))();
678`);
679const globalTemplateVariable = template(`
680 var global = GLOBAL_COVERAGE_SCOPE;
681`);
682// the template to insert at the top of the program.
683const coverageTemplate = template(
684 `
685 function COVERAGE_FUNCTION () {
686 var path = PATH;
687 var hash = HASH;
688 GLOBAL_COVERAGE_TEMPLATE
689 var gcv = GLOBAL_COVERAGE_VAR;
690 var coverageData = INITIAL;
691 var coverage = global[gcv] || (global[gcv] = {});
692 if (!coverage[path] || coverage[path].hash !== hash) {
693 coverage[path] = coverageData;
694 }
695
696 var actualCoverage = coverage[path];
697 {
698 // @ts-ignore
699 COVERAGE_FUNCTION = function () {
700 return actualCoverage;
701 }
702 }
703
704 return actualCoverage;
705 }
706`,
707 { preserveComments: true }
708);
709// the rewire plugin (and potentially other babel middleware)
710// may cause files to be instrumented twice, see:
711// https://github.com/istanbuljs/babel-plugin-istanbul/issues/94
712// we should only instrument code for coverage the first time
713// it's run through istanbul-lib-instrument.
714function alreadyInstrumented(path, visitState) {
715 return path.scope.hasBinding(visitState.varName);
716}
717function shouldIgnoreFile(programNode) {
718 return (
719 programNode.parent &&
720 programNode.parent.comments.some(c => COMMENT_FILE_RE.test(c.value))
721 );
722}
723
724/**
725 * programVisitor is a `babel` adaptor for instrumentation.
726 * It returns an object with two methods `enter` and `exit`.
727 * These should be assigned to or called from `Program` entry and exit functions
728 * in a babel visitor.
729 * These functions do not make assumptions about the state set by Babel and thus
730 * can be used in a context other than a Babel plugin.
731 *
732 * The exit function returns an object that currently has the following keys:
733 *
734 * `fileCoverage` - the file coverage object created for the source file.
735 * `sourceMappingURL` - any source mapping URL found when processing the file.
736 *
737 * @param {Object} types - an instance of babel-types.
738 * @param {string} sourceFilePath - the path to source file.
739 * @param {Object} opts - additional options.
740 * @param {string} [opts.coverageVariable=__coverage__] the global coverage variable name.
741 * @param {boolean} [opts.reportLogic=false] report boolean value of logical expressions.
742 * @param {string} [opts.coverageGlobalScope=this] the global coverage variable scope.
743 * @param {boolean} [opts.coverageGlobalScopeFunc=true] use an evaluated function to find coverageGlobalScope.
744 * @param {Array} [opts.ignoreClassMethods=[]] names of methods to ignore by default on classes.
745 * @param {object} [opts.inputSourceMap=undefined] the input source map, that maps the uninstrumented code back to the
746 * original code.
747 */
748function programVisitor(types, sourceFilePath = 'unknown.js', opts = {}) {
749 const T = types;
750 opts = {
751 ...defaults.instrumentVisitor,
752 ...opts
753 };
754 const visitState = new VisitState(
755 types,
756 sourceFilePath,
757 opts.inputSourceMap,
758 opts.ignoreClassMethods,
759 opts.reportLogic
760 );
761 return {
762 enter(path) {
763 if (shouldIgnoreFile(path.find(p => p.isProgram()))) {
764 return;
765 }
766 if (alreadyInstrumented(path, visitState)) {
767 return;
768 }
769 path.traverse(codeVisitor, visitState);
770 },
771 exit(path) {
772 if (alreadyInstrumented(path, visitState)) {
773 return;
774 }
775 visitState.cov.freeze();
776 const coverageData = visitState.cov.toJSON();
777 if (shouldIgnoreFile(path.find(p => p.isProgram()))) {
778 return {
779 fileCoverage: coverageData,
780 sourceMappingURL: visitState.sourceMappingURL
781 };
782 }
783 coverageData[MAGIC_KEY] = MAGIC_VALUE;
784 const hash = createHash(SHA)
785 .update(JSON.stringify(coverageData))
786 .digest('hex');
787 coverageData.hash = hash;
788 if (
789 coverageData.inputSourceMap &&
790 Object.getPrototypeOf(coverageData.inputSourceMap) !==
791 Object.prototype
792 ) {
793 coverageData.inputSourceMap = {
794 ...coverageData.inputSourceMap
795 };
796 }
797 const coverageNode = T.valueToNode(coverageData);
798 delete coverageData[MAGIC_KEY];
799 delete coverageData.hash;
800 let gvTemplate;
801 if (opts.coverageGlobalScopeFunc) {
802 if (path.scope.getBinding('Function')) {
803 gvTemplate = globalTemplateAlteredFunction({
804 GLOBAL_COVERAGE_SCOPE: T.stringLiteral(
805 'return ' + opts.coverageGlobalScope
806 )
807 });
808 } else {
809 gvTemplate = globalTemplateFunction({
810 GLOBAL_COVERAGE_SCOPE: T.stringLiteral(
811 'return ' + opts.coverageGlobalScope
812 )
813 });
814 }
815 } else {
816 gvTemplate = globalTemplateVariable({
817 GLOBAL_COVERAGE_SCOPE: opts.coverageGlobalScope
818 });
819 }
820 const cv = coverageTemplate({
821 GLOBAL_COVERAGE_VAR: T.stringLiteral(opts.coverageVariable),
822 GLOBAL_COVERAGE_TEMPLATE: gvTemplate,
823 COVERAGE_FUNCTION: T.identifier(visitState.varName),
824 PATH: T.stringLiteral(sourceFilePath),
825 INITIAL: coverageNode,
826 HASH: T.stringLiteral(hash)
827 });
828 // explicitly call this.varName to ensure coverage is always initialized
829 path.node.body.unshift(
830 T.expressionStatement(
831 T.callExpression(T.identifier(visitState.varName), [])
832 )
833 );
834 path.node.body.unshift(cv);
835 return {
836 fileCoverage: coverageData,
837 sourceMappingURL: visitState.sourceMappingURL
838 };
839 }
840 };
841}
842
843module.exports = programVisitor;
Note: See TracBrowser for help on using the repository browser.