import { makeSourceReader } from '../utils/source-reader.js';
import { stableHash } from '../utils/hash.js';
import { hasIgnoreAnnotation } from '../utils/ignore-annotation.js';
// try/catch whose body is empty, only console.log/warn/info/debug, or a bare
// return. console.error + rethrow intentional, skipped. MED.
export function detectSilentCatches(input) {
    const read = makeSourceReader(input.workspaceRoot, input.project);
    const findings = [];
    for (const rel of input.files) {
        if (!isAnalysable(rel))
            continue;
        const raw = read(rel);
        if (raw == null)
            continue;
        findings.push(...analyseFile(rel, raw));
    }
    return findings;
}
const CATCH_RE = /\bcatch\s*(?:\(([^)]*)\))?\s*\{/g;
function analyseFile(file, raw) {
    const out = [];
    for (const match of raw.matchAll(CATCH_RE)) {
        const openBraceIdx = match.index + match[0].length - 1;
        const closeBraceIdx = findMatchingBrace(raw, openBraceIdx);
        if (closeBraceIdx === -1)
            continue;
        const body = raw.slice(openBraceIdx + 1, closeBraceIdx);
        const verdict = classifyCatchBody(body);
        if (!verdict)
            continue;
        const line = lineOf(raw, match.index);
        if (hasIgnoreAnnotation(raw, line, 'silent-catch'))
            continue;
        const snippet = sliceSnippet(raw, match.index, closeBraceIdx);
        out.push({
            detectorId: 'silent-catch',
            severity: verdict.severity,
            confidence: 0.85,
            layer: 1,
            title: `Silent catch in ${file}:${line}`,
            description: verdict.blurb,
            evidence: [
                {
                    file,
                    range: { startLine: line, endLine: lineOf(raw, closeBraceIdx) },
                    snippet,
                },
            ],
            suggestion: 'Either rethrow, route to your error reporter (Sentry/Bugsnag/etc.), or document the deliberate swallow with a comment naming the failure mode you are choosing to ignore.',
            fingerprint: `silent-catch:${stableHash(`${file}:${line}`)}`,
        });
    }
    return out;
}
function classifyCatchBody(body) {
    // Author already documented the swallow with an intent comment — this
    // is exactly what the detector's suggestion asks people to do. Flagging
    // it again would punish the developer for following the guidance and
    // trains the team to ignore the detector. Match the same keyword set
    // we recommend in the suggestion text below.
    if (INTENT_COMMENT_RE.test(body))
        return null;
    const stripped = stripCommentsAndWhitespace(body);
    if (stripped === '') {
        return {
            severity: 'medium',
            blurb: 'The `catch` block is empty — every error inside the corresponding `try` is silently discarded.',
        };
    }
    // Single `return ...` with no operand or literal `null` / `undefined`.
    if (/^return\s*(?:undefined|null)?\s*;?$/.test(stripped)) {
        return {
            severity: 'medium',
            blurb: 'The `catch` block returns silently — the caller cannot distinguish "no result" from "errored out".',
        };
    }
    // Only a console.log/warn/info/debug call (NOT console.error — that's intentional).
    if (/^console\.(log|warn|info|debug)\([^;]*\);?$/.test(stripped)) {
        return {
            severity: 'medium',
            blurb: 'The `catch` block only logs to the console — failures never reach your error reporter or alerting.',
        };
    }
    return null;
}
// Keywords that signal the swallow is deliberate. Matched against
// raw body text (comments NOT stripped) so it picks up `// Deliberate
// swallow: …`, `// intentional — non-JSON frame`, `/* expected during
// shutdown */`, etc.
const INTENT_COMMENT_RE = /\/\/[^\n]*\b(deliberate|intentional|intentionally|expected|known(?:-?safe)?|on\s+purpose|swallow(?:ed)?|by\s+design|noop|no-op)\b|\/\*[\s\S]*?\b(deliberate|intentional|intentionally|expected|known(?:-?safe)?|on\s+purpose|swallow(?:ed)?|by\s+design|noop|no-op)\b[\s\S]*?\*\//i;
function stripCommentsAndWhitespace(body) {
    return body
        .replace(/\/\*[\s\S]*?\*\//g, '')
        .replace(/\/\/.*$/gm, '')
        .replace(/\s+/g, ' ')
        .trim();
}
function findMatchingBrace(raw, openIdx) {
    let depth = 0;
    let inString = null;
    let inLineComment = false;
    let inBlockComment = false;
    for (let i = openIdx; i < raw.length; i++) {
        const c = raw[i];
        const next = raw[i + 1];
        if (inLineComment) {
            if (c === '\n')
                inLineComment = false;
            continue;
        }
        if (inBlockComment) {
            if (c === '*' && next === '/') {
                inBlockComment = false;
                i++;
            }
            continue;
        }
        if (inString) {
            if (c === '\\') {
                i++;
                continue;
            }
            if (c === inString)
                inString = null;
            continue;
        }
        if (c === '/' && next === '/') {
            inLineComment = true;
            i++;
            continue;
        }
        if (c === '/' && next === '*') {
            inBlockComment = true;
            i++;
            continue;
        }
        if (c === '"' || c === "'" || c === '`') {
            inString = c;
            continue;
        }
        if (c === '{')
            depth++;
        else if (c === '}') {
            depth--;
            if (depth === 0)
                return i;
        }
    }
    return -1;
}
function lineOf(raw, idx) {
    return raw.slice(0, idx).split('\n').length;
}
function sliceSnippet(raw, startIdx, endIdx) {
    const lines = raw.split('\n');
    const startLine = lineOf(raw, startIdx);
    const endLine = lineOf(raw, endIdx);
    const from = Math.max(0, startLine - 1);
    const to = Math.min(lines.length, endLine + 1);
    return lines.slice(from, to).join('\n');
}
function isAnalysable(file) {
    return (/\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(file) &&
        !/(^|\/)node_modules\//.test(file) &&
        !/\.d\.ts$/.test(file));
}
//# sourceMappingURL=silent-catch.js.map