'use strict';

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

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

  os = function () {
    return data;
  };

  return data;
}

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

  path = function () {
    return data;
  };

  return data;
}

function _micromatch() {
  const data = _interopRequireDefault(require('micromatch'));

  _micromatch = function () {
    return data;
  };

  return data;
}

function _jestConfig() {
  const data = require('jest-config');

  _jestConfig = function () {
    return data;
  };

  return data;
}

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

  _jestRegexUtil = function () {
    return data;
  };

  return data;
}

function _jestResolveDependencies() {
  const data = require('jest-resolve-dependencies');

  _jestResolveDependencies = function () {
    return data;
  };

  return data;
}

function _jestSnapshot() {
  const data = require('jest-snapshot');

  _jestSnapshot = function () {
    return data;
  };

  return data;
}

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

  _jestUtil = function () {
    return data;
  };

  return data;
}

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

function _getRequireWildcardCache(nodeInterop) {
  if (typeof WeakMap !== 'function') return null;
  var cacheBabelInterop = new WeakMap();
  var cacheNodeInterop = new WeakMap();
  return (_getRequireWildcardCache = function (nodeInterop) {
    return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  })(nodeInterop);
}

function _interopRequireWildcard(obj, nodeInterop) {
  if (!nodeInterop && obj && obj.__esModule) {
    return obj;
  }
  if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
    return {default: obj};
  }
  var cache = _getRequireWildcardCache(nodeInterop);
  if (cache && cache.has(obj)) {
    return cache.get(obj);
  }
  var newObj = {};
  var hasPropertyDescriptor =
    Object.defineProperty && Object.getOwnPropertyDescriptor;
  for (var key in obj) {
    if (key !== 'default' && 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;
}

/**
 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
const regexToMatcher = testRegex => {
  const regexes = testRegex.map(testRegex => new RegExp(testRegex));
  return path =>
    regexes.some(regex => {
      const result = regex.test(path); // prevent stateful regexes from breaking, just in case

      regex.lastIndex = 0;
      return result;
    });
};

const toTests = (context, tests) =>
  tests.map(path => ({
    context,
    duration: undefined,
    path
  }));

const hasSCM = changedFilesInfo => {
  const {repos} = changedFilesInfo; // no SCM (git/hg/...) is found in any of the roots.

  const noSCM = Object.values(repos).every(scm => scm.size === 0);
  return !noSCM;
};

class SearchSource {
  _context;
  _dependencyResolver;
  _testPathCases = [];

  constructor(context) {
    const {config} = context;
    this._context = context;
    this._dependencyResolver = null;
    const rootPattern = new RegExp(
      config.roots
        .map(dir => (0, _jestRegexUtil().escapePathForRegex)(dir + path().sep))
        .join('|')
    );

    this._testPathCases.push({
      isMatch: path => rootPattern.test(path),
      stat: 'roots'
    });

    if (config.testMatch.length) {
      this._testPathCases.push({
        isMatch: (0, _jestUtil().globsToMatcher)(config.testMatch),
        stat: 'testMatch'
      });
    }

    if (config.testPathIgnorePatterns.length) {
      const testIgnorePatternsRegex = new RegExp(
        config.testPathIgnorePatterns.join('|')
      );

      this._testPathCases.push({
        isMatch: path => !testIgnorePatternsRegex.test(path),
        stat: 'testPathIgnorePatterns'
      });
    }

    if (config.testRegex.length) {
      this._testPathCases.push({
        isMatch: regexToMatcher(config.testRegex),
        stat: 'testRegex'
      });
    }
  }

  async _getOrBuildDependencyResolver() {
    if (!this._dependencyResolver) {
      this._dependencyResolver =
        new (_jestResolveDependencies().DependencyResolver)(
          this._context.resolver,
          this._context.hasteFS,
          await (0, _jestSnapshot().buildSnapshotResolver)(this._context.config)
        );
    }

    return this._dependencyResolver;
  }

  _filterTestPathsWithStats(allPaths, testPathPattern) {
    const data = {
      stats: {
        roots: 0,
        testMatch: 0,
        testPathIgnorePatterns: 0,
        testRegex: 0
      },
      tests: [],
      total: allPaths.length
    };
    const testCases = Array.from(this._testPathCases); // clone

    if (testPathPattern) {
      const regex = (0, _jestUtil().testPathPatternToRegExp)(testPathPattern);
      testCases.push({
        isMatch: path => regex.test(path),
        stat: 'testPathPattern'
      });
      data.stats.testPathPattern = 0;
    }

    data.tests = allPaths.filter(test => {
      let filterResult = true;

      for (const {isMatch, stat} of testCases) {
        if (isMatch(test.path)) {
          data.stats[stat]++;
        } else {
          filterResult = false;
        }
      }

      return filterResult;
    });
    return data;
  }

  _getAllTestPaths(testPathPattern) {
    return this._filterTestPathsWithStats(
      toTests(this._context, this._context.hasteFS.getAllFiles()),
      testPathPattern
    );
  }

  isTestFilePath(path) {
    return this._testPathCases.every(testCase => testCase.isMatch(path));
  }

  findMatchingTests(testPathPattern) {
    return this._getAllTestPaths(testPathPattern);
  }

  async findRelatedTests(allPaths, collectCoverage) {
    const dependencyResolver = await this._getOrBuildDependencyResolver();

    if (!collectCoverage) {
      return {
        tests: toTests(
          this._context,
          dependencyResolver.resolveInverse(
            allPaths,
            this.isTestFilePath.bind(this),
            {
              skipNodeResolution: this._context.config.skipNodeResolution
            }
          )
        )
      };
    }

    const testModulesMap = dependencyResolver.resolveInverseModuleMap(
      allPaths,
      this.isTestFilePath.bind(this),
      {
        skipNodeResolution: this._context.config.skipNodeResolution
      }
    );
    const allPathsAbsolute = Array.from(allPaths).map(p => path().resolve(p));
    const collectCoverageFrom = new Set();
    testModulesMap.forEach(testModule => {
      if (!testModule.dependencies) {
        return;
      }

      testModule.dependencies.forEach(p => {
        if (!allPathsAbsolute.includes(p)) {
          return;
        }

        const filename = (0, _jestConfig().replaceRootDirInPath)(
          this._context.config.rootDir,
          p
        );
        collectCoverageFrom.add(
          path().isAbsolute(filename)
            ? path().relative(this._context.config.rootDir, filename)
            : filename
        );
      });
    });
    return {
      collectCoverageFrom,
      tests: toTests(
        this._context,
        testModulesMap.map(testModule => testModule.file)
      )
    };
  }

  findTestsByPaths(paths) {
    return {
      tests: toTests(
        this._context,
        paths
          .map(p => path().resolve(this._context.config.cwd, p))
          .filter(this.isTestFilePath.bind(this))
      )
    };
  }

  async findRelatedTestsFromPattern(paths, collectCoverage) {
    if (Array.isArray(paths) && paths.length) {
      const resolvedPaths = paths.map(p =>
        path().resolve(this._context.config.cwd, p)
      );
      return this.findRelatedTests(new Set(resolvedPaths), collectCoverage);
    }

    return {
      tests: []
    };
  }

  async findTestRelatedToChangedFiles(changedFilesInfo, collectCoverage) {
    if (!hasSCM(changedFilesInfo)) {
      return {
        noSCM: true,
        tests: []
      };
    }

    const {changedFiles} = changedFilesInfo;
    return this.findRelatedTests(changedFiles, collectCoverage);
  }

  async _getTestPaths(globalConfig, changedFiles) {
    if (globalConfig.onlyChanged) {
      if (!changedFiles) {
        throw new Error('Changed files must be set when running with -o.');
      }

      return this.findTestRelatedToChangedFiles(
        changedFiles,
        globalConfig.collectCoverage
      );
    }

    let paths = globalConfig.nonFlagArgs;

    if (globalConfig.findRelatedTests && 'win32' === os().platform()) {
      paths = this.filterPathsWin32(paths);
    }

    if (globalConfig.runTestsByPath && paths && paths.length) {
      return this.findTestsByPaths(paths);
    } else if (globalConfig.findRelatedTests && paths && paths.length) {
      return this.findRelatedTestsFromPattern(
        paths,
        globalConfig.collectCoverage
      );
    } else if (globalConfig.testPathPattern != null) {
      return this.findMatchingTests(globalConfig.testPathPattern);
    } else {
      return {
        tests: []
      };
    }
  }

  filterPathsWin32(paths) {
    const allFiles = this._context.hasteFS.getAllFiles();

    const options = {
      nocase: true,
      windows: false
    };

    function normalizePosix(filePath) {
      return filePath.replace(/\\/g, '/');
    }

    paths = paths
      .map(p => {
        // micromatch works with forward slashes: https://github.com/micromatch/micromatch#backslashes
        const normalizedPath = normalizePosix(
          path().resolve(this._context.config.cwd, p)
        );
        const match = (0, _micromatch().default)(
          allFiles.map(normalizePosix),
          normalizedPath,
          options
        );
        return match[0];
      })
      .filter(Boolean)
      .map(p => path().resolve(p));
    return paths;
  }

  async getTestPaths(globalConfig, changedFiles, filter) {
    const searchResult = await this._getTestPaths(globalConfig, changedFiles);
    const filterPath = globalConfig.filter;

    if (filter) {
      const tests = searchResult.tests;
      const filterResult = await filter(tests.map(test => test.path));

      if (!Array.isArray(filterResult.filtered)) {
        throw new Error(
          `Filter ${filterPath} did not return a valid test list`
        );
      }

      const filteredSet = new Set(
        filterResult.filtered.map(result => result.test)
      );
      return {
        ...searchResult,
        tests: tests.filter(test => filteredSet.has(test.path))
      };
    }

    return searchResult;
  }

  async findRelatedSourcesFromTestsInChangedFiles(changedFilesInfo) {
    if (!hasSCM(changedFilesInfo)) {
      return [];
    }

    const {changedFiles} = changedFilesInfo;
    const dependencyResolver = await this._getOrBuildDependencyResolver();
    const relatedSourcesSet = new Set();
    changedFiles.forEach(filePath => {
      if (this.isTestFilePath(filePath)) {
        const sourcePaths = dependencyResolver.resolve(filePath, {
          skipNodeResolution: this._context.config.skipNodeResolution
        });
        sourcePaths.forEach(sourcePath => relatedSourcesSet.add(sourcePath));
      }
    });
    return Array.from(relatedSourcesSet);
  }
}

exports.default = SearchSource;
