"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 () {
    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.deadCodeRules = void 0;
/**
 * Dead Code Rules — Find unused exports, orphan files, unused components
 */
const path = __importStar(require("path"));
function diag(rule, filePath, message, opts = {}) {
    return {
        ruleId: rule.id,
        filePath,
        line: opts.line,
        severity: rule.severity,
        message,
        help: opts.help || rule.help,
        autoFixable: opts.autoFixable ?? false,
    };
}
// ─── Rules ────────────────────────────────────────────────────────
const orphanFiles = {
    id: "dead/orphan-files",
    name: "Orphan Files",
    category: "dead",
    severity: "warning",
    description: "Files not imported by any other file in the project",
    help: "Delete orphan files or import them where needed",
    appliesTo: ["node", "nextjs"],
    async check(ctx) {
        const results = [];
        // Find all .ts/.tsx/.js/.jsx files
        const allFiles = await ctx.findFiles(/\.(ts|tsx|js|jsx)$/);
        if (allFiles.length === 0)
            return results;
        // Build import graph
        const importedFiles = new Set();
        // Files that are entry points (not expected to be imported)
        const entryPatterns = [
            /page\.(tsx|jsx|ts|js)$/,
            /layout\.(tsx|jsx|ts|js)$/,
            /route\.(tsx|jsx|ts|js)$/,
            /loading\.(tsx|jsx|ts|js)$/,
            /error\.(tsx|jsx|ts|js)$/,
            /not-found\.(tsx|jsx|ts|js)$/,
            /middleware\.(ts|js)$/,
            /\bindex\.(ts|tsx|js|jsx)$/,
            /\.config\.(ts|js|mjs|cjs)$/,
            /\.test\.(ts|tsx|js|jsx)$/,
            /\.spec\.(ts|tsx|js|jsx)$/,
            /cli\.(ts|js)$/,
        ];
        for (const f of allFiles) {
            // Skip generated files
            if (f.startsWith(".mycontext/") || f.includes("/.mycontext/"))
                continue;
            const content = await ctx.readFile(f);
            if (!content)
                continue;
            // Find import/require statements
            const importRegex = /(?:import|require)\s*\(?['"]([^'"]+)['"]\)?/g;
            const importFromRegex = /import\s+[\s\S]*?\s+from\s+['"]([^'"]+)['"]/g;
            const exportFromRegex = /export\s+[\s\S]*?\s+from\s+['"]([^'"]+)['"]/g;
            let match;
            const processImport = (importPath) => {
                if (importPath.startsWith(".")) {
                    // Resolve relative import to actual file
                    const dir = path.dirname(f);
                    const resolved = path.join(dir, importPath);
                    // Try common extensions
                    const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
                    for (const ext of extensions) {
                        const candidate = resolved + ext;
                        // Normalize path
                        const normalized = path.normalize(candidate);
                        importedFiles.add(normalized);
                    }
                }
            };
            while ((match = importRegex.exec(content)) !== null) {
                const m = match[1];
                if (m)
                    processImport(m);
            }
            while ((match = importFromRegex.exec(content)) !== null) {
                const m = match[1];
                if (m)
                    processImport(m);
            }
            while ((match = exportFromRegex.exec(content)) !== null) {
                const m = match[1];
                if (m)
                    processImport(m);
            }
        }
        // Find orphans
        const orphans = [];
        for (const f of allFiles) {
            // Skip entry points
            if (entryPatterns.some(p => p.test(f)))
                continue;
            // Skip if the file (without extension) is in the imported set
            const withoutExt = f.replace(/\.(ts|tsx|js|jsx)$/, "");
            const normalized = path.normalize(f);
            const normalizedWithout = path.normalize(withoutExt);
            const isImported = importedFiles.has(normalized) ||
                importedFiles.has(normalizedWithout) ||
                importedFiles.has(normalizedWithout + "/index");
            if (!isImported) {
                orphans.push(f);
            }
        }
        // Only report if we found a reasonable number of orphans (< 30% of files)
        // Otherwise, the detection is probably incomplete
        if (orphans.length > 0 && orphans.length < allFiles.length * 0.3) {
            for (const orphan of orphans.slice(0, 20)) { // cap at 20 to avoid noise
                results.push(diag(this, orphan, `File appears unused — not imported anywhere`, { autoFixable: true }));
            }
        }
        return results;
    },
    async fix(ctx, diag) {
        if (diag.autoFixable) {
            const fs = require("fs-extra");
            const path = require("path");
            const absPath = path.join(ctx.root, diag.filePath);
            await fs.unlink(absPath);
            return true;
        }
        return false;
    },
};
const unusedExports = {
    id: "dead/unused-exports",
    name: "Unused Exports",
    category: "dead",
    severity: "warning",
    description: "Exported functions/types that aren't imported anywhere",
    help: "Remove unused exports or make them private",
    appliesTo: ["node", "nextjs"],
    async check(ctx) {
        const results = [];
        // Skip if it's a library package (has main/module/exports/types in package.json)
        const pkg = await ctx.readJson("package.json");
        if (pkg && (pkg.main || pkg.module || pkg.exports || pkg.types || pkg.typings)) {
            return results;
        }
        // Find all named exports across the project
        const allFiles = await ctx.findFiles(/\.(ts|tsx|js|jsx)$/);
        if (allFiles.length > 300)
            return results; // too big, skip
        // Track server action files (their exports are invoked via form actions, not imports)
        const serverActionFiles = new Set();
        // Common server action function patterns (also include getters used as server functions)
        const serverActionPatterns = /^(signIn|signUp|signOut|login|logout|register|create|update|delete|remove|submit|send|handle|process|get|fetch|complete|fix)/;
        // Build a map of all exported names
        const exports = [];
        let allContent = "";
        for (const f of allFiles) {
            const content = await ctx.readFile(f);
            if (!content)
                continue;
            allContent += content + "\n";
            // Detect server action modules — skip all their exports
            // Check for "use server" anywhere in the file (top-level or after imports)
            if (content.includes('"use server"') || content.includes("'use server'")) {
                serverActionFiles.add(f);
                continue;
            }
            // Skip .mycontext/ generated files from unused export detection
            if (f.startsWith(".mycontext/") || f.includes("/.mycontext/"))
                continue;
            const lines = content.split("\n");
            for (let i = 0; i < lines.length; i++) {
                const line = lines[i];
                if (!line)
                    continue;
                // export function name
                const funcMatch = /export\s+(?:async\s+)?function\s+(\w+)/g.exec(line);
                if (funcMatch && funcMatch[1]) {
                    exports.push({ name: funcMatch[1], file: f, line: i + 1 });
                }
                // export const name
                const constMatch = /export\s+const\s+(\w+)/g.exec(line);
                if (constMatch && constMatch[1]) {
                    exports.push({ name: constMatch[1], file: f, line: i + 1 });
                }
                // export class name
                const classMatch = /export\s+class\s+(\w+)/g.exec(line);
                if (classMatch && classMatch[1]) {
                    exports.push({ name: classMatch[1], file: f, line: i + 1 });
                }
                // export interface/type name
                const typeMatch = /export\s+(?:interface|type)\s+(\w+)/g.exec(line);
                if (typeMatch && typeMatch[1]) {
                    exports.push({ name: typeMatch[1], file: f, line: i + 1 });
                }
            }
        }
        // Also check for action= and formAction= references in JSX
        const actionRefPattern = /(?:action|formAction)\s*=\s*\{?\s*(\w+)/g;
        const actionReferences = new Set();
        let actionMatch;
        while ((actionMatch = actionRefPattern.exec(allContent)) !== null) {
            if (actionMatch[1])
                actionReferences.add(actionMatch[1]);
        }
        // For each export, check if it's referenced elsewhere
        const unused = [];
        for (const exp of exports) {
            // Skip very common names that might be false positives
            if (["default", "metadata", "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"].includes(exp.name))
                continue;
            // Skip index files (barrel exports)
            if (/index\.(ts|tsx|js|jsx)$/.test(exp.file))
                continue;
            // Skip server action function patterns
            if (serverActionPatterns.test(exp.name))
                continue;
            // Skip if referenced in action= or formAction= attributes
            if (actionReferences.has(exp.name))
                continue;
            // Count references (subtract 1 for the export itself)
            const regex = new RegExp(`\\b${exp.name}\\b`, "g");
            const matches = allContent.match(regex);
            const count = matches ? matches.length : 0;
            // If only referenced once (the export declaration itself), it's unused
            if (count <= 1) {
                unused.push(exp);
            }
        }
        // Cap results
        for (const u of unused.slice(0, 15)) {
            results.push(diag(this, u.file, `Export "${u.name}" appears unused`, { line: u.line }));
        }
        return results;
    },
};
const unusedComponents = {
    id: "dead/unused-components",
    name: "Unused Components",
    category: "dead",
    severity: "warning",
    description: "React components defined but never imported/used",
    help: "Remove unused components or use them in your pages",
    appliesTo: ["nextjs"],
    async check(ctx) {
        const results = [];
        // Find component files (PascalCase .tsx files, not in pages/layouts/routes)
        const componentFiles = await ctx.findFiles(/\/[A-Z][a-zA-Z]+\.(tsx|jsx)$/);
        const allFiles = await ctx.findFiles(/\.(ts|tsx|js|jsx)$/);
        if (allFiles.length > 300)
            return results; // too big
        // Read all content for reference checking
        let allContent = "";
        for (const f of allFiles) {
            const content = await ctx.readFile(f);
            if (content)
                allContent += content + "\n";
        }
        for (const cf of componentFiles) {
            // Skip generated files and .mycontext/
            if (cf.startsWith(".mycontext/") || cf.includes("/.mycontext/"))
                continue;
            // Extract component name from file path
            const basename = path.basename(cf, path.extname(cf));
            // Skip if it's a page/layout/route
            if (["page", "layout", "route", "loading", "error", "not-found"].includes(basename.toLowerCase()))
                continue;
            // Check if component name is referenced in any other file
            const regex = new RegExp(`<${basename}[\\s/>]|import.*${basename}`, "g");
            const matches = allContent.match(regex);
            const references = matches ? matches.length : 0;
            // The component definition itself counts as 1 reference (import in its own file)
            if (references <= 1) {
                results.push(diag(this, cf, `Component "${basename}" appears unused`, { autoFixable: true }));
            }
        }
        return results;
    },
    async fix(ctx, diag) {
        if (diag.autoFixable) {
            const fs = require("fs-extra");
            const path = require("path");
            const absPath = path.join(ctx.root, diag.filePath);
            await fs.unlink(absPath);
            return true;
        }
        return false;
    },
};
exports.deadCodeRules = [
    orphanFiles,
    unusedExports,
    unusedComponents,
];
//# sourceMappingURL=dead-code-rules.js.map