You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							673 lines
						
					
					
						
							20 KiB
						
					
					
				
			
		
		
	
	
							673 lines
						
					
					
						
							20 KiB
						
					
					
				"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; |