"use strict";
/**
 * User-defined suppressions for scanner findings.
 *
 * Reads `.dxkit-suppressions.json` at the repo root. Example:
 *
 *   {
 *     "gitleaks": [
 *       { "rule": "generic-api-key", "paths": ["test/fixtures/**"], "reason": "fake keys in fixtures" }
 *     ],
 *     "semgrep": [
 *       { "rule": "*", "paths": ["scripts/migrations/**"], "reason": "legacy, grandfathered" }
 *     ]
 *   }
 *
 * A finding is suppressed when:
 *   - its rule matches an entry's `rule` (exact string, or "*" as wildcard), AND
 *   - its path matches at least one of the entry's `paths` globs.
 *
 * Globs support `**` (any path segments), `*` (any chars except `/`), and `?`.
 * Leading `./` is normalized away.
 */
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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadSuppressions = loadSuppressions;
exports.clearSuppressionsCache = clearSuppressionsCache;
exports.applySuppressions = applySuppressions;
exports.globToRegex = globToRegex;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const EMPTY = { gitleaks: [], semgrep: [], slop: [] };
const cache = new Map();
/** Load suppressions for a repo root, memoized. Returns empty suppressions if file is missing/malformed. */
function loadSuppressions(cwd) {
    const hit = cache.get(cwd);
    if (hit)
        return hit;
    const filePath = path.join(cwd, '.dxkit-suppressions.json');
    let raw;
    try {
        raw = fs.readFileSync(filePath, 'utf-8');
    }
    catch {
        cache.set(cwd, EMPTY);
        return EMPTY;
    }
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch {
        cache.set(cwd, EMPTY);
        return EMPTY;
    }
    const resolved = {
        gitleaks: extractRules(parsed, 'gitleaks'),
        semgrep: extractRules(parsed, 'semgrep'),
        slop: extractRules(parsed, 'slop'),
    };
    cache.set(cwd, resolved);
    return resolved;
}
/** Clear memo — useful for tests. */
function clearSuppressionsCache() {
    cache.clear();
}
function extractRules(parsed, key) {
    if (!parsed || typeof parsed !== 'object')
        return [];
    const bucket = parsed[key];
    if (!Array.isArray(bucket))
        return [];
    return bucket
        .filter((e) => !!e && typeof e === 'object')
        .map((e) => ({
        rule: typeof e.rule === 'string' ? e.rule : '',
        paths: Array.isArray(e.paths)
            ? e.paths.filter((p) => typeof p === 'string')
            : [],
        reason: typeof e.reason === 'string' ? e.reason : undefined,
    }))
        .filter((r) => r.rule && r.paths.length > 0);
}
/**
 * Apply suppressions to a set of findings.
 *
 * Callers pass accessors (findings vary in shape across tools). Returns the
 * kept findings plus a parallel list of suppressed findings with the matched
 * reason annotated.
 */
function applySuppressions(findings, rules, getRule, getPath) {
    if (rules.length === 0)
        return { kept: findings, suppressed: [] };
    const compiled = rules.map((r) => ({
        rule: r.rule,
        regexes: r.paths.map(globToRegex),
        reason: r.reason,
    }));
    const kept = [];
    const suppressed = [];
    for (const f of findings) {
        const fRule = getRule(f);
        const fPath = normalizePath(getPath(f));
        let matched = null;
        for (const c of compiled) {
            if (c.rule !== '*' && c.rule !== fRule)
                continue;
            if (c.regexes.some((re) => re.test(fPath))) {
                matched = { reason: c.reason };
                break;
            }
        }
        if (matched) {
            suppressed.push({ finding: f, reason: matched.reason });
        }
        else {
            kept.push(f);
        }
    }
    return { kept, suppressed };
}
/** Convert a glob pattern to an anchored regex. Supports `**`, `*`, `?`. */
function globToRegex(glob) {
    let g = glob;
    if (g.startsWith('./'))
        g = g.slice(2);
    let re = '';
    let i = 0;
    while (i < g.length) {
        // leading or mid-path `**/` — optional path prefix
        if (g.startsWith('**/', i)) {
            re += '(?:.*/)?';
            i += 3;
            continue;
        }
        // trailing `/**` — optional path suffix
        if (i + 3 === g.length && g.startsWith('/**', i)) {
            re += '(?:/.*)?';
            i += 3;
            continue;
        }
        // bare `**` — any chars including slashes
        if (g.startsWith('**', i)) {
            re += '.*';
            i += 2;
            continue;
        }
        const c = g[i];
        if (c === '*') {
            re += '[^/]*';
        }
        else if (c === '?') {
            re += '[^/]';
        }
        else if ('.+()|[]{}^$\\'.includes(c)) {
            re += '\\' + c;
        }
        else {
            re += c;
        }
        i++;
    }
    return new RegExp('^' + re + '$');
}
function normalizePath(p) {
    let n = p.replace(/\\/g, '/');
    if (n.startsWith('./'))
        n = n.slice(2);
    return n;
}
//# sourceMappingURL=suppressions.js.map