// P1: JSON.parse without Partial — CRITICAL
const JSON_PARSE_CAST = /JSON\.parse\([^)]+\)\s+as\s+(?!Partial<|unknown|Record<string)[A-Z]/;
// P2: Incomplete optional chaining — HIGH
// x?.field.method() should be x?.field?.method()
const INCOMPLETE_OPTIONAL_CHAIN = /\?\.[a-zA-Z_]+\.[a-zA-Z_]+\(/;
// P3: readFileSync/readFile without try in exported function
const READ_FILE_PATTERN = /\breadFile(?:Sync)?\s*\(/;
const EXPORT_FUNCTION_PATTERN = /export\s+(?:async\s+)?function\s+/;
const TRY_PATTERN = /\btry\s*\{/;
// P4: Array index double-access — HIGH
// Detects pattern: arr[N]?.prop === ... then arr[N].something later
const ARRAY_INDEX_GUARD_MISS = /\[\d+\]\?\.[a-zA-Z_]+/;
const ARRAY_INDEX_NO_GUARD = /\[\d+\]\.[a-zA-Z_]+/;
// P5: .map/.filter on cast array without validation — MEDIUM
const CAST_ARRAY_MAP = /\)\s+as\s+(?:\w+\s*&\s*)?\w+(?:<[^>]+>)?\[\]\s*[;\n].*?\.\s*(?:map|filter|forEach)\s*\(/s;
// Magic comment suppressors — same pattern as eslint-disable-next-line.
// Use when the developer has external knowledge (e.g. writer is under our
// control, JSON shape is guaranteed by an upstream contract).
const SUPPRESS_NEXT_LINE = /\/\/\s*crash-shield-ignore-next-line/;
const SUPPRESS_LINE = /\/\/\s*crash-shield-ignore/;
const SUPPRESS_FILE = /\/\/\s*@crash-shield-ignore-file/;
function isFileSuppressed(content) {
    return SUPPRESS_FILE.test(content);
}
function isSuppressed(lines, lineIdx) {
    const current = lines[lineIdx] ?? '';
    if (SUPPRESS_LINE.test(current)) {
        return true;
    }
    const prev = lines[lineIdx - 1] ?? '';
    return SUPPRESS_NEXT_LINE.test(prev);
}
function detectLine(content, pattern) {
    const lines = content.split('\n');
    for (let i = 0; i < lines.length; i++) {
        if (pattern.test(lines[i] ?? '')) {
            return i + 1;
        }
    }
    return 1;
}
function findAllMatchLines(content, pattern) {
    const lines = content.split('\n');
    const result = [];
    for (let i = 0; i < lines.length; i++) {
        if (pattern.test(lines[i] ?? '')) {
            result.push(i + 1);
        }
    }
    return result;
}
function detectP1(filePath, content) {
    const risks = [];
    const lines = content.split('\n');
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i] ?? '';
        if (JSON_PARSE_CAST.test(line) && !isSuppressed(lines, i)) {
            risks.push({
                file: filePath,
                line: i + 1,
                pattern: 'JSON_PARSE_UNSAFE_CAST',
                severity: 'CRITICAL',
                description: 'JSON.parse result cast to concrete type without Partial<>. Runtime fields may be undefined despite TypeScript types.',
                suggestedFix: 'Use safeJsonParse(raw, fallback) from src/storage/base-store.ts, or change `as Type` to `as Partial<Type>` and validate required fields. Suppress with `// crash-shield-ignore-next-line` if the writer is under our control.',
                autoFixable: true,
            });
        }
    }
    return risks;
}
function detectP2(filePath, content) {
    const risks = [];
    const lines = content.split('\n');
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i] ?? '';
        if (INCOMPLETE_OPTIONAL_CHAIN.test(line) && !isSuppressed(lines, i)) {
            risks.push({
                file: filePath,
                line: i + 1,
                pattern: 'INCOMPLETE_OPTIONAL_CHAIN',
                severity: 'HIGH',
                description: 'x?.field.method() only guards against null/undefined x. If field is undefined at runtime, .method() will crash.',
                suggestedFix: 'Use x?.field?.method() to guard each access.',
                autoFixable: true,
            });
        }
    }
    return risks;
}
function detectP3(filePath, content) {
    const risks = [];
    if (!EXPORT_FUNCTION_PATTERN.test(content) || !READ_FILE_PATTERN.test(content)) {
        return risks;
    }
    const lines = content.split('\n');
    let inExportedFunction = false;
    let braceDepth = 0;
    let hasTry = false;
    let functionStartLine = 0;
    let hasReadFile = false;
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i] ?? '';
        if (EXPORT_FUNCTION_PATTERN.test(line)) {
            inExportedFunction = true;
            braceDepth = 0;
            hasTry = false;
            hasReadFile = false;
            functionStartLine = i + 1;
        }
        if (inExportedFunction) {
            braceDepth += (line.match(/\{/g) ?? []).length;
            braceDepth -= (line.match(/\}/g) ?? []).length;
            if (TRY_PATTERN.test(line)) {
                hasTry = true;
            }
            if (READ_FILE_PATTERN.test(line)) {
                hasReadFile = true;
            }
            if (braceDepth <= 0 && functionStartLine > 0) {
                if (hasReadFile && !hasTry && !isSuppressed(lines, functionStartLine - 1)) {
                    risks.push({
                        file: filePath,
                        line: functionStartLine,
                        pattern: 'READ_FILE_WITHOUT_TRY_CATCH',
                        severity: 'HIGH',
                        description: 'Exported function uses readFile/readFileSync without try/catch. File not found throws unhandled error.',
                        suggestedFix: 'Wrap readFile calls in try/catch and handle ENOENT.',
                        autoFixable: false,
                    });
                }
                inExportedFunction = false;
            }
        }
    }
    return risks;
}
function detectP4(filePath, content) {
    const risks = [];
    const hasGuard = ARRAY_INDEX_GUARD_MISS.test(content);
    const hasNoGuard = ARRAY_INDEX_NO_GUARD.test(content);
    if (hasGuard && hasNoGuard) {
        const lines = content.split('\n');
        const line = detectLine(content, ARRAY_INDEX_NO_GUARD);
        if (!isSuppressed(lines, line - 1)) {
            risks.push({
                file: filePath,
                line,
                pattern: 'ARRAY_INDEX_DOUBLE_ACCESS',
                severity: 'HIGH',
                description: 'Array index accessed with optional chaining in one place but without guard in another. Second access can crash if element is undefined.',
                suggestedFix: 'Use a variable to store arr[i] and check once: const item = arr[i]; if (item) ...',
                autoFixable: false,
            });
        }
    }
    return risks;
}
function detectP5(filePath, content) {
    const risks = [];
    if (CAST_ARRAY_MAP.test(content)) {
        const allLines = content.split('\n');
        const matchLines = findAllMatchLines(content, /\)\s+as\s+\w+(?:<[^>]+>)?\[\]/);
        for (const line of matchLines) {
            if (isSuppressed(allLines, line - 1)) {
                continue;
            }
            risks.push({
                file: filePath,
                line,
                pattern: 'CAST_ARRAY_NO_VALIDATION',
                severity: 'MEDIUM',
                description: 'Array cast without runtime validation then iterated. JSON.parse result fields are not guaranteed at runtime.',
                suggestedFix: 'Use Array.isArray() check before map/filter, or validate with Zod.',
                autoFixable: false,
            });
        }
    }
    return risks;
}
export const typescriptDetector = {
    language: 'TypeScript',
    fileExtensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'],
    detect(filePath, content) {
        if (isFileSuppressed(content)) {
            return [];
        }
        return [
            ...detectP1(filePath, content),
            ...detectP2(filePath, content),
            ...detectP3(filePath, content),
            ...detectP4(filePath, content),
            ...detectP5(filePath, content),
        ];
    },
};
//# sourceMappingURL=typescript-detector.js.map