instrumenter.js 6.01 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _core = require("@babel/core");

var _schema = require("@istanbuljs/schema");

var _visitor = _interopRequireDefault(require("./visitor"));

var _readCoverage = _interopRequireDefault(require("./read-coverage"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/*
 Copyright 2012-2015, Yahoo Inc.
 Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */

/**
 * Instrumenter is the public API for the instrument library.
 * It is typically used for ES5 code. For ES6 code that you
 * are already running under `babel` use the coverage plugin
 * instead.
 * @param {Object} opts optional.
 * @param {string} [opts.coverageVariable=__coverage__] name of global coverage variable.
 * @param {boolean} [opts.preserveComments=false] preserve comments in output
 * @param {boolean} [opts.compact=true] generate compact code.
 * @param {boolean} [opts.esModules=false] set to true to instrument ES6 modules.
 * @param {boolean} [opts.autoWrap=false] set to true to allow `return` statements outside of functions.
 * @param {boolean} [opts.produceSourceMap=false] set to true to produce a source map for the instrumented code.
 * @param {Array} [opts.ignoreClassMethods=[]] set to array of class method names to ignore for coverage.
 * @param {Function} [opts.sourceMapUrlCallback=null] a callback function that is called when a source map URL
 *     is found in the original code. This function is called with the source file name and the source map URL.
 * @param {boolean} [opts.debug=false] - turn debugging on
 * @param {array} [opts.parserPlugins] - set babel parser plugins, see @istanbuljs/schema for defaults.
 */
class Instrumenter {
  constructor(opts = {}) {
    this.opts = { ..._schema.defaults.instrumenter,
      ...opts
    };
    this.fileCoverage = null;
    this.sourceMap = null;
  }
  /**
   * instrument the supplied code and track coverage against the supplied
   * filename. It throws if invalid code is passed to it. ES5 and ES6 syntax
   * is supported. To instrument ES6 modules, make sure that you set the
   * `esModules` property to `true` when creating the instrumenter.
   *
   * @param {string} code - the code to instrument
   * @param {string} filename - the filename against which to track coverage.
   * @param {object} [inputSourceMap] - the source map that maps the not instrumented code back to it's original form.
   * Is assigned to the coverage object and therefore, is available in the json output and can be used to remap the
   * coverage to the untranspiled source.
   * @returns {string} the instrumented code.
   */


  instrumentSync(code, filename, inputSourceMap) {
    if (typeof code !== 'string') {
      throw new Error('Code must be a string');
    }

    filename = filename || String(new Date().getTime()) + '.js';
    const {
      opts
    } = this;
    let output = {};
    const babelOpts = {
      configFile: false,
      babelrc: false,
      ast: true,
      filename: filename || String(new Date().getTime()) + '.js',
      inputSourceMap,
      sourceMaps: opts.produceSourceMap,
      compact: opts.compact,
      comments: opts.preserveComments,
      parserOpts: {
        allowReturnOutsideFunction: opts.autoWrap,
        sourceType: opts.esModules ? 'module' : 'script',
        plugins: opts.parserPlugins
      },
      plugins: [[({
        types
      }) => {
        const ee = (0, _visitor.default)(types, filename, {
          coverageVariable: opts.coverageVariable,
          coverageGlobalScope: opts.coverageGlobalScope,
          coverageGlobalScopeFunc: opts.coverageGlobalScopeFunc,
          ignoreClassMethods: opts.ignoreClassMethods,
          inputSourceMap
        });
        return {
          visitor: {
            Program: {
              enter: ee.enter,

              exit(path) {
                output = ee.exit(path);
              }

            }
          }
        };
      }]]
    };
    const codeMap = (0, _core.transformSync)(code, babelOpts);

    if (!output || !output.fileCoverage) {
      const initialCoverage = (0, _readCoverage.default)(codeMap.ast) ||
      /* istanbul ignore next: paranoid check */
      {};
      this.fileCoverage = initialCoverage.coverageData;
      this.sourceMap = inputSourceMap;
      return code;
    }

    this.fileCoverage = output.fileCoverage;
    this.sourceMap = codeMap.map;
    const cb = this.opts.sourceMapUrlCallback;

    if (cb && output.sourceMappingURL) {
      cb(filename, output.sourceMappingURL);
    }

    return codeMap.code;
  }
  /**
   * callback-style instrument method that calls back with an error
   * as opposed to throwing one. Note that in the current implementation,
   * the callback will be called in the same process tick and is not asynchronous.
   *
   * @param {string} code - the code to instrument
   * @param {string} filename - the filename against which to track coverage.
   * @param {Function} callback - the callback
   * @param {Object} inputSourceMap - the source map that maps the not instrumented code back to it's original form.
   * Is assigned to the coverage object and therefore, is available in the json output and can be used to remap the
   * coverage to the untranspiled source.
   */


  instrument(code, filename, callback, inputSourceMap) {
    if (!callback && typeof filename === 'function') {
      callback = filename;
      filename = null;
    }

    try {
      const out = this.instrumentSync(code, filename, inputSourceMap);
      callback(null, out);
    } catch (ex) {
      callback(ex);
    }
  }
  /**
   * returns the file coverage object for the last file instrumented.
   * @returns {Object} the file coverage object.
   */


  lastFileCoverage() {
    return this.fileCoverage;
  }
  /**
   * returns the source map produced for the last file instrumented.
   * @returns {null|Object} the source map object.
   */


  lastSourceMap() {
    return this.sourceMap;
  }

}

var _default = Instrumenter;
exports.default = _default;