673 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			673 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | "use strict"; | ||
|  | 
 | ||
|  | Object.defineProperty(exports, "__esModule", { | ||
|  |   value: true | ||
|  | }); | ||
|  | exports.default = void 0; | ||
|  | 
 | ||
|  | var _crypto = require("crypto"); | ||
|  | 
 | ||
|  | var _template = _interopRequireDefault(require("@babel/template")); | ||
|  | 
 | ||
|  | var _sourceCoverage = require("./source-coverage"); | ||
|  | 
 | ||
|  | var _constants = require("./constants"); | ||
|  | 
 | ||
|  | var _instrumenter = require("./instrumenter"); | ||
|  | 
 | ||
|  | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
|  | 
 | ||
|  | // pattern for istanbul to ignore a section
 | ||
|  | const COMMENT_RE = /^\s*istanbul\s+ignore\s+(if|else|next)(?=\W|$)/; // pattern for istanbul to ignore the whole file
 | ||
|  | 
 | ||
|  | const COMMENT_FILE_RE = /^\s*istanbul\s+ignore\s+(file)(?=\W|$)/; // source map URL pattern
 | ||
|  | 
 | ||
|  | const SOURCE_MAP_RE = /[#@]\s*sourceMappingURL=(.*)\s*$/m; // generate a variable name from hashing the supplied file path
 | ||
|  | 
 | ||
|  | function genVar(filename) { | ||
|  |   const hash = (0, _crypto.createHash)(_constants.SHA); | ||
|  |   hash.update(filename); | ||
|  |   return 'cov_' + parseInt(hash.digest('hex').substr(0, 12), 16).toString(36); | ||
|  | } // VisitState holds the state of the visitor, provides helper functions
 | ||
|  | // and is the `this` for the individual coverage visitors.
 | ||
|  | 
 | ||
|  | 
 | ||
|  | class VisitState { | ||
|  |   constructor(types, sourceFilePath, inputSourceMap, ignoreClassMethods = []) { | ||
|  |     this.varName = genVar(sourceFilePath); | ||
|  |     this.attrs = {}; | ||
|  |     this.nextIgnore = null; | ||
|  |     this.cov = new _sourceCoverage.SourceCoverage(sourceFilePath); | ||
|  | 
 | ||
|  |     if (typeof inputSourceMap !== 'undefined') { | ||
|  |       this.cov.inputSourceMap(inputSourceMap); | ||
|  |     } | ||
|  | 
 | ||
|  |     this.ignoreClassMethods = ignoreClassMethods; | ||
|  |     this.types = types; | ||
|  |     this.sourceMappingURL = null; | ||
|  |   } // should we ignore the node? Yes, if specifically ignoring
 | ||
|  |   // or if the node is generated.
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   shouldIgnore(path) { | ||
|  |     return this.nextIgnore || !path.node.loc; | ||
|  |   } // extract the ignore comment hint (next|if|else) or null
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   hintFor(node) { | ||
|  |     let hint = null; | ||
|  | 
 | ||
|  |     if (node.leadingComments) { | ||
|  |       node.leadingComments.forEach(c => { | ||
|  |         const v = (c.value || | ||
|  |         /* istanbul ignore next: paranoid check */ | ||
|  |         '').trim(); | ||
|  |         const groups = v.match(COMMENT_RE); | ||
|  | 
 | ||
|  |         if (groups) { | ||
|  |           hint = groups[1]; | ||
|  |         } | ||
|  |       }); | ||
|  |     } | ||
|  | 
 | ||
|  |     return hint; | ||
|  |   } // extract a source map URL from comments and keep track of it
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   maybeAssignSourceMapURL(node) { | ||
|  |     const extractURL = comments => { | ||
|  |       if (!comments) { | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       comments.forEach(c => { | ||
|  |         const v = (c.value || | ||
|  |         /* istanbul ignore next: paranoid check */ | ||
|  |         '').trim(); | ||
|  |         const groups = v.match(SOURCE_MAP_RE); | ||
|  | 
 | ||
|  |         if (groups) { | ||
|  |           this.sourceMappingURL = groups[1]; | ||
|  |         } | ||
|  |       }); | ||
|  |     }; | ||
|  | 
 | ||
|  |     extractURL(node.leadingComments); | ||
|  |     extractURL(node.trailingComments); | ||
|  |   } // for these expressions the statement counter needs to be hoisted, so
 | ||
|  |   // function name inference can be preserved
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   counterNeedsHoisting(path) { | ||
|  |     return path.isFunctionExpression() || path.isArrowFunctionExpression() || path.isClassExpression(); | ||
|  |   } // all the generic stuff that needs to be done on enter for every node
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   onEnter(path) { | ||
|  |     const n = path.node; | ||
|  |     this.maybeAssignSourceMapURL(n); // if already ignoring, nothing more to do
 | ||
|  | 
 | ||
|  |     if (this.nextIgnore !== null) { | ||
|  |       return; | ||
|  |     } // check hint to see if ignore should be turned on
 | ||
|  | 
 | ||
|  | 
 | ||
|  |     const hint = this.hintFor(n); | ||
|  | 
 | ||
|  |     if (hint === 'next') { | ||
|  |       this.nextIgnore = n; | ||
|  |       return; | ||
|  |     } // else check custom node attribute set by a prior visitor
 | ||
|  | 
 | ||
|  | 
 | ||
|  |     if (this.getAttr(path.node, 'skip-all') !== null) { | ||
|  |       this.nextIgnore = n; | ||
|  |     } // else check for ignored class methods
 | ||
|  | 
 | ||
|  | 
 | ||
|  |     if (path.isFunctionExpression() && this.ignoreClassMethods.some(name => path.node.id && name === path.node.id.name)) { | ||
|  |       this.nextIgnore = n; | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (path.isClassMethod() && this.ignoreClassMethods.some(name => name === path.node.key.name)) { | ||
|  |       this.nextIgnore = n; | ||
|  |       return; | ||
|  |     } | ||
|  |   } // all the generic stuff on exit of a node,
 | ||
|  |   // including reseting ignores and custom node attrs
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   onExit(path) { | ||
|  |     // restore ignore status, if needed
 | ||
|  |     if (path.node === this.nextIgnore) { | ||
|  |       this.nextIgnore = null; | ||
|  |     } // nuke all attributes for the node
 | ||
|  | 
 | ||
|  | 
 | ||
|  |     delete path.node.__cov__; | ||
|  |   } // set a node attribute for the supplied node
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   setAttr(node, name, value) { | ||
|  |     node.__cov__ = node.__cov__ || {}; | ||
|  |     node.__cov__[name] = value; | ||
|  |   } // retrieve a node attribute for the supplied node or null
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   getAttr(node, name) { | ||
|  |     const c = node.__cov__; | ||
|  | 
 | ||
|  |     if (!c) { | ||
|  |       return null; | ||
|  |     } | ||
|  | 
 | ||
|  |     return c[name]; | ||
|  |   } //
 | ||
|  | 
 | ||
|  | 
 | ||
|  |   increase(type, id, index) { | ||
|  |     const T = this.types; | ||
|  |     const wrap = index !== null ? // If `index` present, turn `x` into `x[index]`.
 | ||
|  |     x => T.memberExpression(x, T.numericLiteral(index), true) : x => x; | ||
|  |     return T.updateExpression('++', wrap(T.memberExpression(T.memberExpression(T.identifier(this.varName), T.identifier(type)), T.numericLiteral(id), true))); | ||
|  |   } | ||
|  | 
 | ||
|  |   insertCounter(path, increment) { | ||
|  |     const T = this.types; | ||
|  | 
 | ||
|  |     if (path.isBlockStatement()) { | ||
|  |       path.node.body.unshift(T.expressionStatement(increment)); | ||
|  |     } else if (path.isStatement()) { | ||
|  |       path.insertBefore(T.expressionStatement(increment)); | ||
|  |     } else if (this.counterNeedsHoisting(path) && T.isVariableDeclarator(path.parentPath)) { | ||
|  |       // make an attempt to hoist the statement counter, so that
 | ||
|  |       // function names are maintained.
 | ||
|  |       const parent = path.parentPath.parentPath; | ||
|  | 
 | ||
|  |       if (parent && T.isExportNamedDeclaration(parent.parentPath)) { | ||
|  |         parent.parentPath.insertBefore(T.expressionStatement(increment)); | ||
|  |       } else if (parent && (T.isProgram(parent.parentPath) || T.isBlockStatement(parent.parentPath))) { | ||
|  |         parent.insertBefore(T.expressionStatement(increment)); | ||
|  |       } else { | ||
|  |         path.replaceWith(T.sequenceExpression([increment, path.node])); | ||
|  |       } | ||
|  |     } | ||
|  |     /* istanbul ignore else: not expected */ | ||
|  |     else if (path.isExpression()) { | ||
|  |         path.replaceWith(T.sequenceExpression([increment, path.node])); | ||
|  |       } else { | ||
|  |         console.error('Unable to insert counter for node type:', path.node.type); | ||
|  |       } | ||
|  |   } | ||
|  | 
 | ||
|  |   insertStatementCounter(path) { | ||
|  |     /* istanbul ignore if: paranoid check */ | ||
|  |     if (!(path.node && path.node.loc)) { | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     const index = this.cov.newStatement(path.node.loc); | ||
|  |     const increment = this.increase('s', index, null); | ||
|  |     this.insertCounter(path, increment); | ||
|  |   } | ||
|  | 
 | ||
|  |   insertFunctionCounter(path) { | ||
|  |     const T = this.types; | ||
|  |     /* istanbul ignore if: paranoid check */ | ||
|  | 
 | ||
|  |     if (!(path.node && path.node.loc)) { | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     const n = path.node; | ||
|  |     let dloc = null; // get location for declaration
 | ||
|  | 
 | ||
|  |     switch (n.type) { | ||
|  |       case 'FunctionDeclaration': | ||
|  |         /* istanbul ignore else: paranoid check */ | ||
|  |         if (n.id) { | ||
|  |           dloc = n.id.loc; | ||
|  |         } | ||
|  | 
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       case 'FunctionExpression': | ||
|  |         if (n.id) { | ||
|  |           dloc = n.id.loc; | ||
|  |         } | ||
|  | 
 | ||
|  |         break; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!dloc) { | ||
|  |       dloc = { | ||
|  |         start: n.loc.start, | ||
|  |         end: { | ||
|  |           line: n.loc.start.line, | ||
|  |           column: n.loc.start.column + 1 | ||
|  |         } | ||
|  |       }; | ||
|  |     } | ||
|  | 
 | ||
|  |     const name = path.node.id ? path.node.id.name : path.node.name; | ||
|  |     const index = this.cov.newFunction(name, dloc, path.node.body.loc); | ||
|  |     const increment = this.increase('f', index, null); | ||
|  |     const body = path.get('body'); | ||
|  |     /* istanbul ignore else: not expected */ | ||
|  | 
 | ||
|  |     if (body.isBlockStatement()) { | ||
|  |       body.node.body.unshift(T.expressionStatement(increment)); | ||
|  |     } else { | ||
|  |       console.error('Unable to process function body node type:', path.node.type); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   getBranchIncrement(branchName, loc) { | ||
|  |     const index = this.cov.addBranchPath(branchName, loc); | ||
|  |     return this.increase('b', branchName, index); | ||
|  |   } | ||
|  | 
 | ||
|  |   insertBranchCounter(path, branchName, loc) { | ||
|  |     const increment = this.getBranchIncrement(branchName, loc || path.node.loc); | ||
|  |     this.insertCounter(path, increment); | ||
|  |   } | ||
|  | 
 | ||
|  |   findLeaves(node, accumulator, parent, property) { | ||
|  |     if (!node) { | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.type === 'LogicalExpression') { | ||
|  |       const hint = this.hintFor(node); | ||
|  | 
 | ||
|  |       if (hint !== 'next') { | ||
|  |         this.findLeaves(node.left, accumulator, node, 'left'); | ||
|  |         this.findLeaves(node.right, accumulator, node, 'right'); | ||
|  |       } | ||
|  |     } else { | ||
|  |       accumulator.push({ | ||
|  |         node, | ||
|  |         parent, | ||
|  |         property | ||
|  |       }); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  | } // generic function that takes a set of visitor methods and
 | ||
|  | // returns a visitor object with `enter` and `exit` properties,
 | ||
|  | // such that:
 | ||
|  | //
 | ||
|  | // * standard entry processing is done
 | ||
|  | // * the supplied visitors are called only when ignore is not in effect
 | ||
|  | //   This relieves them from worrying about ignore states and generated nodes.
 | ||
|  | // * standard exit processing is done
 | ||
|  | //
 | ||
|  | 
 | ||
|  | 
 | ||
|  | function entries(...enter) { | ||
|  |   // the enter function
 | ||
|  |   const wrappedEntry = function wrappedEntry(path, node) { | ||
|  |     this.onEnter(path); | ||
|  | 
 | ||
|  |     if (this.shouldIgnore(path)) { | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     enter.forEach(e => { | ||
|  |       e.call(this, path, node); | ||
|  |     }); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const exit = function exit(path, node) { | ||
|  |     this.onExit(path, node); | ||
|  |   }; | ||
|  | 
 | ||
|  |   return { | ||
|  |     enter: wrappedEntry, | ||
|  |     exit | ||
|  |   }; | ||
|  | } | ||
|  | 
 | ||
|  | function coverStatement(path) { | ||
|  |   this.insertStatementCounter(path); | ||
|  | } | ||
|  | /* istanbul ignore next: no node.js support */ | ||
|  | 
 | ||
|  | 
 | ||
|  | function coverAssignmentPattern(path) { | ||
|  |   const n = path.node; | ||
|  |   const b = this.cov.newBranch('default-arg', n.loc); | ||
|  |   this.insertBranchCounter(path.get('right'), b); | ||
|  | } | ||
|  | 
 | ||
|  | function coverFunction(path) { | ||
|  |   this.insertFunctionCounter(path); | ||
|  | } | ||
|  | 
 | ||
|  | function coverVariableDeclarator(path) { | ||
|  |   this.insertStatementCounter(path.get('init')); | ||
|  | } | ||
|  | 
 | ||
|  | function coverClassPropDeclarator(path) { | ||
|  |   this.insertStatementCounter(path.get('value')); | ||
|  | } | ||
|  | 
 | ||
|  | function makeBlock(path) { | ||
|  |   const T = this.types; | ||
|  | 
 | ||
|  |   if (!path.node) { | ||
|  |     path.replaceWith(T.blockStatement([])); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!path.isBlockStatement()) { | ||
|  |     path.replaceWith(T.blockStatement([path.node])); | ||
|  |     path.node.loc = path.node.body[0].loc; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function blockProp(prop) { | ||
|  |   return function (path) { | ||
|  |     makeBlock.call(this, path.get(prop)); | ||
|  |   }; | ||
|  | } | ||
|  | 
 | ||
|  | function makeParenthesizedExpressionForNonIdentifier(path) { | ||
|  |   const T = this.types; | ||
|  | 
 | ||
|  |   if (path.node && !path.isIdentifier()) { | ||
|  |     path.replaceWith(T.parenthesizedExpression(path.node)); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function parenthesizedExpressionProp(prop) { | ||
|  |   return function (path) { | ||
|  |     makeParenthesizedExpressionForNonIdentifier.call(this, path.get(prop)); | ||
|  |   }; | ||
|  | } | ||
|  | 
 | ||
|  | function convertArrowExpression(path) { | ||
|  |   const n = path.node; | ||
|  |   const T = this.types; | ||
|  | 
 | ||
|  |   if (!T.isBlockStatement(n.body)) { | ||
|  |     const bloc = n.body.loc; | ||
|  | 
 | ||
|  |     if (n.expression === true) { | ||
|  |       n.expression = false; | ||
|  |     } | ||
|  | 
 | ||
|  |     n.body = T.blockStatement([T.returnStatement(n.body)]); // restore body location
 | ||
|  | 
 | ||
|  |     n.body.loc = bloc; // set up the location for the return statement so it gets
 | ||
|  |     // instrumented
 | ||
|  | 
 | ||
|  |     n.body.body[0].loc = bloc; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function coverIfBranches(path) { | ||
|  |   const n = path.node; | ||
|  |   const hint = this.hintFor(n); | ||
|  |   const ignoreIf = hint === 'if'; | ||
|  |   const ignoreElse = hint === 'else'; | ||
|  |   const branch = this.cov.newBranch('if', n.loc); | ||
|  | 
 | ||
|  |   if (ignoreIf) { | ||
|  |     this.setAttr(n.consequent, 'skip-all', true); | ||
|  |   } else { | ||
|  |     this.insertBranchCounter(path.get('consequent'), branch, n.loc); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (ignoreElse) { | ||
|  |     this.setAttr(n.alternate, 'skip-all', true); | ||
|  |   } else { | ||
|  |     this.insertBranchCounter(path.get('alternate'), branch, n.loc); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function createSwitchBranch(path) { | ||
|  |   const b = this.cov.newBranch('switch', path.node.loc); | ||
|  |   this.setAttr(path.node, 'branchName', b); | ||
|  | } | ||
|  | 
 | ||
|  | function coverSwitchCase(path) { | ||
|  |   const T = this.types; | ||
|  |   const b = this.getAttr(path.parentPath.node, 'branchName'); | ||
|  |   /* istanbul ignore if: paranoid check */ | ||
|  | 
 | ||
|  |   if (b === null) { | ||
|  |     throw new Error('Unable to get switch branch name'); | ||
|  |   } | ||
|  | 
 | ||
|  |   const increment = this.getBranchIncrement(b, path.node.loc); | ||
|  |   path.node.consequent.unshift(T.expressionStatement(increment)); | ||
|  | } | ||
|  | 
 | ||
|  | function coverTernary(path) { | ||
|  |   const n = path.node; | ||
|  |   const branch = this.cov.newBranch('cond-expr', path.node.loc); | ||
|  |   const cHint = this.hintFor(n.consequent); | ||
|  |   const aHint = this.hintFor(n.alternate); | ||
|  | 
 | ||
|  |   if (cHint !== 'next') { | ||
|  |     this.insertBranchCounter(path.get('consequent'), branch); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (aHint !== 'next') { | ||
|  |     this.insertBranchCounter(path.get('alternate'), branch); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function coverLogicalExpression(path) { | ||
|  |   const T = this.types; | ||
|  | 
 | ||
|  |   if (path.parentPath.node.type === 'LogicalExpression') { | ||
|  |     return; // already processed
 | ||
|  |   } | ||
|  | 
 | ||
|  |   const leaves = []; | ||
|  |   this.findLeaves(path.node, leaves); | ||
|  |   const b = this.cov.newBranch('binary-expr', path.node.loc); | ||
|  | 
 | ||
|  |   for (let i = 0; i < leaves.length; i += 1) { | ||
|  |     const leaf = leaves[i]; | ||
|  |     const hint = this.hintFor(leaf.node); | ||
|  | 
 | ||
|  |     if (hint === 'next') { | ||
|  |       continue; | ||
|  |     } | ||
|  | 
 | ||
|  |     const increment = this.getBranchIncrement(b, leaf.node.loc); | ||
|  | 
 | ||
|  |     if (!increment) { | ||
|  |       continue; | ||
|  |     } | ||
|  | 
 | ||
|  |     leaf.parent[leaf.property] = T.sequenceExpression([increment, leaf.node]); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | const codeVisitor = { | ||
|  |   ArrowFunctionExpression: entries(convertArrowExpression, coverFunction), | ||
|  |   AssignmentPattern: entries(coverAssignmentPattern), | ||
|  |   BlockStatement: entries(), | ||
|  |   // ignore processing only
 | ||
|  |   ExportDefaultDeclaration: entries(), | ||
|  |   // ignore processing only
 | ||
|  |   ExportNamedDeclaration: entries(), | ||
|  |   // ignore processing only
 | ||
|  |   ClassMethod: entries(coverFunction), | ||
|  |   ClassDeclaration: entries(parenthesizedExpressionProp('superClass')), | ||
|  |   ClassProperty: entries(coverClassPropDeclarator), | ||
|  |   ClassPrivateProperty: entries(coverClassPropDeclarator), | ||
|  |   ObjectMethod: entries(coverFunction), | ||
|  |   ExpressionStatement: entries(coverStatement), | ||
|  |   BreakStatement: entries(coverStatement), | ||
|  |   ContinueStatement: entries(coverStatement), | ||
|  |   DebuggerStatement: entries(coverStatement), | ||
|  |   ReturnStatement: entries(coverStatement), | ||
|  |   ThrowStatement: entries(coverStatement), | ||
|  |   TryStatement: entries(coverStatement), | ||
|  |   VariableDeclaration: entries(), | ||
|  |   // ignore processing only
 | ||
|  |   VariableDeclarator: entries(coverVariableDeclarator), | ||
|  |   IfStatement: entries(blockProp('consequent'), blockProp('alternate'), coverStatement, coverIfBranches), | ||
|  |   ForStatement: entries(blockProp('body'), coverStatement), | ||
|  |   ForInStatement: entries(blockProp('body'), coverStatement), | ||
|  |   ForOfStatement: entries(blockProp('body'), coverStatement), | ||
|  |   WhileStatement: entries(blockProp('body'), coverStatement), | ||
|  |   DoWhileStatement: entries(blockProp('body'), coverStatement), | ||
|  |   SwitchStatement: entries(createSwitchBranch, coverStatement), | ||
|  |   SwitchCase: entries(coverSwitchCase), | ||
|  |   WithStatement: entries(blockProp('body'), coverStatement), | ||
|  |   FunctionDeclaration: entries(coverFunction), | ||
|  |   FunctionExpression: entries(coverFunction), | ||
|  |   LabeledStatement: entries(coverStatement), | ||
|  |   ConditionalExpression: entries(coverTernary), | ||
|  |   LogicalExpression: entries(coverLogicalExpression) | ||
|  | }; | ||
|  | const globalTemplateAlteredFunction = (0, _template.default)(`
 | ||
|  |         var Function = (function(){}).constructor; | ||
|  |         var global = (new Function(GLOBAL_COVERAGE_SCOPE))(); | ||
|  | `);
 | ||
|  | const globalTemplateFunction = (0, _template.default)(`
 | ||
|  |         var global = (new Function(GLOBAL_COVERAGE_SCOPE))(); | ||
|  | `);
 | ||
|  | const globalTemplateVariable = (0, _template.default)(`
 | ||
|  |         var global = GLOBAL_COVERAGE_SCOPE; | ||
|  | `); // the template to insert at the top of the program.
 | ||
|  | 
 | ||
|  | const coverageTemplate = (0, _template.default)(`
 | ||
|  |     var COVERAGE_VAR = (function () { | ||
|  |         var path = PATH; | ||
|  |         var hash = HASH; | ||
|  |         GLOBAL_COVERAGE_TEMPLATE | ||
|  |         var gcv = GLOBAL_COVERAGE_VAR; | ||
|  |         var coverageData = INITIAL; | ||
|  |         var coverage = global[gcv] || (global[gcv] = {}); | ||
|  |         if (coverage[path] && coverage[path].hash === hash) { | ||
|  |             return coverage[path]; | ||
|  |         } | ||
|  |         return coverage[path] = coverageData; | ||
|  |     })(); | ||
|  | `); // the rewire plugin (and potentially other babel middleware)
 | ||
|  | // may cause files to be instrumented twice, see:
 | ||
|  | // https://github.com/istanbuljs/babel-plugin-istanbul/issues/94
 | ||
|  | // we should only instrument code for coverage the first time
 | ||
|  | // it's run through istanbul-lib-instrument.
 | ||
|  | 
 | ||
|  | function alreadyInstrumented(path, visitState) { | ||
|  |   return path.scope.hasBinding(visitState.varName); | ||
|  | } | ||
|  | 
 | ||
|  | function shouldIgnoreFile(programNode) { | ||
|  |   return programNode.parent && programNode.parent.comments.some(c => COMMENT_FILE_RE.test(c.value)); | ||
|  | } | ||
|  | 
 | ||
|  | const defaultProgramVisitorOpts = { | ||
|  |   inputSourceMap: undefined | ||
|  | }; | ||
|  | /** | ||
|  |  * programVisitor is a `babel` adaptor for instrumentation. | ||
|  |  * It returns an object with two methods `enter` and `exit`. | ||
|  |  * These should be assigned to or called from `Program` entry and exit functions | ||
|  |  * in a babel visitor. | ||
|  |  * These functions do not make assumptions about the state set by Babel and thus | ||
|  |  * can be used in a context other than a Babel plugin. | ||
|  |  * | ||
|  |  * The exit function returns an object that currently has the following keys: | ||
|  |  * | ||
|  |  * `fileCoverage` - the file coverage object created for the source file. | ||
|  |  * `sourceMappingURL` - any source mapping URL found when processing the file. | ||
|  |  * | ||
|  |  * @param {Object} types - an instance of babel-types | ||
|  |  * @param {string} sourceFilePath - the path to source file | ||
|  |  * @param {Object} opts - additional options | ||
|  |  * @param {string} [opts.coverageVariable=__coverage__] the global coverage variable name. | ||
|  |  * @param {string} [opts.coverageGlobalScope=this] the global coverage variable scope. | ||
|  |  * @param {boolean} [opts.coverageGlobalScopeFunc=true] use an evaluated function to find coverageGlobalScope. | ||
|  |  * @param {Array} [opts.ignoreClassMethods=[]] names of methods to ignore by default on classes. | ||
|  |  * @param {object} [opts.inputSourceMap=undefined] the input source map, that maps the uninstrumented code back to the | ||
|  |  * original code. | ||
|  |  */ | ||
|  | 
 | ||
|  | function programVisitor(types, sourceFilePath = 'unknown.js', opts = defaultProgramVisitorOpts) { | ||
|  |   const T = types; // This sets some unused options but ensures all required options are initialized
 | ||
|  | 
 | ||
|  |   opts = Object.assign({}, (0, _instrumenter.defaultOpts)(), defaultProgramVisitorOpts, opts); | ||
|  |   const visitState = new VisitState(types, sourceFilePath, opts.inputSourceMap, opts.ignoreClassMethods); | ||
|  |   return { | ||
|  |     enter(path) { | ||
|  |       if (shouldIgnoreFile(path.find(p => p.isProgram()))) { | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (alreadyInstrumented(path, visitState)) { | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       path.traverse(codeVisitor, visitState); | ||
|  |     }, | ||
|  | 
 | ||
|  |     exit(path) { | ||
|  |       if (alreadyInstrumented(path, visitState)) { | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       visitState.cov.freeze(); | ||
|  |       const coverageData = visitState.cov.toJSON(); | ||
|  | 
 | ||
|  |       if (shouldIgnoreFile(path.find(p => p.isProgram()))) { | ||
|  |         return { | ||
|  |           fileCoverage: coverageData, | ||
|  |           sourceMappingURL: visitState.sourceMappingURL | ||
|  |         }; | ||
|  |       } | ||
|  | 
 | ||
|  |       coverageData[_constants.MAGIC_KEY] = _constants.MAGIC_VALUE; | ||
|  |       const hash = (0, _crypto.createHash)(_constants.SHA).update(JSON.stringify(coverageData)).digest('hex'); | ||
|  |       coverageData.hash = hash; | ||
|  |       const coverageNode = T.valueToNode(coverageData); | ||
|  |       delete coverageData[_constants.MAGIC_KEY]; | ||
|  |       delete coverageData.hash; | ||
|  |       let gvTemplate; | ||
|  | 
 | ||
|  |       if (opts.coverageGlobalScopeFunc) { | ||
|  |         if (path.scope.getBinding('Function')) { | ||
|  |           gvTemplate = globalTemplateAlteredFunction({ | ||
|  |             GLOBAL_COVERAGE_SCOPE: T.stringLiteral('return ' + opts.coverageGlobalScope) | ||
|  |           }); | ||
|  |         } else { | ||
|  |           gvTemplate = globalTemplateFunction({ | ||
|  |             GLOBAL_COVERAGE_SCOPE: T.stringLiteral('return ' + opts.coverageGlobalScope) | ||
|  |           }); | ||
|  |         } | ||
|  |       } else { | ||
|  |         gvTemplate = globalTemplateVariable({ | ||
|  |           GLOBAL_COVERAGE_SCOPE: opts.coverageGlobalScope | ||
|  |         }); | ||
|  |       } | ||
|  | 
 | ||
|  |       const cv = coverageTemplate({ | ||
|  |         GLOBAL_COVERAGE_VAR: T.stringLiteral(opts.coverageVariable), | ||
|  |         GLOBAL_COVERAGE_TEMPLATE: gvTemplate, | ||
|  |         COVERAGE_VAR: T.identifier(visitState.varName), | ||
|  |         PATH: T.stringLiteral(sourceFilePath), | ||
|  |         INITIAL: coverageNode, | ||
|  |         HASH: T.stringLiteral(hash) | ||
|  |       }); | ||
|  |       cv._blockHoist = 5; | ||
|  |       path.node.body.unshift(cv); | ||
|  |       return { | ||
|  |         fileCoverage: coverageData, | ||
|  |         sourceMappingURL: visitState.sourceMappingURL | ||
|  |       }; | ||
|  |     } | ||
|  | 
 | ||
|  |   }; | ||
|  | } | ||
|  | 
 | ||
|  | var _default = programVisitor; | ||
|  | exports.default = _default; |