lint 0.1.3

A versatile linting tool with CLI, MCP, and library interfaces
Documentation
use crate::language_rules::LanguageRule;
use crate::output::LintMessage;
use regex::Regex;
use std::path::Path;
use std::sync::LazyLock;

#[derive(Debug, Clone)]
pub struct ConsoleLogRule;

static CONSOLE_LOG_PATTERN: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"console\.(log|warn|error|info|debug)\(").unwrap());

impl LanguageRule for ConsoleLogRule {
    fn name(&self) -> &str {
        "no-console-log"
    }

    fn description(&self) -> &str {
        "Detects console.log and similar debugging statements in JS/TS code."
    }

    fn check(&self, content: &str, _file_path: &Path) -> Vec<LintMessage> {
        let mut messages = Vec::new();
        let pattern = &*CONSOLE_LOG_PATTERN;

        for (line_num, line) in content.lines().enumerate() {
            if pattern.is_match(line) {
                messages.push(LintMessage::new(
                    line_num + 1,
                    line.find("console").unwrap_or(0),
                    self.default_severity(),
                    "Console statement found".to_string(),
                    self.name().to_string(),
                    Some("Remove before release or use a logger: `import { logger } from 'logger'; logger.debug('message')`".to_string()),
                ));
            }
        }

        messages
    }

    fn supports_extension(&self, extension: &str) -> bool {
        matches!(extension, "js" | "ts" | "jsx" | "tsx")
    }
}

#[derive(Debug, Clone)]
pub struct VarUsageRule;

static NO_VAR_PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\bvar\s+\w+").unwrap());

impl LanguageRule for VarUsageRule {
    fn name(&self) -> &str {
        "no-var"
    }

    fn description(&self) -> &str {
        "Detects usage of `var` instead of `let` or `const` in JavaScript."
    }

    fn check(&self, content: &str, _file_path: &Path) -> Vec<LintMessage> {
        let mut messages = Vec::new();
        let pattern = &*NO_VAR_PATTERN;

        for (line_num, line) in content.lines().enumerate() {
            if pattern.is_match(line) {
                messages.push(LintMessage::new(
                    line_num + 1,
                    line.find("var").unwrap_or(0),
                    self.default_severity(),
                    "var usage detected".to_string(),
                    self.name().to_string(),
                    Some("Replace with let or const: 'var x = 1' → 'const x = 1' (or 'let x = 1' if reassigned)".to_string()),
                ));
            }
        }

        messages
    }

    fn supports_extension(&self, extension: &str) -> bool {
        matches!(extension, "js" | "ts" | "jsx" | "tsx")
    }
}