"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const constants = require("./constants");
const instances_1 = require("./instances");
const utils_1 = require("./utils");
function makeAfterCompile(instance, configFilePath) {
    let getCompilerOptionDiagnostics = true;
    let checkAllFilesForErrors = true;
    return (compilation, callback) => {
        // Don't add errors for child compilations
        if (compilation.compiler.isChild()) {
            callback();
            return;
        }
        removeTSLoaderErrors(compilation.errors);
        provideCompilerOptionDiagnosticErrorsToWebpack(getCompilerOptionDiagnostics, compilation, instance, configFilePath);
        getCompilerOptionDiagnostics = false;
        const modules = determineModules(compilation);
        const filesToCheckForErrors = determineFilesToCheckForErrors(checkAllFilesForErrors, instance);
        checkAllFilesForErrors = false;
        const filesWithErrors = new Map();
        provideErrorsToWebpack(filesToCheckForErrors, filesWithErrors, compilation, modules, instance);
        provideDeclarationFilesToWebpack(filesToCheckForErrors, instance, compilation);
        provideSolutionErrorsToWebpack(compilation, modules, instance);
        provideTsBuildInfoFilesToWebpack(instance, compilation);
        instance.filesWithErrors = filesWithErrors;
        instance.modifiedFiles = undefined;
        instance.projectsMissingSourceMaps = new Set();
        callback();
    };
}
exports.makeAfterCompile = makeAfterCompile;
/**
 * handle compiler option errors after the first compile
 */
function provideCompilerOptionDiagnosticErrorsToWebpack(getCompilerOptionDiagnostics, compilation, instance, configFilePath) {
    if (getCompilerOptionDiagnostics) {
        const { languageService, loaderOptions, compiler, program } = instance;
        const errorsToAdd = utils_1.formatErrors(program === undefined
            ? languageService.getCompilerOptionsDiagnostics()
            : program.getOptionsDiagnostics(), loaderOptions, instance.colors, compiler, { file: configFilePath || 'tsconfig.json' }, compilation.compiler.context);
        compilation.errors.push(...errorsToAdd);
    }
}
/**
 * build map of all modules based on normalized filename
 * this is used for quick-lookup when trying to find modules
 * based on filepath
 */
function determineModules(compilation) {
    return compilation.modules.reduce((modules, module) => {
        if (module.resource) {
            const modulePath = path.normalize(module.resource);
            const existingModules = modules.get(modulePath);
            if (existingModules !== undefined) {
                if (existingModules.indexOf(module) === -1) {
                    existingModules.push(module);
                }
            }
            else {
                modules.set(modulePath, [module]);
            }
        }
        return modules;
    }, new Map());
}
function determineFilesToCheckForErrors(checkAllFilesForErrors, instance) {
    const { files, modifiedFiles, filesWithErrors, otherFiles } = instance;
    // calculate array of files to check
    const filesToCheckForErrors = new Map();
    if (checkAllFilesForErrors) {
        // check all files on initial run
        for (const [filePath, file] of files) {
            filesToCheckForErrors.set(filePath, file);
        }
        for (const [filePath, file] of otherFiles) {
            filesToCheckForErrors.set(filePath, file);
        }
    }
    else if (modifiedFiles !== null && modifiedFiles !== undefined) {
        // check all modified files, and all dependants
        for (const modifiedFileName of modifiedFiles.keys()) {
            utils_1.collectAllDependants(instance.reverseDependencyGraph, modifiedFileName).forEach(fileName => {
                const fileToCheckForErrors = files.get(fileName) || otherFiles.get(fileName);
                filesToCheckForErrors.set(fileName, fileToCheckForErrors);
            });
        }
    }
    // re-check files with errors from previous build
    if (filesWithErrors !== undefined) {
        for (const [fileWithErrorName, fileWithErrors] of filesWithErrors) {
            filesToCheckForErrors.set(fileWithErrorName, fileWithErrors);
        }
    }
    return filesToCheckForErrors;
}
function provideErrorsToWebpack(filesToCheckForErrors, filesWithErrors, compilation, modules, instance) {
    const { compiler, files, loaderOptions, compilerOptions, otherFiles } = instance;
    const filePathRegex = compilerOptions.allowJs === true
        ? constants.dtsTsTsxJsJsxRegex
        : constants.dtsTsTsxRegex;
    // I’m pretty sure this will never be undefined here
    const program = utils_1.ensureProgram(instance);
    for (const filePath of filesToCheckForErrors.keys()) {
        if (filePath.match(filePathRegex) === null) {
            continue;
        }
        const sourceFile = program && program.getSourceFile(filePath);
        // If the source file is undefined, that probably means it’s actually part of an unbuilt project reference,
        // which will have already produced a more useful error than the one we would get by proceeding here.
        // If it’s undefined and we’re not using project references at all, I guess carry on so the user will
        // get a useful error about which file was unexpectedly missing.
        if (utils_1.isUsingProjectReferences(instance) && sourceFile === undefined) {
            continue;
        }
        const errors = [];
        if (program && sourceFile) {
            errors.push(...program.getSyntacticDiagnostics(sourceFile), ...program
                .getSemanticDiagnostics(sourceFile)
                // Output file has not been built from source file - this message is redundant with
                // program.getOptionsDiagnostics() separately added in instances.ts
                .filter(({ code }) => code !== 6305));
        }
        if (errors.length > 0) {
            const fileWithError = files.get(filePath) || otherFiles.get(filePath);
            filesWithErrors.set(filePath, fileWithError);
        }
        // if we have access to a webpack module, use that
        const associatedModules = modules.get(filePath);
        if (associatedModules !== undefined) {
            associatedModules.forEach(module => {
                // remove any existing errors
                removeTSLoaderErrors(module.errors);
                // append errors
                const formattedErrors = utils_1.formatErrors(errors, loaderOptions, instance.colors, compiler, { module }, compilation.compiler.context);
                module.errors.push(...formattedErrors);
                compilation.errors.push(...formattedErrors);
            });
        }
        else {
            // otherwise it's a more generic error
            const formattedErrors = utils_1.formatErrors(errors, loaderOptions, instance.colors, compiler, { file: filePath }, compilation.compiler.context);
            compilation.errors.push(...formattedErrors);
        }
    }
}
function provideSolutionErrorsToWebpack(compilation, modules, instance) {
    if (!instance.solutionBuilderHost ||
        !(instance.solutionBuilderHost.diagnostics.global.length ||
            instance.solutionBuilderHost.diagnostics.perFile.size)) {
        return;
    }
    const { compiler, loaderOptions, solutionBuilderHost: { diagnostics } } = instance;
    for (const [filePath, perFileDiagnostics] of diagnostics.perFile) {
        // if we have access to a webpack module, use that
        const associatedModules = modules.get(filePath);
        if (associatedModules !== undefined) {
            associatedModules.forEach(module => {
                // remove any existing errors
                removeTSLoaderErrors(module.errors);
                // append errors
                const formattedErrors = utils_1.formatErrors(perFileDiagnostics, loaderOptions, instance.colors, compiler, { module }, compilation.compiler.context);
                module.errors.push(...formattedErrors);
                compilation.errors.push(...formattedErrors);
            });
        }
        else {
            // otherwise it's a more generic error
            const formattedErrors = utils_1.formatErrors(perFileDiagnostics, loaderOptions, instance.colors, compiler, { file: filePath }, compilation.compiler.context);
            compilation.errors.push(...formattedErrors);
        }
    }
    // Add global solution errors
    compilation.errors.push(...utils_1.formatErrors(diagnostics.global, instance.loaderOptions, instance.colors, instance.compiler, { file: 'tsconfig.json' }, compilation.compiler.context));
}
/**
 * gather all declaration files from TypeScript and output them to webpack
 */
function provideDeclarationFilesToWebpack(filesToCheckForErrors, instance, compilation) {
    for (const filePath of filesToCheckForErrors.keys()) {
        if (filePath.match(constants.tsTsxRegex) === null) {
            continue;
        }
        const outputFiles = instances_1.getEmitOutput(instance, filePath);
        const declarationFiles = outputFiles.filter(outputFile => outputFile.name.match(constants.dtsDtsxOrDtsDtsxMapRegex));
        declarationFiles.forEach(declarationFile => {
            const assetPath = path.relative(compilation.compiler.outputPath, declarationFile.name);
            compilation.assets[assetPath] = {
                source: () => declarationFile.text,
                size: () => declarationFile.text.length
            };
        });
    }
}
function getOutputPathForBuildInfo(compiler, options) {
    return compiler.getTsBuildInfoEmitOutputFilePath
        ? compiler.getTsBuildInfoEmitOutputFilePath(options)
        : compiler.getOutputPathForBuildInfo(options);
}
/**
 * gather all .tsbuildinfo for the project
 */
function provideTsBuildInfoFilesToWebpack(instance, compilation) {
    if (instance.solutionBuilderHost && instance.modifiedFiles) {
        const program = utils_1.ensureProgram(instance);
        if (program) {
            instances_1.forEachResolvedProjectReference(program.getResolvedProjectReferences(), resolvedRef => {
                if (resolvedRef.commandLine.fileNames.some(f => instance.modifiedFiles.has(path.resolve(f)))) {
                    const buildInfoPath = getOutputPathForBuildInfo(instance.compiler, resolvedRef.commandLine.options);
                    if (buildInfoPath) {
                        const text = instance.compiler.sys.readFile(buildInfoPath);
                        if (text) {
                            const assetPath = path.relative(compilation.compiler.outputPath, path.resolve(buildInfoPath));
                            compilation.assets[assetPath] = {
                                source: () => text,
                                size: () => text.length
                            };
                        }
                    }
                }
            });
        }
    }
    if (instance.watchHost) {
        // Ensure emit is complete
        instances_1.getEmitFromWatchHost(instance);
        if (instance.watchHost.tsbuildinfo) {
            const { tsbuildinfo } = instance.watchHost;
            const assetPath = path.relative(compilation.compiler.outputPath, path.resolve(tsbuildinfo.name));
            compilation.assets[assetPath] = {
                source: () => tsbuildinfo.text,
                size: () => tsbuildinfo.text.length
            };
        }
        instance.watchHost.outputFiles.clear();
        instance.watchHost.tsbuildinfo = undefined;
    }
}
/**
 * handle all other errors. The basic approach here to get accurate error
 * reporting is to start with a "blank slate" each compilation and gather
 * all errors from all files. Since webpack tracks errors in a module from
 * compilation-to-compilation, and since not every module always runs through
 * the loader, we need to detect and remove any pre-existing errors.
 */
function removeTSLoaderErrors(errors) {
    let index = -1;
    let length = errors.length;
    while (++index < length) {
        if (errors[index].loaderSource === 'ts-loader') {
            errors.splice(index--, 1);
            length--;
        }
    }
}
//# sourceMappingURL=after-compile.js.map