'use strict';

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

function path() {
  const data = _interopRequireWildcard(require('path'));

  path = function () {
    return data;
  };

  return data;
}

function _v8Coverage() {
  const data = require('@bcoe/v8-coverage');

  _v8Coverage = function () {
    return data;
  };

  return data;
}

function _chalk() {
  const data = _interopRequireDefault(require('chalk'));

  _chalk = function () {
    return data;
  };

  return data;
}

function _glob() {
  const data = _interopRequireDefault(require('glob'));

  _glob = function () {
    return data;
  };

  return data;
}

function fs() {
  const data = _interopRequireWildcard(require('graceful-fs'));

  fs = function () {
    return data;
  };

  return data;
}

function _istanbulLibCoverage() {
  const data = _interopRequireDefault(require('istanbul-lib-coverage'));

  _istanbulLibCoverage = function () {
    return data;
  };

  return data;
}

function _istanbulLibReport() {
  const data = _interopRequireDefault(require('istanbul-lib-report'));

  _istanbulLibReport = function () {
    return data;
  };

  return data;
}

function _istanbulLibSourceMaps() {
  const data = _interopRequireDefault(require('istanbul-lib-source-maps'));

  _istanbulLibSourceMaps = function () {
    return data;
  };

  return data;
}

function _istanbulReports() {
  const data = _interopRequireDefault(require('istanbul-reports'));

  _istanbulReports = function () {
    return data;
  };

  return data;
}

function _v8ToIstanbul() {
  const data = _interopRequireDefault(require('v8-to-istanbul'));

  _v8ToIstanbul = function () {
    return data;
  };

  return data;
}

function _jestUtil() {
  const data = require('jest-util');

  _jestUtil = function () {
    return data;
  };

  return data;
}

function _jestWorker() {
  const data = _interopRequireDefault(require('jest-worker'));

  _jestWorker = function () {
    return data;
  };

  return data;
}

var _BaseReporter = _interopRequireDefault(require('./BaseReporter'));

var _getWatermarks = _interopRequireDefault(require('./getWatermarks'));

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

function _getRequireWildcardCache() {
  if (typeof WeakMap !== 'function') return null;
  var cache = new WeakMap();
  _getRequireWildcardCache = function () {
    return cache;
  };
  return cache;
}

function _interopRequireWildcard(obj) {
  if (obj && obj.__esModule) {
    return obj;
  }
  if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
    return {default: obj};
  }
  var cache = _getRequireWildcardCache();
  if (cache && cache.has(obj)) {
    return cache.get(obj);
  }
  var newObj = {};
  var hasPropertyDescriptor =
    Object.defineProperty && Object.getOwnPropertyDescriptor;
  for (var key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      var desc = hasPropertyDescriptor
        ? Object.getOwnPropertyDescriptor(obj, key)
        : null;
      if (desc && (desc.get || desc.set)) {
        Object.defineProperty(newObj, key, desc);
      } else {
        newObj[key] = obj[key];
      }
    }
  }
  newObj.default = obj;
  if (cache) {
    cache.set(obj, newObj);
  }
  return newObj;
}

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

const FAIL_COLOR = _chalk().default.bold.red;

const RUNNING_TEST_COLOR = _chalk().default.bold.dim;

class CoverageReporter extends _BaseReporter.default {
  constructor(globalConfig, options) {
    super();

    _defineProperty(this, '_coverageMap', void 0);

    _defineProperty(this, '_globalConfig', void 0);

    _defineProperty(this, '_sourceMapStore', void 0);

    _defineProperty(this, '_options', void 0);

    _defineProperty(this, '_v8CoverageResults', void 0);

    this._coverageMap = _istanbulLibCoverage().default.createCoverageMap({});
    this._globalConfig = globalConfig;
    this._sourceMapStore = _istanbulLibSourceMaps().default.createSourceMapStore();
    this._v8CoverageResults = [];
    this._options = options || {};
  }

  onTestResult(_test, testResult) {
    if (testResult.v8Coverage) {
      this._v8CoverageResults.push(testResult.v8Coverage);

      return;
    }

    if (testResult.coverage) {
      this._coverageMap.merge(testResult.coverage);
    }
  }

  async onRunComplete(contexts, aggregatedResults) {
    await this._addUntestedFiles(contexts);
    const {map, reportContext} = await this._getCoverageResult();

    try {
      const coverageReporters = this._globalConfig.coverageReporters || [];

      if (!this._globalConfig.useStderr && coverageReporters.length < 1) {
        coverageReporters.push('text-summary');
      }

      coverageReporters.forEach(reporter => {
        let additionalOptions = {};

        if (Array.isArray(reporter)) {
          [reporter, additionalOptions] = reporter;
        }

        _istanbulReports()
          .default.create(reporter, {
            maxCols: process.stdout.columns || Infinity,
            ...additionalOptions
          }) // @ts-expect-error
          .execute(reportContext);
      });
      aggregatedResults.coverageMap = map;
    } catch (e) {
      console.error(
        _chalk().default.red(`
        Failed to write coverage reports:
        ERROR: ${e.toString()}
        STACK: ${e.stack}
      `)
      );
    }

    this._checkThreshold(map);
  }

  async _addUntestedFiles(contexts) {
    const files = [];
    contexts.forEach(context => {
      const config = context.config;

      if (
        this._globalConfig.collectCoverageFrom &&
        this._globalConfig.collectCoverageFrom.length
      ) {
        context.hasteFS
          .matchFilesWithGlob(
            this._globalConfig.collectCoverageFrom,
            config.rootDir
          )
          .forEach(filePath =>
            files.push({
              config,
              path: filePath
            })
          );
      }
    });

    if (!files.length) {
      return;
    }

    if (_jestUtil().isInteractive) {
      process.stderr.write(
        RUNNING_TEST_COLOR('Running coverage on untested files...')
      );
    }

    let worker;

    if (this._globalConfig.maxWorkers <= 1) {
      worker = require('./CoverageWorker');
    } else {
      worker = new (_jestWorker().default)(
        require.resolve('./CoverageWorker'),
        {
          exposedMethods: ['worker'],
          maxRetries: 2,
          numWorkers: this._globalConfig.maxWorkers
        }
      );
    }

    const instrumentation = files.map(async fileObj => {
      const filename = fileObj.path;
      const config = fileObj.config;

      const hasCoverageData = this._v8CoverageResults.some(v8Res =>
        v8Res.some(innerRes => innerRes.result.url === filename)
      );

      if (
        !hasCoverageData &&
        !this._coverageMap.data[filename] &&
        'worker' in worker
      ) {
        try {
          const result = await worker.worker({
            config,
            globalConfig: this._globalConfig,
            options: {
              ...this._options,
              changedFiles:
                this._options.changedFiles &&
                Array.from(this._options.changedFiles),
              sourcesRelatedToTestsInChangedFiles:
                this._options.sourcesRelatedToTestsInChangedFiles &&
                Array.from(this._options.sourcesRelatedToTestsInChangedFiles)
            },
            path: filename
          });

          if (result) {
            if (result.kind === 'V8Coverage') {
              this._v8CoverageResults.push([
                {
                  codeTransformResult: undefined,
                  result: result.result
                }
              ]);
            } else {
              this._coverageMap.addFileCoverage(result.coverage);
            }
          }
        } catch (error) {
          console.error(
            _chalk().default.red(
              [
                `Failed to collect coverage from ${filename}`,
                `ERROR: ${error.message}`,
                `STACK: ${error.stack}`
              ].join('\n')
            )
          );
        }
      }
    });

    try {
      await Promise.all(instrumentation);
    } catch {
      // Do nothing; errors were reported earlier to the console.
    }

    if (_jestUtil().isInteractive) {
      (0, _jestUtil().clearLine)(process.stderr);
    }

    if (worker && 'end' in worker && typeof worker.end === 'function') {
      await worker.end();
    }
  }

  _checkThreshold(map) {
    const {coverageThreshold} = this._globalConfig;

    if (coverageThreshold) {
      function check(name, thresholds, actuals) {
        return ['statements', 'branches', 'lines', 'functions'].reduce(
          (errors, key) => {
            const actual = actuals[key].pct;
            const actualUncovered = actuals[key].total - actuals[key].covered;
            const threshold = thresholds[key];

            if (threshold !== undefined) {
              if (threshold < 0) {
                if (threshold * -1 < actualUncovered) {
                  errors.push(
                    `Jest: Uncovered count for ${key} (${actualUncovered}) ` +
                      `exceeds ${name} threshold (${-1 * threshold})`
                  );
                }
              } else if (actual < threshold) {
                errors.push(
                  `Jest: "${name}" coverage threshold for ${key} (${threshold}%) not met: ${actual}%`
                );
              }
            }

            return errors;
          },
          []
        );
      }

      const THRESHOLD_GROUP_TYPES = {
        GLOB: 'glob',
        GLOBAL: 'global',
        PATH: 'path'
      };
      const coveredFiles = map.files();
      const thresholdGroups = Object.keys(coverageThreshold);
      const groupTypeByThresholdGroup = {};
      const filesByGlob = {};
      const coveredFilesSortedIntoThresholdGroup = coveredFiles.reduce(
        (files, file) => {
          const pathOrGlobMatches = thresholdGroups.reduce(
            (agg, thresholdGroup) => {
              const absoluteThresholdGroup = path().resolve(thresholdGroup); // The threshold group might be a path:

              if (file.indexOf(absoluteThresholdGroup) === 0) {
                groupTypeByThresholdGroup[thresholdGroup] =
                  THRESHOLD_GROUP_TYPES.PATH;
                return agg.concat([[file, thresholdGroup]]);
              } // If the threshold group is not a path it might be a glob:
              // Note: glob.sync is slow. By memoizing the files matching each glob
              // (rather than recalculating it for each covered file) we save a tonne
              // of execution time.

              if (filesByGlob[absoluteThresholdGroup] === undefined) {
                filesByGlob[absoluteThresholdGroup] = _glob()
                  .default.sync(absoluteThresholdGroup)
                  .map(filePath => path().resolve(filePath));
              }

              if (filesByGlob[absoluteThresholdGroup].indexOf(file) > -1) {
                groupTypeByThresholdGroup[thresholdGroup] =
                  THRESHOLD_GROUP_TYPES.GLOB;
                return agg.concat([[file, thresholdGroup]]);
              }

              return agg;
            },
            []
          );

          if (pathOrGlobMatches.length > 0) {
            return files.concat(pathOrGlobMatches);
          } // Neither a glob or a path? Toss it in global if there's a global threshold:

          if (thresholdGroups.indexOf(THRESHOLD_GROUP_TYPES.GLOBAL) > -1) {
            groupTypeByThresholdGroup[THRESHOLD_GROUP_TYPES.GLOBAL] =
              THRESHOLD_GROUP_TYPES.GLOBAL;
            return files.concat([[file, THRESHOLD_GROUP_TYPES.GLOBAL]]);
          } // A covered file that doesn't have a threshold:

          return files.concat([[file, undefined]]);
        },
        []
      );

      const getFilesInThresholdGroup = thresholdGroup =>
        coveredFilesSortedIntoThresholdGroup
          .filter(fileAndGroup => fileAndGroup[1] === thresholdGroup)
          .map(fileAndGroup => fileAndGroup[0]);

      function combineCoverage(filePaths) {
        return filePaths
          .map(filePath => map.fileCoverageFor(filePath))
          .reduce((combinedCoverage, nextFileCoverage) => {
            if (combinedCoverage === undefined || combinedCoverage === null) {
              return nextFileCoverage.toSummary();
            }

            return combinedCoverage.merge(nextFileCoverage.toSummary());
          }, undefined);
      }

      let errors = [];
      thresholdGroups.forEach(thresholdGroup => {
        switch (groupTypeByThresholdGroup[thresholdGroup]) {
          case THRESHOLD_GROUP_TYPES.GLOBAL: {
            const coverage = combineCoverage(
              getFilesInThresholdGroup(THRESHOLD_GROUP_TYPES.GLOBAL)
            );

            if (coverage) {
              errors = errors.concat(
                check(
                  thresholdGroup,
                  coverageThreshold[thresholdGroup],
                  coverage
                )
              );
            }

            break;
          }

          case THRESHOLD_GROUP_TYPES.PATH: {
            const coverage = combineCoverage(
              getFilesInThresholdGroup(thresholdGroup)
            );

            if (coverage) {
              errors = errors.concat(
                check(
                  thresholdGroup,
                  coverageThreshold[thresholdGroup],
                  coverage
                )
              );
            }

            break;
          }

          case THRESHOLD_GROUP_TYPES.GLOB:
            getFilesInThresholdGroup(thresholdGroup).forEach(
              fileMatchingGlob => {
                errors = errors.concat(
                  check(
                    fileMatchingGlob,
                    coverageThreshold[thresholdGroup],
                    map.fileCoverageFor(fileMatchingGlob).toSummary()
                  )
                );
              }
            );
            break;

          default:
            // If the file specified by path is not found, error is returned.
            if (thresholdGroup !== THRESHOLD_GROUP_TYPES.GLOBAL) {
              errors = errors.concat(
                `Jest: Coverage data for ${thresholdGroup} was not found.`
              );
            }

          // Sometimes all files in the coverage data are matched by
          // PATH and GLOB threshold groups in which case, don't error when
          // the global threshold group doesn't match any files.
        }
      });
      errors = errors.filter(
        err => err !== undefined && err !== null && err.length > 0
      );

      if (errors.length > 0) {
        this.log(`${FAIL_COLOR(errors.join('\n'))}`);

        this._setError(new Error(errors.join('\n')));
      }
    }
  }

  async _getCoverageResult() {
    if (this._globalConfig.coverageProvider === 'v8') {
      const mergedCoverages = (0, _v8Coverage().mergeProcessCovs)(
        this._v8CoverageResults.map(cov => ({
          result: cov.map(r => r.result)
        }))
      );
      const fileTransforms = new Map();

      this._v8CoverageResults.forEach(res =>
        res.forEach(r => {
          if (r.codeTransformResult && !fileTransforms.has(r.result.url)) {
            fileTransforms.set(r.result.url, r.codeTransformResult);
          }
        })
      );

      const transformedCoverage = await Promise.all(
        mergedCoverages.result.map(async res => {
          var _fileTransform$wrappe;

          const fileTransform = fileTransforms.get(res.url);
          let sourcemapContent = undefined;

          if (
            fileTransform &&
            fileTransform.sourceMapPath &&
            fs().existsSync(fileTransform.sourceMapPath)
          ) {
            sourcemapContent = JSON.parse(
              fs().readFileSync(fileTransform.sourceMapPath, 'utf8')
            );
          }

          const converter = (0, _v8ToIstanbul().default)(
            res.url,
            (_fileTransform$wrappe =
              fileTransform === null || fileTransform === void 0
                ? void 0
                : fileTransform.wrapperLength) !== null &&
              _fileTransform$wrappe !== void 0
              ? _fileTransform$wrappe
              : 0,
            fileTransform && sourcemapContent
              ? {
                  originalSource: fileTransform.originalCode,
                  source: fileTransform.code,
                  sourceMap: {
                    sourcemap: sourcemapContent
                  }
                }
              : {
                  source: fs().readFileSync(res.url, 'utf8')
                }
          );
          await converter.load();
          converter.applyCoverage(res.functions);
          return converter.toIstanbul();
        })
      );

      const map = _istanbulLibCoverage().default.createCoverageMap({});

      transformedCoverage.forEach(res => map.merge(res));

      const reportContext = _istanbulLibReport().default.createContext({
        coverageMap: map,
        dir: this._globalConfig.coverageDirectory,
        watermarks: (0, _getWatermarks.default)(this._globalConfig)
      });

      return {
        map,
        reportContext
      };
    }

    const map = await this._sourceMapStore.transformCoverage(this._coverageMap);

    const reportContext = _istanbulLibReport().default.createContext({
      coverageMap: map,
      dir: this._globalConfig.coverageDirectory,
      sourceFinder: this._sourceMapStore.sourceFinder,
      watermarks: (0, _getWatermarks.default)(this._globalConfig)
    });

    return {
      map,
      reportContext
    };
  }
}

exports.default = CoverageReporter;
