'use strict'; /**
               * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
               *
               * This source code is licensed under the BSD-style license found in the
               * LICENSE file in the root directory of this source tree. An additional grant
               * of patent rights can be found in the PATENTS file in the same directory.
               *
               * 
               */








const crypto = require('crypto');
const path = require('path');
const vm = require('vm');var _require =
require('jest-util');const createDirectory = _require.createDirectory;
const fs = require('graceful-fs');var _require2 =
require('jest-haste-map');const getCacheFilePath = _require2.getCacheFilePath;
const stableStringify = require('json-stable-stringify');
const slash = require('slash');

const VERSION = require('../package.json').version;
const shouldInstrument = require('./shouldInstrument');









const cache = new Map();
const configToJsonMap = new Map();
// Cache regular expressions to test whether the file needs to be preprocessed
const ignoreCache = new WeakMap();

class ScriptTransformer {




  constructor(config) {
    this._config = config;
    this._transformCache = new Map();
  }

  _getCacheKey(
  fileData,
  filename,
  instrument,
  mapCoverage)
  {
    if (!configToJsonMap.has(this._config)) {
      // We only need this set of config options that can likely influence
      // cached output instead of all config options.
      configToJsonMap.set(this._config, stableStringify(this._config));
    }
    const configString = configToJsonMap.get(this._config) || '';
    const transformer = this._getTransformer(filename, this._config);

    if (transformer && typeof transformer.getCacheKey === 'function') {
      return transformer.getCacheKey(fileData, filename, configString, {
        instrument });

    } else {
      return crypto.
      createHash('md5').
      update(fileData).
      update(configString).
      update(instrument ? 'instrument' : '').
      update(mapCoverage ? 'mapCoverage' : '').
      digest('hex');
    }
  }

  _getFileCachePath(
  filename,
  content,
  instrument,
  mapCoverage)
  {
    const baseCacheDir = getCacheFilePath(
    this._config.cacheDirectory,
    'jest-transform-cache-' + this._config.name,
    VERSION);

    const cacheKey = this._getCacheKey(
    content,
    filename,
    instrument,
    mapCoverage);

    // Create sub folders based on the cacheKey to avoid creating one
    // directory with many files.
    const cacheDir = path.join(baseCacheDir, cacheKey[0] + cacheKey[1]);
    const cachePath = slash(
    path.join(
    cacheDir,
    path.basename(filename, path.extname(filename)) + '_' + cacheKey));


    createDirectory(cacheDir);

    return cachePath;
  }

  _getTransformPath(filename) {
    for (let i = 0; i < this._config.transform.length; i++) {
      if (new RegExp(this._config.transform[i][0]).test(filename)) {
        return this._config.transform[i][1];
      }
    }
    return null;
  }

  _getTransformer(filename) {
    let transform;
    if (!this._config.transform || !this._config.transform.length) {
      return null;
    }

    const transformPath = this._getTransformPath(filename);
    if (transformPath) {
      const transformer = this._transformCache.get(transformPath);
      if (transformer != null) {
        return transformer;
      }

      // $FlowFixMe
      transform = require(transformPath);
      if (typeof transform.process !== 'function') {
        throw new TypeError(
        'Jest: a transform must export a `process` function.');

      }
      if (typeof transform.createTransformer === 'function') {
        transform = transform.createTransformer();
      }
      this._transformCache.set(transformPath, transform);
    }
    return transform;
  }

  _instrumentFile(filename, content) {
    // Keeping these requires inside this function reduces a single run
    // time by 2sec if not running in `--coverage` mode
    const babel = require('babel-core');
    const babelPluginIstanbul = require('babel-plugin-istanbul').default;

    return babel.transform(content, {
      auxiliaryCommentBefore: ' istanbul ignore next ',
      babelrc: false,
      filename,
      plugins: [
      [
      babelPluginIstanbul,
      {
        // files outside `cwd` will not be instrumented
        cwd: this._config.rootDir,
        exclude: [],
        useInlineSourceMaps: false }]],



      retainLines: true }).
    code;
  }

  transformSource(
  filename,
  content,
  instrument,
  mapCoverage)
  {
    const transform = this._getTransformer(filename);
    const cacheFilePath = this._getFileCachePath(
    filename,
    content,
    instrument,
    mapCoverage);

    let sourceMapPath = cacheFilePath + '.map';
    // Ignore cache if `config.cache` is set (--no-cache)
    let code = this._config.cache ?
    readCacheFile(filename, cacheFilePath) :
    null;

    if (code) {
      return {
        code,
        sourceMapPath };

    }

    let transformed = {
      code: content,
      map: null };


    if (transform && shouldTransform(filename, this._config)) {
      const processed = transform.process(content, filename, this._config, {
        instrument });


      if (typeof processed === 'string') {
        transformed.code = processed;
      } else {
        transformed = processed;
      }
    }

    if (mapCoverage) {
      if (!transformed.map) {
        const convert = require('convert-source-map');
        const inlineSourceMap = convert.fromSource(transformed.code);
        if (inlineSourceMap) {
          transformed.map = inlineSourceMap.toJSON();
        }
      }
    } else {
      transformed.map = null;
    }

    // That means that the transform has a custom instrumentation
    // logic and will handle it based on `config.collectCoverage` option
    const transformDidInstrument = transform && transform.canInstrument;

    if (!transformDidInstrument && instrument) {
      code = this._instrumentFile(filename, transformed.code);
    } else {
      code = transformed.code;
    }

    if (instrument && transformed.map && mapCoverage) {
      const sourceMapContent = typeof transformed.map === 'string' ?
      transformed.map :
      JSON.stringify(transformed.map);
      writeCacheFile(sourceMapPath, sourceMapContent);
    } else {
      sourceMapPath = null;
    }

    writeCacheFile(cacheFilePath, code);

    return {
      code,
      sourceMapPath };

  }

  _transformAndBuildScript(
  filename,
  options,
  instrument,
  fileSource)
  {
    const isInternalModule = !!(options && options.isInternalModule);
    const content = stripShebang(
    fileSource || fs.readFileSync(filename, 'utf8'));

    let wrappedCode;
    let sourceMapPath = null;
    const willTransform =
    !isInternalModule && (
    shouldTransform(filename, this._config) || instrument);

    try {
      if (willTransform) {
        const transformedSource = this.transformSource(
        filename,
        content,
        instrument,
        !!(options && options.mapCoverage));


        wrappedCode = wrap(transformedSource.code);
        sourceMapPath = transformedSource.sourceMapPath;
      } else {
        wrappedCode = wrap(content);
      }

      return {
        script: new vm.Script(wrappedCode, { displayErrors: true, filename }),
        sourceMapPath };

    } catch (e) {
      if (e.codeFrame) {
        e.stack = e.codeFrame;
      }

      throw e;
    }
  }

  transform(
  filename,
  options,
  fileSource)
  {
    const instrument = shouldInstrument(filename, options, this._config);
    const scriptCacheKey = getScriptCacheKey(
    filename,
    this._config,
    instrument);

    let result = cache.get(scriptCacheKey);
    if (result) {
      return result;
    } else {
      result = this._transformAndBuildScript(
      filename,
      options,
      instrument,
      fileSource);

      cache.set(scriptCacheKey, result);
      return result;
    }
  }}


const removeFile = path => {
  try {
    fs.unlinkSync(path);
  } catch (e) {}
};

const stripShebang = content => {
  // If the file data starts with a shebang remove it. Leaves the empty line
  // to keep stack trace line numbers correct.
  if (content.startsWith('#!')) {
    return content.replace(/^#!.*/, '');
  } else {
    return content;
  }
};

const writeCacheFile = (cachePath, fileData) => {
  try {
    fs.writeFileSync(cachePath, fileData, 'utf8');
  } catch (e) {
    e.message =
    'jest: failed to cache transform results in: ' +
    cachePath +
    '\nFailure message: ' +
    e.message;
    removeFile(cachePath);
    throw e;
  }
};

const readCacheFile = (filename, cachePath) => {
  if (!fs.existsSync(cachePath)) {
    return null;
  }

  let fileData;
  try {
    fileData = fs.readFileSync(cachePath, 'utf8');
  } catch (e) {
    e.message = 'jest: failed to read cache file: ' + cachePath;
    removeFile(cachePath);
    throw e;
  }

  if (fileData == null) {
    // We must have somehow created the file but failed to write to it,
    // let's delete it and retry.
    removeFile(cachePath);
  }
  return fileData;
};

const getScriptCacheKey = (filename, config, instrument) => {
  const mtime = fs.statSync(filename).mtime;
  return filename + '_' + mtime.getTime() + (instrument ? '_instrumented' : '');
};

const shouldTransform = (filename, config) => {
  if (!ignoreCache.has(config)) {
    if (!config.transformIgnorePatterns) {
      ignoreCache.set(config, null);
    } else {
      ignoreCache.set(
      config,
      new RegExp(config.transformIgnorePatterns.join('|')));

    }
  }

  const ignoreRegexp = ignoreCache.get(config);
  const isIgnored = ignoreRegexp ? ignoreRegexp.test(filename) : false;
  return (
    !!config.transform &&
    !!config.transform.length && (
    !config.transformIgnorePatterns.length || !isIgnored));

};

const wrap = content =>
'({"' +
ScriptTransformer.EVAL_RESULT_VARIABLE +
'":function(module,exports,require,__dirname,__filename,global,jest){' +
content +
'\n}});';

ScriptTransformer.EVAL_RESULT_VARIABLE = 'Object.<anonymous>';

module.exports = ScriptTransformer;