/**
 * ESLint Scanner Integration
 *
 * Code quality and security scanning using ESLint.
 *
 * @module scanners/eslint
 */
import { exec } from "child_process";
import { promisify } from "util";
import { access } from "fs/promises";
import { join } from "path";
function buildErrorDetails(phase, message, suggestions = [], fullOutput) {
    return {
        message,
        phase,
        suggestions,
        fullOutput,
    };
}
const execAsync = promisify(exec);
/**
 * Security-related ESLint rules to flag
 */
const SECURITY_RULES = new Set([
    "no-eval",
    "no-implied-eval",
    "no-new-func",
    "no-script-url",
    "@typescript-eslint/no-implied-eval",
    "security/detect-eval-with-expression",
    "security/detect-non-literal-fs-filename",
    "security/detect-non-literal-regexp",
    "security/detect-non-literal-require",
    "security/detect-object-injection",
    "security/detect-possible-timing-attacks",
    "security/detect-pseudoRandomBytes",
    "security/detect-unsafe-regex",
    "security/detect-buffer-noassert",
    "security/detect-child-process",
    "security/detect-disable-mustache-escape",
    "security/detect-no-csrf-before-method-override",
    "security/detect-bidi-characters",
]);
/**
 * Check if ESLint is available
 */
export async function checkEslintAvailable() {
    try {
        const { stdout } = await execAsync("npx eslint --version", { timeout: 10000 });
        const version = stdout.trim();
        return {
            scanner: "eslint",
            available: true,
            version,
        };
    }
    catch (error) {
        return {
            scanner: "eslint",
            available: false,
            error: error instanceof Error ? error.message : "ESLint not found",
        };
    }
}
/**
 * Map ESLint severity to vaspera severity
 */
function mapSeverity(eslintSeverity, ruleId) {
    // Security rules are always high severity
    if (ruleId && SECURITY_RULES.has(ruleId)) {
        return "high";
    }
    // Map based on ESLint severity
    return eslintSeverity === 2 ? "medium" : "low";
}
/**
 * Check if project has ESLint configuration
 */
async function hasEslintConfig(projectPath) {
    const configFiles = [
        ".eslintrc.js",
        ".eslintrc.cjs",
        ".eslintrc.json",
        ".eslintrc.yml",
        ".eslintrc.yaml",
        "eslint.config.js",
        "eslint.config.mjs",
        "eslint.config.cjs",
    ];
    for (const file of configFiles) {
        try {
            await access(join(projectPath, file));
            return true;
        }
        catch {
            // File doesn't exist
        }
    }
    // Check package.json for eslintConfig
    try {
        const pkgPath = join(projectPath, "package.json");
        await access(pkgPath);
        const { readFile } = await import("fs/promises");
        const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
        return "eslintConfig" in pkg;
    }
    catch {
        return false;
    }
}
/**
 * Run ESLint on a project
 */
export async function runEslint(projectPath, options) {
    const startTime = Date.now();
    try {
        // Check if ESLint is available
        const availability = await checkEslintAvailable();
        if (!availability.available) {
            return {
                scanner: "eslint",
                findings: [],
                duration: Date.now() - startTime,
                success: false,
                error: "ESLint is not installed",
                errorDetails: buildErrorDetails("init", "ESLint is not installed", [
                    "Install ESLint: npm install eslint --save-dev",
                    "Or globally: npm install -g eslint",
                    "Then run: npx eslint --init to configure",
                ]),
            };
        }
        // Check if project has ESLint config
        const hasConfig = await hasEslintConfig(projectPath);
        if (!hasConfig) {
            return {
                scanner: "eslint",
                findings: [],
                duration: Date.now() - startTime,
                success: false,
                error: "No ESLint configuration found",
                errorDetails: buildErrorDetails("init", "No ESLint configuration found", [
                    "Run: npx eslint --init to create a config",
                    "Or create eslint.config.js (flat config) or .eslintrc.json",
                    "Check eslint.org/docs/user-guide/configuring",
                ]),
            };
        }
        // Build command
        const extensions = options?.extensions || [".js", ".jsx", ".ts", ".tsx"];
        const extArg = extensions.map((e) => e.replace(".", "")).join(",");
        let command = `npx eslint "${projectPath}" --ext ${extArg} --format json`;
        if (options?.fix) {
            command += " --fix";
        }
        // Run ESLint
        const { stdout, stderr } = await execAsync(command, {
            timeout: options?.timeout || 120000,
            maxBuffer: 10 * 1024 * 1024, // 10MB
            cwd: projectPath,
        }).catch((error) => {
            // ESLint exits with code 1 if there are errors
            if (error.stdout) {
                return { stdout: error.stdout, stderr: error.stderr || "" };
            }
            throw error;
        });
        // Parse JSON output
        let output;
        try {
            output = JSON.parse(stdout);
        }
        catch {
            const combinedOutput = `stdout: ${stdout.slice(0, 2000)}\nstderr: ${stderr?.slice(0, 500) || ""}`;
            return {
                scanner: "eslint",
                findings: [],
                duration: Date.now() - startTime,
                success: false,
                error: "Failed to parse ESLint output",
                errorDetails: buildErrorDetails("parse", "ESLint output was not valid JSON", [
                    "Check if ESLint config is valid",
                    "Run: npx eslint --print-config . to debug",
                    "Ensure no plugins are missing",
                ], combinedOutput),
            };
        }
        // Convert to DeterministicFindings
        const findings = [];
        for (const file of output) {
            const relativePath = file.filePath.replace(projectPath + "/", "");
            for (const message of file.messages) {
                if (!message.ruleId)
                    continue; // Skip parsing errors
                findings.push({
                    scanner: "eslint",
                    ruleId: `eslint:${message.ruleId}`,
                    file: relativePath,
                    line: message.line,
                    column: message.column,
                    endLine: message.endLine,
                    endColumn: message.endColumn,
                    message: message.message,
                    severity: mapSeverity(message.severity, message.ruleId),
                    confidence: 100,
                    fixAvailable: !!message.fix,
                    metadata: {
                        ruleId: message.ruleId,
                        eslintSeverity: message.severity,
                        isSecurityRule: SECURITY_RULES.has(message.ruleId),
                    },
                });
            }
        }
        // Count files scanned
        const filesScanned = output.length;
        const errorCount = output.reduce((sum, f) => sum + f.errorCount, 0);
        const warningCount = output.reduce((sum, f) => sum + f.warningCount, 0);
        return {
            scanner: "eslint",
            findings,
            duration: Date.now() - startTime,
            success: true,
            version: availability.version,
            filesScanned,
        };
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : "Unknown error";
        const suggestions = [];
        if (errorMessage.includes("Cannot find module")) {
            suggestions.push("Run: npm install to install dependencies");
            suggestions.push("Check if required ESLint plugins are installed");
        }
        else if (errorMessage.includes("ENOENT")) {
            suggestions.push("Ensure ESLint is installed in the project");
        }
        else if (errorMessage.includes("timeout")) {
            suggestions.push("Increase timeout or reduce number of files");
            suggestions.push("Use .eslintignore to skip large directories");
        }
        else {
            suggestions.push("Check ESLint configuration for errors");
            suggestions.push("Run: npx eslint --debug to get more info");
        }
        return {
            scanner: "eslint",
            findings: [],
            duration: Date.now() - startTime,
            success: false,
            error: errorMessage,
            errorDetails: buildErrorDetails("scan", errorMessage, suggestions),
        };
    }
}
/**
 * Check if a project is likely to use ESLint
 */
export async function detectEslint(projectPath) {
    return hasEslintConfig(projectPath);
}
//# sourceMappingURL=eslint.js.map