"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) {
        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}'.`);
    }
}
