"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnsibleLint = void 0;
const path = __importStar(require("path"));
const vscode_uri_1 = require("vscode-uri");
const vscode_languageserver_1 = require("vscode-languageserver");
const misc_1 = require("../utils/misc");
const commandRunner_1 = require("../utils/commandRunner");
/**
 * Acts as and interface to ansible-lint and a cache of its output.
 *
 * ansible-lint may provide diagnostics for more than just the file for which
 * linting was triggered, and this is reflected in the implementation.
 */
class AnsibleLint {
    constructor(connection, context) {
        var _a;
        this.useProgressTracker = false;
        this.connection = connection;
        this.context = context;
        this.useProgressTracker =
            !!((_a = context.clientCapabilities.window) === null || _a === void 0 ? void 0 : _a.workDoneProgress);
    }
    /**
     * Perform linting for the given document.
     *
     * In case no errors are found for the current document, and linting has been
     * performed on opening the document, then only the cache is cleared, and not
     * the diagnostics on the client side. That way old diagnostics will persist
     * until the file is changed. This allows inspecting more complex errors
     * reported in other files.
     */
    doValidate(textDocument) {
        var _a, _b, _c;
        return __awaiter(this, void 0, void 0, function* () {
            let diagnostics = new Map();
            const workingDirectory = vscode_uri_1.URI.parse(this.context.workspaceFolder.uri).path;
            const mountPaths = new Set([workingDirectory]);
            const settings = yield this.context.documentSettings.get(textDocument.uri);
            let linterArguments = (_a = settings.validation.lint.arguments) !== null && _a !== void 0 ? _a : "";
            // Determine linter config file
            let ansibleLintConfigPath = (_c = (_b = linterArguments.match(/(?:^|\s)-c\s*(?<sep>[\s'"])(?<conf>.+?)(?:\k<sep>|$)/)) === null || _b === void 0 ? void 0 : _b.groups) === null || _c === void 0 ? void 0 : _c.conf;
            if (!ansibleLintConfigPath) {
                // Config file not provided in arguments -> search for one mimicking the
                // way ansible-lint looks for it, going up the directory structure
                const ansibleLintConfigFile = yield this.findAnsibleLintConfigFile(textDocument.uri);
                if (ansibleLintConfigFile) {
                    ansibleLintConfigPath = vscode_uri_1.URI.parse(ansibleLintConfigFile).path;
                    linterArguments = `${linterArguments} -c "${ansibleLintConfigPath}"`;
                    mountPaths.add(path.dirname(ansibleLintConfigPath));
                }
            }
            this._ansibleLintConfigFilePath = ansibleLintConfigPath;
            linterArguments = `${linterArguments} --offline --nocolor -f codeclimate`;
            const docPath = vscode_uri_1.URI.parse(textDocument.uri).path;
            mountPaths.add(path.dirname(docPath));
            const progressTracker = this.useProgressTracker
                ? yield this.connection.window.createWorkDoneProgress()
                : {
                    begin: () => { },
                    done: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
                };
            progressTracker.begin("ansible-lint", undefined, "Processing files...");
            const commandRunner = new commandRunner_1.CommandRunner(this.connection, this.context, settings);
            try {
                // get ansible-lint result on the doc
                const result = yield commandRunner.runCommand("ansible-lint", `${linterArguments} "${docPath}"`, workingDirectory, mountPaths);
                diagnostics = this.processReport(result.stdout, workingDirectory);
                if (result.stderr) {
                    this.connection.console.info(`[ansible-lint] ${result.stderr}`);
                }
            }
            catch (error) {
                if (error instanceof Error) {
                    const execError = error;
                    if (execError.stdout) {
                        diagnostics = this.processReport(execError.stdout, workingDirectory);
                    }
                    else {
                        if (execError.stderr) {
                            this.connection.console.info(`[ansible-lint] ${execError.stderr}`);
                        }
                        progressTracker.done();
                        this.connection.window.showErrorMessage(execError.message);
                        return;
                    }
                }
                else {
                    const exceptionString = `Exception in AnsibleLint service: ${JSON.stringify(error)}`;
                    progressTracker.done();
                    this.connection.console.error(exceptionString);
                    this.connection.window.showErrorMessage(exceptionString);
                    return;
                }
            }
            progressTracker.done();
            return diagnostics;
        });
    }
    processReport(result, workingDirectory) {
        const diagnostics = new Map();
        if (!result) {
            this.connection.console.warn("Standard output from ansible-lint is suspiciously empty.");
            return diagnostics;
        }
        try {
            const report = JSON.parse(result);
            if (report instanceof Array) {
                for (const item of report) {
                    if (typeof item.check_name === "string" &&
                        item.location &&
                        typeof item.location.path === "string" &&
                        item.location.lines &&
                        (item.location.lines.begin ||
                            typeof item.location.lines.begin === "number")) {
                        const begin_line = item.location.lines.begin.line || item.location.lines.begin || 1;
                        const begin_column = item.location.lines.begin.column || 1;
                        const start = {
                            line: begin_line - 1,
                            character: begin_column - 1,
                        };
                        const end = {
                            line: begin_line - 1,
                            character: vscode_languageserver_1.integer.MAX_VALUE,
                        };
                        const range = {
                            start: start,
                            end: end,
                        };
                        let severity = vscode_languageserver_1.DiagnosticSeverity.Error;
                        if (item.severity) {
                            if (item.severity === "major") {
                                severity = vscode_languageserver_1.DiagnosticSeverity.Error;
                            }
                            else if (item.severity === "minor") {
                                severity = vscode_languageserver_1.DiagnosticSeverity.Warning;
                            }
                        }
                        const path = `${workingDirectory}/${item.location.path}`;
                        const locationUri = vscode_uri_1.URI.file(path).toString();
                        const helpUri = item.url ? item.url : undefined;
                        const helpUrlName = helpUri ? item.check_name : undefined;
                        let fileDiagnostics = diagnostics.get(locationUri);
                        if (!fileDiagnostics) {
                            fileDiagnostics = [];
                            diagnostics.set(locationUri, fileDiagnostics);
                        }
                        let message = item.check_name;
                        if (item.description) {
                            message = item.description;
                        }
                        fileDiagnostics.push({
                            message: message,
                            range: range || vscode_languageserver_1.Range.create(0, 0, 0, 0),
                            severity: severity,
                            source: "ansible-lint",
                            code: helpUrlName,
                            codeDescription: { href: helpUri },
                        });
                    }
                }
            }
        }
        catch (error) {
            this.connection.window.showErrorMessage("Could not parse ansible-lint output. Please check your ansible-lint installation & configuration." +
                " More info in `Ansible Server` output.");
            let message;
            if (error instanceof Error) {
                message = error.message;
            }
            else {
                message = JSON.stringify(error);
            }
            this.connection.console.error(`Exception while parsing ansible-lint output: ${message}` +
                `\nTried to parse the following:\n${result}`);
        }
        return diagnostics;
    }
    findAnsibleLintConfigFile(uri) {
        return __awaiter(this, void 0, void 0, function* () {
            // find configuration path
            let configPath;
            const pathArray = uri.split("/");
            // Find first configuration file going up until workspace root
            for (let index = pathArray.length - 1; index >= 0; index--) {
                let candidatePath = pathArray
                    .slice(0, index)
                    .concat(".ansible-lint")
                    .join("/");
                const workspacePath = vscode_uri_1.URI.parse(this.context.workspaceFolder.uri).path;
                candidatePath = vscode_uri_1.URI.parse(candidatePath).path;
                if (!candidatePath.startsWith(workspacePath)) {
                    // we've gone out of the workspace folder
                    break;
                }
                if (yield (0, misc_1.fileExists)(candidatePath)) {
                    configPath = vscode_uri_1.URI.parse(candidatePath).path;
                    break;
                }
            }
            return configPath;
        });
    }
    get ansibleLintConfigFilePath() {
        return this._ansibleLintConfigFilePath;
    }
}
exports.AnsibleLint = AnsibleLint;
//# sourceMappingURL=ansibleLint.js.map