text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  /**
   * @license
   * Copyright Google Inc. All Rights Reserved.
   *
   * Use of this source code is governed by an MIT-style license that can be
   * found in the LICENSE file at https://angular.io/license
   */
  const architect_1 = require("@angular-devkit/architect");
  const fs_1 = require("fs");
  const glob = require("glob");
  const minimatch_1 = require("minimatch");
  const path = require("path");
  const strip_bom_1 = require("../angular-cli-files/utilities/strip-bom");
  async function _loadTslint() {
      let tslint;
      try {
          tslint = await Promise.resolve().then(() => require('tslint')); // tslint:disable-line:no-implicit-dependencies
      }
      catch (_a) {
          throw new Error('Unable to find TSLint. Ensure TSLint is installed.');
      }
      const version = tslint.Linter.VERSION && tslint.Linter.VERSION.split('.');
      if (!version || version.length < 2
          || (Number(version[0]) === 5 && Number(version[1]) < 5) // 5.5+
          || Number(version[0]) < 5 // 6.0+
      ) {
          throw new Error('TSLint must be version 5.5 or higher.');
      }
      return tslint;
  }
  async function _run(options, context) {
      const systemRoot = context.workspaceRoot;
      process.chdir(context.currentDirectory);
      const projectName = (context.target && context.target.project) || '<???>';
      // Print formatter output only for non human-readable formats.
      const printInfo = ['prose', 'verbose', 'stylish'].includes(options.format || '') && !options.silent;
      context.reportStatus(`Linting ${JSON.stringify(projectName)}...`);
      if (printInfo) {
          context.logger.info(`Linting ${JSON.stringify(projectName)}...`);
      }
      if (!options.tsConfig && options.typeCheck) {
          throw new Error('A "project" must be specified to enable type checking.');
      }
      const projectTslint = await _loadTslint();
      const tslintConfigPath = options.tslintConfig
          ? path.resolve(systemRoot, options.tslintConfig)
          : null;
      const Linter = projectTslint.Linter;
      let result = undefined;
      if (options.tsConfig) {
          const tsConfigs = Array.isArray(options.tsConfig) ? options.tsConfig : [options.tsConfig];
          context.reportProgress(0, tsConfigs.length);
          const allPrograms = tsConfigs.map(tsConfig => {
              return Linter.createProgram(path.resolve(systemRoot, tsConfig));
          });
          let i = 0;
          for (const program of allPrograms) {
              const partial = await _lint(projectTslint, systemRoot, tslintConfigPath, options, program, allPrograms);
              if (result === undefined) {
                  result = partial;
              }
              else {
                  result.failures = result.failures
                      .filter(curr => {
                      return !partial.failures.some(prev => curr.equals(prev));
                  })
                      .concat(partial.failures);
                  // we are not doing much with 'errorCount' and 'warningCount'
                  // apart from checking if they are greater than 0 thus no need to dedupe these.
                  result.errorCount += partial.errorCount;
                  result.warningCount += partial.warningCount;
                  result.fileNames = [...new Set([...result.fileNames, ...partial.fileNames])];
                  if (partial.fixes) {
                      result.fixes = result.fixes ? result.fixes.concat(partial.fixes) : partial.fixes;
                  }
              }
              context.reportProgress(++i, allPrograms.length);
          }
      }
      else {
          result = await _lint(projectTslint, systemRoot, tslintConfigPath, options);
      }
      if (result == undefined) {
          throw new Error('Invalid lint configuration. Nothing to lint.');
      }
      if (!options.silent) {
          const Formatter = projectTslint.findFormatter(options.format || '');
          if (!Formatter) {
              throw new Error(`Invalid lint format "${options.format}".`);
          }
          const formatter = new Formatter();
          const output = formatter.format(result.failures, result.fixes, result.fileNames);
          if (output.trim()) {
              context.logger.info(output);
          }
      }
      if (result.warningCount > 0 && printInfo) {
          context.logger.warn('Lint warnings found in the listed files.');
      }
      if (result.errorCount > 0 && printInfo) {
          context.logger.error('Lint errors found in the listed files.');
      }
      if (result.warningCount === 0 && result.errorCount === 0 && printInfo) {
          context.logger.info('All files pass linting.');
      }
      return {
          success: options.force || result.errorCount === 0,
      };
  }
  exports.default = architect_1.createBuilder(_run);
  async function _lint(projectTslint, systemRoot, tslintConfigPath, options, program, allPrograms) {
      const Linter = projectTslint.Linter;
      const Configuration = projectTslint.Configuration;
      const files = getFilesToLint(systemRoot, options, Linter, program);
      const lintOptions = {
          fix: !!options.fix,
          formatter: options.format,
      };
      const linter = new Linter(lintOptions, program);
      let lastDirectory = undefined;
      let configLoad;
      const lintedFiles = [];
      for (const file of files) {
          if (program && allPrograms) {
              // If it cannot be found in ANY program, then this is an error.
              if (allPrograms.every(p => p.getSourceFile(file) === undefined)) {
                  throw new Error(`File ${JSON.stringify(file)} is not part of a TypeScript project '${options.tsConfig}'.`);
              }
              else if (program.getSourceFile(file) === undefined) {
                  // The file exists in some other programs. We will lint it later (or earlier) in the loop.
                  continue;
              }
          }
          const contents = getFileContents(file);
          // Only check for a new tslint config if the path changes.
          const currentDirectory = path.dirname(file);
          if (currentDirectory !== lastDirectory) {
              configLoad = Configuration.findConfiguration(tslintConfigPath, file);
              lastDirectory = currentDirectory;
          }
          if (configLoad) {
              // Give some breathing space to other promises that might be waiting.
              await Promise.resolve();
              linter.lint(file, contents, configLoad.results);
              lintedFiles.push(file);
          }
      }
      return {
          ...linter.getResult(),
          fileNames: lintedFiles,
      };
  }
  function getFilesToLint(root, options, linter, program) {
      const ignore = options.exclude;
      const files = options.files || [];
      if (files.length > 0) {
          return files
              .map(file => glob.sync(file, { cwd: root, ignore, nodir: true }))
              .reduce((prev, curr) => prev.concat(curr), [])
              .map(file => path.join(root, file));
      }
      if (!program) {
          return [];
      }
      let programFiles = linter.getFileNames(program);
      if (ignore && ignore.length > 0) {
          // normalize to support ./ paths
          const ignoreMatchers = ignore
              .map(pattern => new minimatch_1.Minimatch(path.normalize(pattern), { dot: true }));
          programFiles = programFiles
              .filter(file => !ignoreMatchers.some(matcher => matcher.match(path.relative(root, file))));
      }
      return programFiles;
  }
  function getFileContents(file) {
      // NOTE: The tslint CLI checks for and excludes MPEG transport streams; this does not.
      try {
          return strip_bom_1.stripBom(fs_1.readFileSync(file, 'utf-8'));
      }
      catch (_a) {
          throw new Error(`Could not read file '${file}'.`);
      }
  }
  