## Coverage Report

#### Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/ruchy/src/quality/linter.rs

    Line
    Count
    Source
    1
    // Code linter for Ruchy with comprehensive variable tracking
    2
    // Toyota Way: Catch issues early through static analysis
    3
    4
    use anyhow::Result;
    5
    use crate::frontend::ast::{Expr, ExprKind, Pattern};
    6
    use serde::{Serialize, Deserialize};
    7
    use std::collections::HashMap;
    8
    9
    #[derive(Debug, Clone, Serialize, Deserialize)]
    10
    pub struct LintIssue {
    11
        pub line: usize,
    12
        pub column: usize,
    13
        pub severity: String,
    14
        pub rule: String,
    15
        pub message: String,
    16
        pub suggestion: String,
    17
        #[serde(rename = "type")]
    18
        pub issue_type: String,
    19
        pub name: String,
    20
    }
    21
    22
    #[derive(Debug, Clone)]
    23
    pub enum LintRule {
    24
        UnusedVariable,
    25
        UndefinedVariable,
    26
        VariableShadowing,
    27
        UnusedParameter,
    28
        UnusedLoopVariable,
    29
        UnusedMatchBinding,
    30
        ComplexityLimit,
    31
        NamingConvention,
    32
        StyleViolation,
    33
        Security,
    34
        Performance,
    35
    }
    36
    37
    #[derive(Debug, Clone)]
    38
    struct Scope {
    39
        variables: HashMap<String, VariableInfo>,
    40
        parent: Option<Box<Scope>>,
    41
    }
    42
    43
    #[derive(Debug, Clone)]
    44
    struct VariableInfo {
    45
        defined_at: (usize, usize),
    46
        used: bool,
    47
        var_type: VarType,
    48
    }
    49
    50
    #[derive(Debug, Clone)]
    51
    enum VarType {
    52
        Local,
    53
        Parameter,
    54
        LoopVariable,
    55
        MatchBinding,
    56
    }
    57
    58
    impl Scope {
    59
    0
        fn new() -> Self {
    60
    0
            Self {
    61
    0
                variables: HashMap::new(),
    62
    0
                parent: None,
    63
    0
            }
    64
    0
        }
    65
    66
    0
        fn with_parent(parent: Scope) -> Self {
    67
    0
            Self {
    68
    0
                variables: HashMap::new(),
    69
    0
                parent: Some(Box::new(parent)),
    70
    0
            }
    71
    0
        }
    72
    73
    0
        fn define(&mut self, name: String, line: usize, column: usize, var_type: VarType) {
    74
    0
            self.variables.insert(name, VariableInfo {
    75
    0
                defined_at: (line, column),
    76
    0
                used: false,
    77
    0
                var_type,
    78
    0
            });
    79
    0
        }
    80
    81
    0
        fn mark_used(&mut self, name: &str) -> bool {
    82
    0
            if let Some(info) = self.variables.get_mut(name) {
    83
    0
                info.used = true;
    84
    0
                true
    85
    0
            } else if let Some(parent) = &mut self.parent {
    86
    0
                parent.mark_used(name)
    87
            } else {
    88
    0
                false
    89
            }
    90
    0
        }
    91
    92
    0
        fn is_defined(&self, name: &str) -> bool {
    93
    0
            self.variables.contains_key(name) ||
    94
    0
            self.parent.as_ref().is_some_and(|p| p.is_defined(name))
    95
    0
        }
    96
    97
    0
        fn is_shadowing(&self, name: &str) -> bool {
    98
    0
            self.parent.as_ref().is_some_and(|p| p.is_defined(name))
    99
    0
        }
    100
    }
    101
    102
    pub struct Linter {
    103
        rules: Vec<LintRule>,
    104
        strict_mode: bool,
    105
        max_complexity: usize,
    106
    }
    107
    108
    impl Linter {
    109
    0
        pub fn new() -> Self {
    110
    0
            Self {
    111
    0
                rules: vec![
    112
    0
                    LintRule::UnusedVariable,
    113
    0
                    LintRule::UndefinedVariable,
    114
    0
                    LintRule::VariableShadowing,
    115
    0
                    LintRule::UnusedParameter,
    116
    0
                    LintRule::UnusedLoopVariable,
    117
    0
                    LintRule::UnusedMatchBinding,
    118
    0
                    LintRule::ComplexityLimit,
    119
    0
                    LintRule::NamingConvention,
    120
    0
                ],
    121
    0
                strict_mode: false,
    122
    0
                max_complexity: 10,
    123
    0
            }
    124
    0
        }
    125
    126
    0
        pub fn set_rules(&mut self, rule_filter: &str) {
    127
    0
            self.rules.clear();
    128
    0
            for rule in rule_filter.split(',') {
    129
    0
                match rule.trim() {
    130
    0
                    "unused" => {
    131
    0
                        self.rules.push(LintRule::UnusedVariable);
    132
    0
                        self.rules.push(LintRule::UnusedParameter);
    133
    0
                        self.rules.push(LintRule::UnusedLoopVariable);
    134
    0
                        self.rules.push(LintRule::UnusedMatchBinding);
    135
    0
                    }
    136
    0
                    "undefined" => self.rules.push(LintRule::UndefinedVariable),
    137
    0
                    "shadowing" => self.rules.push(LintRule::VariableShadowing),
    138
    0
                    "complexity" => self.rules.push(LintRule::ComplexityLimit),
    139
    0
                    "style" => self.rules.push(LintRule::StyleViolation),
    140
    0
                    "security" => self.rules.push(LintRule::Security),
    141
    0
                    "performance" => self.rules.push(LintRule::Performance),
    142
    0
                    _ => {}
    143
                }
    144
            }
    145
    0
        }
    146
    147
    0
        pub fn set_strict_mode(&mut self, strict: bool) {
    148
    0
            self.strict_mode = strict;
    149
    0
        }
    150
    151
    0
        pub fn lint(&self, ast: &Expr, _source: &str) -> Result<Vec<LintIssue>> {
    152
    0
            let mut issues = Vec::new();
    153
    0
            let mut scope = Scope::new();
    154
    155
            // Analyze the AST with variable tracking
    156
    0
            self.analyze_expr(ast, &mut scope, &mut issues);
    157
    158
            // Check for unused variables
    159
    0
            self.check_unused_in_scope(&scope, &mut issues);
    160
    161
            // Check complexity
    162
    0
            if self.rules.iter().any(|r| matches!(r, LintRule::ComplexityLimit))
    163
    0
                && self.calculate_complexity(ast) > self.max_complexity {
    164
    0
                    issues.push(LintIssue {
    165
                        line: 1,
    166
                        column: 1,
    167
    0
                        severity: if self.strict_mode { "error" } else { "warning" }.to_string(),
    168
    0
                        rule: "complexity".to_string(),
    169
    0
                        message: format!("Function complexity exceeds limit of {}", self.max_complexity),
    170
    0
                        suggestion: "Consider breaking this into smaller functions".to_string(),
    171
    0
                        issue_type: "complexity".to_string(),
    172
    0
                        name: String::new(),
    173
                    });
    174
    0
                }
    175
    176
            // Return empty if clean
    177
    0
            if issues.is_empty() {
    178
                // For JSON format compatibility
    179
    0
                return Ok(vec![]);
    180
    0
            }
    181
    182
    0
            Ok(issues)
    183
    0
        }
    184
    185
    0
        fn analyze_expr(&self, expr: &Expr, scope: &mut Scope, issues: &mut Vec<LintIssue>) {
    186
    0
            match &expr.kind {
    187
    0
                ExprKind::Let { name, value, body, .. } => {
    188
                    // Analyze the value first (with current scope)
    189
    0
                    self.analyze_expr(value, scope, issues);
    190
    191
                    // Create new scope for the let binding body
    192
    0
                    let mut let_scope = Scope::with_parent(scope.clone());
    193
    194
                    // Check for shadowing before defining
    195
    0
                    if self.rules.iter().any(|r| matches!(r, LintRule::VariableShadowing))
    196
    0
                        && let_scope.is_shadowing(name) {
    197
    0
                            issues.push(LintIssue {
    198
    0
                                line: 3, // Simplified line tracking
    199
    0
                                column: 1,
    200
    0
                                severity: "warning".to_string(),
    201
    0
                                rule: "shadowing".to_string(),
    202
    0
                                message: format!("variable shadowing: {name}"),
    203
    0
                                suggestion: format!("Consider renaming variable '{name}'"),
    204
    0
                                issue_type: "variable_shadowing".to_string(),
    205
    0
                                name: name.clone(),
    206
    0
                            });
    207
    0
                        }
    208
    209
                    // Define the variable in the new scope
    210
    0
                    let_scope.define(name.clone(), 2, 1, VarType::Local);
    211
    212
                    // Analyze the body with the new scope
    213
    0
                    self.analyze_expr(body, &mut let_scope, issues);
    214
    215
                    // Check for unused variables in the let scope
    216
    0
                    self.check_unused_in_scope(&let_scope, issues);
    217
                }
    218
    219
    0
                ExprKind::Identifier(name) => {
    220
                    // Special case: println is a built-in, not an undefined variable
    221
    0
                    if name == "println" || name == "print" || name == "eprintln" {
    222
    0
                        return;
    223
    0
                    }
    224
    225
                    // Mark as used if defined, otherwise report as undefined
    226
    0
                    if !scope.mark_used(name)
    227
    0
                        && self.rules.iter().any(|r| matches!(r, LintRule::UndefinedVariable)) {
    228
    0
                            issues.push(LintIssue {
    229
    0
                                line: 3,
    230
    0
                                column: 1,
    231
    0
                                severity: "error".to_string(),
    232
    0
                                rule: "undefined".to_string(),
    233
    0
                                message: format!("undefined variable: {name}"),
    234
    0
                                suggestion: format!("Define '{name}' before using it"),
    235
    0
                                issue_type: "undefined_variable".to_string(),
    236
    0
                                name: name.clone(),
    237
    0
                            });
    238
    0
                        }
    239
                }
    240
    241
    0
                ExprKind::Function { name, params, body, .. } => {
    242
                    // Define the function name in the current scope
    243
    0
                    scope.define(name.clone(), 1, 1, VarType::Local);
    244
    245
                    // Create new scope for function body
    246
    0
                    let mut func_scope = Scope::with_parent(scope.clone());
    247
    248
                    // Add parameters to scope with correct type
    249
    0
                    for param in params {
    250
    0
                        self.extract_param_bindings(&param.pattern, &mut func_scope);
    251
    0
                    }
    252
    253
                    // Analyze function body
    254
    0
                    self.analyze_expr(body, &mut func_scope, issues);
    255
    256
                    // Check for unused variables in function body (but not parameters for now)
    257
                    // Parameters might be part of public API
    258
    0
                    for (name, info) in &func_scope.variables {
    259
    0
                        if !info.used && matches!(info.var_type, VarType::Local) {
    260
    0
                            issues.push(LintIssue {
    261
    0
                                line: info.defined_at.0,
    262
    0
                                column: info.defined_at.1,
    263
    0
                                severity: "warning".to_string(),
    264
    0
                                rule: "unused_variable".to_string(),
    265
    0
                                message: format!("unused variable: {name}"),
    266
    0
                                suggestion: format!("Remove unused variable '{name}'"),
    267
    0
                                issue_type: "unused_variable".to_string(),
    268
    0
                                name: name.clone(),
    269
    0
                            });
    270
    0
                        }
    271
                    }
    272
                }
    273
    274
    0
                ExprKind::For { var, pattern, iter, body, .. } => {
    275
                    // Create new scope for loop
    276
    0
                    let mut loop_scope = Scope::with_parent(scope.clone());
    277
    278
                    // Add loop variable to scope
    279
    0
                    if let Some(pat) = pattern {
    280
    0
                        self.extract_loop_bindings(pat, &mut loop_scope);
    281
    0
                    } else {
    282
    0
                        // Fall back to var field for backward compatibility
    283
    0
                        loop_scope.define(var.clone(), 2, 1, VarType::LoopVariable);
    284
    0
                    }
    285
    286
                    // Analyze iterator
    287
    0
                    self.analyze_expr(iter, scope, issues);
    288
    289
                    // Analyze loop body
    290
    0
                    self.analyze_expr(body, &mut loop_scope, issues);
    291
    292
                    // Check for unused loop variables
    293
    0
                    self.check_unused_in_scope(&loop_scope, issues);
    294
                }
    295
    296
    0
                ExprKind::Match { expr, arms, .. } => {
    297
                    // Analyze scrutinee
    298
    0
                    self.analyze_expr(expr, scope, issues);
    299
    300
                    // Analyze each branch
    301
    0
                    for arm in arms {
    302
    0
                        let mut branch_scope = Scope::with_parent(scope.clone());
    303
    304
                        // Add pattern bindings to scope
    305
    0
                        self.extract_pattern_bindings(&arm.pattern, &mut branch_scope);
    306
    307
                        // Analyze guard if present
    308
    0
                        if let Some(guard) = &arm.guard {
    309
    0
                            self.analyze_expr(guard, &mut branch_scope, issues);
    310
    0
                        }
    311
    312
                        // Analyze branch expression
    313
    0
                        self.analyze_expr(&arm.body, &mut branch_scope, issues);
    314
    315
                        // Check for unused match bindings
    316
    0
                        self.check_unused_in_scope(&branch_scope, issues);
    317
                    }
    318
                }
    319
    320
    0
                ExprKind::If { condition, then_branch, else_branch, .. } => {
    321
    0
                    self.analyze_expr(condition, scope, issues);
    322
    323
                    // Create new scope for then branch
    324
    0
                    let mut then_scope = Scope::with_parent(scope.clone());
    325
    0
                    self.analyze_expr(then_branch, &mut then_scope, issues);
    326
    327
                    // Create new scope for else branch if exists
    328
    0
                    if let Some(else_expr) = else_branch {
    329
    0
                        let mut else_scope = Scope::with_parent(scope.clone());
    330
    0
                        self.analyze_expr(else_expr, &mut else_scope, issues);
    331
    0
                    }
    332
                }
    333
    334
    0
                ExprKind::Block(exprs) => {
    335
                    // For blocks, we use the same scope level - each statement can see previous ones
    336
    0
                    for expr in exprs {
    337
    0
                        self.analyze_expr(expr, scope, issues);
    338
    0
                    }
    339
                }
    340
    341
    0
                ExprKind::Binary { left, right, .. } => {
    342
    0
                    self.analyze_expr(left, scope, issues);
    343
    0
                    self.analyze_expr(right, scope, issues);
    344
    0
                }
    345
    346
    0
                ExprKind::Call { func, args, .. } => {
    347
    0
                    self.analyze_expr(func, scope, issues);
    348
    0
                    for arg in args {
    349
    0
                        self.analyze_expr(arg, scope, issues);
    350
    0
                    }
    351
                }
    352
    353
    0
                ExprKind::MethodCall { receiver, args, .. } => {
    354
    0
                    self.analyze_expr(receiver, scope, issues);
    355
    0
                    for arg in args {
    356
    0
                        self.analyze_expr(arg, scope, issues);
    357
    0
                    }
    358
                }
    359
    360
    0
                ExprKind::StringInterpolation { parts } => {
    361
                    // Analyze expressions within f-string interpolations
    362
    0
                    for part in parts {
    363
    0
                        match part {
    364
    0
                            crate::frontend::ast::StringPart::Expr(expr) => {
    365
    0
                                self.analyze_expr(expr, scope, issues);
    366
    0
                            }
    367
    0
                            crate::frontend::ast::StringPart::ExprWithFormat { expr, .. } => {
    368
    0
                                self.analyze_expr(expr, scope, issues);
    369
    0
                            }
    370
    0
                            crate::frontend::ast::StringPart::Text(_) => {
    371
    0
                                // Literal text, nothing to analyze
    372
    0
                            }
    373
                        }
    374
                    }
    375
                }
    376
    377
    0
                ExprKind::Lambda { params, body, .. } => {
    378
                    // Create new scope for lambda body
    379
    0
                    let mut lambda_scope = Scope::with_parent(scope.clone());
    380
    381
                    // Add parameters to scope
    382
    0
                    for param in params {
    383
    0
                        self.extract_param_bindings(&param.pattern, &mut lambda_scope);
    384
    0
                    }
    385
    386
                    // Analyze lambda body
    387
    0
                    self.analyze_expr(body, &mut lambda_scope, issues);
    388
    389
                    // Check for unused parameters
    390
    0
                    self.check_unused_in_scope(&lambda_scope, issues);
    391
                }
    392
    393
    0
                ExprKind::Return { value } => {
    394
    0
                    if let Some(expr) = value {
    395
    0
                        self.analyze_expr(expr, scope, issues);
    396
    0
                    }
    397
                }
    398
    399
    0
                ExprKind::List(exprs) | ExprKind::Tuple(exprs) => {
    400
    0
                    for expr in exprs {
    401
    0
                        self.analyze_expr(expr, scope, issues);
    402
    0
                    }
    403
                }
    404
    405
    0
                ExprKind::FieldAccess { object, .. } => {
    406
    0
                    self.analyze_expr(object, scope, issues);
    407
    0
                }
    408
    409
    0
                ExprKind::IndexAccess { object, index } => {
    410
    0
                    self.analyze_expr(object, scope, issues);
    411
    0
                    self.analyze_expr(index, scope, issues);
    412
    0
                }
    413
    414
    0
                ExprKind::While { condition, body, .. } => {
    415
    0
                    self.analyze_expr(condition, scope, issues);
    416
    0
                    self.analyze_expr(body, scope, issues);
    417
    0
                }
    418
    419
    0
                ExprKind::Assign { target, value, .. } => {
    420
    0
                    self.analyze_expr(target, scope, issues);
    421
    0
                    self.analyze_expr(value, scope, issues);
    422
    0
                }
    423
    424
    0
                _ => {
    425
    0
                    // Handle other expression types as needed
    426
    0
                }
    427
            }
    428
    0
        }
    429
    430
    0
        fn extract_loop_bindings(&self, pattern: &Pattern, scope: &mut Scope) {
    431
    0
            match pattern {
    432
    0
                Pattern::Identifier(name) => {
    433
                    // Check if it's a special identifier like _
    434
    0
                    if name != "_" {
    435
    0
                        scope.define(name.clone(), 2, 1, VarType::LoopVariable);
    436
    0
                    }
    437
                }
    438
    0
                Pattern::Tuple(patterns) => {
    439
    0
                    for p in patterns {
    440
    0
                        self.extract_loop_bindings(p, scope);
    441
    0
                    }
    442
                }
    443
    0
                Pattern::Struct { fields, .. } => {
    444
    0
                    for field in fields {
    445
    0
                        if let Some(pattern) = &field.pattern {
    446
    0
                            self.extract_loop_bindings(pattern, scope);
    447
    0
                        } else {
    448
    0
                            // Shorthand: { x } means { x: x }, bind the name
    449
    0
                            scope.define(field.name.clone(), 2, 1, VarType::LoopVariable);
    450
    0
                        }
    451
                    }
    452
                }
    453
    0
                Pattern::List(patterns) => {
    454
    0
                    for p in patterns {
    455
    0
                        self.extract_loop_bindings(p, scope);
    456
    0
                    }
    457
                }
    458
    0
                _ => {}
    459
            }
    460
    0
        }
    461
    462
    0
        fn extract_param_bindings(&self, pattern: &Pattern, scope: &mut Scope) {
    463
    0
            match pattern {
    464
    0
                Pattern::Identifier(name) => {
    465
                    // Check if it's a special identifier like _
    466
    0
                    if name != "_" {
    467
    0
                        scope.define(name.clone(), 1, 1, VarType::Parameter);
    468
    0
                    }
    469
                }
    470
    0
                Pattern::Tuple(patterns) => {
    471
    0
                    for p in patterns {
    472
    0
                        self.extract_param_bindings(p, scope);
    473
    0
                    }
    474
                }
    475
    0
                Pattern::Struct { fields, .. } => {
    476
    0
                    for field in fields {
    477
    0
                        if let Some(pattern) = &field.pattern {
    478
    0
                            self.extract_param_bindings(pattern, scope);
    479
    0
                        } else {
    480
    0
                            // Shorthand: { x } means { x: x }, bind the name
    481
    0
                            scope.define(field.name.clone(), 1, 1, VarType::Parameter);
    482
    0
                        }
    483
                    }
    484
                }
    485
    0
                Pattern::List(patterns) => {
    486
    0
                    for p in patterns {
    487
    0
                        self.extract_param_bindings(p, scope);
    488
    0
                    }
    489
                }
    490
    0
                _ => {}
    491
            }
    492
    0
        }
    493
    494
    0
        fn extract_pattern_bindings(&self, pattern: &Pattern, scope: &mut Scope) {
    495
    0
            match pattern {
    496
    0
                Pattern::Identifier(name) => {
    497
                    // Check if it's a special identifier like _
    498
    0
                    if name != "_" {
    499
    0
                        scope.define(name.clone(), 3, 1, VarType::MatchBinding);
    500
    0
                    }
    501
                }
    502
    0
                Pattern::Tuple(patterns) => {
    503
    0
                    for p in patterns {
    504
    0
                        self.extract_pattern_bindings(p, scope);
    505
    0
                    }
    506
                }
    507
    0
                Pattern::Struct { fields, .. } => {
    508
    0
                    for field in fields {
    509
    0
                        if let Some(pattern) = &field.pattern {
    510
    0
                            self.extract_pattern_bindings(pattern, scope);
    511
    0
                        } else {
    512
    0
                            // Shorthand: { x } means { x: x }, bind the name
    513
    0
                            scope.define(field.name.clone(), 3, 1, VarType::MatchBinding);
    514
    0
                        }
    515
                    }
    516
                }
    517
    0
                Pattern::List(patterns) => {
    518
    0
                    for p in patterns {
    519
    0
                        self.extract_pattern_bindings(p, scope);
    520
    0
                    }
    521
                }
    522
    0
                Pattern::Some(inner) | Pattern::Ok(inner) | Pattern::Err(inner) => {
    523
    0
                    self.extract_pattern_bindings(inner, scope);
    524
    0
                }
    525
    0
                _ => {}
    526
            }
    527
    0
        }
    528
    529
    0
        fn check_unused_in_scope(&self, scope: &Scope, issues: &mut Vec<LintIssue>) {
    530
    0
            for (name, info) in &scope.variables {
    531
    0
                if !info.used {
    532
    0
                    let (rule_type, message) = match info.var_type {
    533
                        VarType::Local => {
    534
    0
                            if self.rules.iter().any(|r| matches!(r, LintRule::UnusedVariable)) {
    535
    0
                                ("unused_variable", format!("unused variable: {name}"))
    536
                            } else {
    537
    0
                                continue;
    538
                            }
    539
                        }
    540
                        VarType::Parameter => {
    541
    0
                            if self.rules.iter().any(|r| matches!(r, LintRule::UnusedParameter)) {
    542
    0
                                ("unused_parameter", format!("unused parameter: {name}"))
    543
                            } else {
    544
    0
                                continue;
    545
                            }
    546
                        }
    547
                        VarType::LoopVariable => {
    548
    0
                            if self.rules.iter().any(|r| matches!(r, LintRule::UnusedLoopVariable)) {
    549
    0
                                ("unused_loop_variable", format!("unused loop variable: {name}"))
    550
                            } else {
    551
    0
                                continue;
    552
                            }
    553
                        }
    554
                        VarType::MatchBinding => {
    555
    0
                            if self.rules.iter().any(|r| matches!(r, LintRule::UnusedMatchBinding)) {
    556
    0
                                ("unused_match_binding", format!("unused match binding: {name}"))
    557
                            } else {
    558
    0
                                continue;
    559
                            }
    560
                        }
    561
                    };
    562
    563
    0
                    issues.push(LintIssue {
    564
    0
                        line: info.defined_at.0,
    565
    0
                        column: info.defined_at.1,
    566
    0
                        severity: "warning".to_string(),
    567
    0
                        rule: rule_type.to_string(),
    568
    0
                        message: message.clone(),
    569
    0
                        suggestion: format!("Remove unused {}",
    570
    0
                            match info.var_type {
    571
    0
                                VarType::Local => "variable",
    572
    0
                                VarType::Parameter => "parameter",
    573
    0
                                VarType::LoopVariable => "loop variable",
    574
    0
                                VarType::MatchBinding => "match binding",
    575
                            }
    576
                        ),
    577
    0
                        issue_type: rule_type.to_string(),
    578
    0
                        name: name.clone(),
    579
                    });
    580
    0
                }
    581
            }
    582
    0
        }
    583
    584
    0
        pub fn auto_fix(&self, source: &str, issues: &[LintIssue]) -> Result<String> {
    585
            // Simple auto-fix implementation
    586
    0
            let mut fixed = source.to_string();
    587
    588
    0
            for issue in issues {
    589
    0
                if issue.rule == "style" {
    590
    0
                    // Fix style issues
    591
    0
                    fixed = fixed.replace("  ", " ");
    592
    0
                }
    593
            }
    594
    595
    0
            Ok(fixed)
    596
    0
        }
    597
    598
    0
        fn calculate_complexity(&self, expr: &Expr) -> usize {
    599
    0
            match &expr.kind {
    600
    0
                ExprKind::If { condition: _, then_branch, else_branch, .. } => {
    601
    0
                    1 + self.calculate_complexity(then_branch)
    602
    0
                      + else_branch.as_ref().map_or(0, |e| self.calculate_complexity(e))
    603
                }
    604
    0
                ExprKind::Match { .. } => 2,
    605
    0
                ExprKind::While { .. } | ExprKind::For { .. } => 2,
    606
    0
                ExprKind::Block(exprs) => {
    607
    0
                    exprs.iter().map(|e| self.calculate_complexity(e)).sum()
    608
                }
    609
    0
                _ => 0,
    610
            }
    611
    0
        }
    612
    }
    613
    614
    impl Default for Linter {
    615
    0
        fn default() -> Self {
    616
    0
            Self::new()
    617
    0
        }
    618
    }
    619
    620
    #[cfg(test)]
    621
    mod tests {
    622
        use super::*;
    623
        use crate::frontend::ast::{
    624
            Expr, ExprKind, Pattern, Literal, BinaryOp, Span, Param, Type, TypeKind,
    625
            MatchArm, StringPart, StructPatternField
    626
        };
    627
    628
        // Helper functions for consistent test setup
    629
        fn create_test_span() -> Span {
    630
            Span { start: 0, end: 1 }
    631
        }
    632
    633
        fn create_test_linter() -> Linter {
    634
            Linter::new()
    635
        }
    636
    637
        fn create_test_linter_with_rules(rules: &str) -> Linter {
    638
            let mut linter = Linter::new();
    639
            linter.set_rules(rules);
    640
            linter
    641
        }
    642
    643
        fn create_test_expr_literal_int(value: i64) -> Expr {
    644
            Expr::new(ExprKind::Literal(Literal::Integer(value)), create_test_span())
    645
        }
    646
    647
        fn create_test_expr_identifier(name: &str) -> Expr {
    648
            Expr::new(ExprKind::Identifier(name.to_string()), create_test_span())
    649
        }
    650
    651
        fn create_test_expr_let(name: &str, value: Expr, body: Expr) -> Expr {
    652
            Expr::new(ExprKind::Let {
    653
                name: name.to_string(),
    654
                type_annotation: None,
    655
                value: Box::new(value),
    656
                body: Box::new(body),
    657
                is_mutable: false,
    658
            }, create_test_span())
    659
        }
    660
    661
        fn create_test_expr_function(name: &str, params: Vec<Param>, body: Expr) -> Expr {
    662
            Expr::new(ExprKind::Function {
    663
                name: name.to_string(),
    664
                type_params: vec![],
    665
                params,
    666
                return_type: None,
    667
                body: Box::new(body),
    668
                is_async: false,
    669
                is_pub: false,
    670
            }, create_test_span())
    671
        }
    672
    673
        fn create_test_param(name: &str) -> Param {
    674
            Param {
    675
                pattern: Pattern::Identifier(name.to_string()),
    676
                ty: Type {
    677
                    kind: TypeKind::Named("Any".to_string()),
    678
                    span: create_test_span(),
    679
                },
    680
                span: create_test_span(),
    681
                is_mutable: false,
    682
                default_value: None,
    683
            }
    684
        }
    685
    686
        fn create_test_expr_block(exprs: Vec<Expr>) -> Expr {
    687
            Expr::new(ExprKind::Block(exprs), create_test_span())
    688
        }
    689
    690
        fn create_test_expr_binary(op: BinaryOp, left: Expr, right: Expr) -> Expr {
    691
            Expr::new(ExprKind::Binary {
    692
                op,
    693
                left: Box::new(left),
    694
                right: Box::new(right),
    695
            }, create_test_span())
    696
        }
    697
    698
        fn create_test_expr_call(func: Expr, args: Vec<Expr>) -> Expr {
    699
            Expr::new(ExprKind::Call {
    700
                func: Box::new(func),
    701
                args,
    702
            }, create_test_span())
    703
        }
    704
    705
        fn create_test_expr_if(condition: Expr, then_branch: Expr, else_branch: Option<Expr>) -> Expr {
    706
            Expr::new(ExprKind::If {
    707
                condition: Box::new(condition),
    708
                then_branch: Box::new(then_branch),
    709
                else_branch: else_branch.map(Box::new),
    710
            }, create_test_span())
    711
        }
    712
    713
        fn create_test_expr_for(var: &str, pattern: Option<Pattern>, iter: Expr, body: Expr) -> Expr {
    714
            Expr::new(ExprKind::For {
    715
                var: var.to_string(),
    716
                pattern,
    717
                iter: Box::new(iter),
    718
                body: Box::new(body),
    719
            }, create_test_span())
    720
        }
    721
    722
        fn create_test_expr_match(expr: Expr, arms: Vec<MatchArm>) -> Expr {
    723
            Expr::new(ExprKind::Match {
    724
                expr: Box::new(expr),
    725
                arms,
    726
            }, create_test_span())
    727
        }
    728
    729
        fn create_test_match_arm(pattern: Pattern, body: Expr) -> MatchArm {
    730
            MatchArm {
    731
                pattern,
    732
                guard: None,
    733
                body: Box::new(body),
    734
                span: create_test_span(),
    735
            }
    736
        }
    737
    738
        fn create_test_expr_lambda(params: Vec<Param>, body: Expr) -> Expr {
    739
            Expr::new(ExprKind::Lambda {
    740
                params,
    741
                body: Box::new(body),
    742
            }, create_test_span())
    743
        }
    744
    745
        fn create_test_expr_method_call(receiver: Expr, method: &str, args: Vec<Expr>) -> Expr {
    746
            Expr::new(ExprKind::MethodCall {
    747
                receiver: Box::new(receiver),
    748
                method: method.to_string(),
    749
                args,
    750
            }, create_test_span())
    751
        }
    752
    753
        fn create_test_expr_while(condition: Expr, body: Expr) -> Expr {
    754
            Expr::new(ExprKind::While {
    755
                condition: Box::new(condition),
    756
                body: Box::new(body),
    757
            }, create_test_span())
    758
        }
    759
    760
        fn create_test_expr_return(value: Option<Expr>) -> Expr {
    761
            Expr::new(ExprKind::Return {
    762
                value: value.map(Box::new),
    763
            }, create_test_span())
    764
        }
    765
    766
        // ========== Linter Construction Tests ==========
    767
    768
        #[test]
    769
        fn test_linter_creation() {
    770
            let linter = Linter::new();
    771
            assert_eq!(linter.rules.len(), 8); // Default rules count
    772
            assert!(!linter.strict_mode);
    773
            assert_eq!(linter.max_complexity, 10);
    774
        }
    775
    776
        #[test]
    777
        fn test_linter_default() {
    778
            let linter = Linter::default();
    779
            assert_eq!(linter.rules.len(), 8);
    780
            assert!(!linter.strict_mode);
    781
            assert_eq!(linter.max_complexity, 10);
    782
        }
    783
    784
        #[test]
    785
        fn test_linter_set_strict_mode() {
    786
            let mut linter = Linter::new();
    787
            linter.set_strict_mode(true);
    788
            assert!(linter.strict_mode);
    789
        }
    790
    791
        // ========== Rule Configuration Tests ==========
    792
    793
        #[test]
    794
        fn test_set_rules_unused() {
    795
            let mut linter = Linter::new();
    796
            linter.set_rules("unused");
    797
            assert_eq!(linter.rules.len(), 4); // UnusedVariable, Parameter, LoopVariable, MatchBinding
    798
        }
    799
    800
        #[test]
    801
        fn test_set_rules_undefined() {
    802
            let mut linter = Linter::new();
    803
            linter.set_rules("undefined");
    804
            assert_eq!(linter.rules.len(), 1);
    805
            assert!(matches!(linter.rules[0], LintRule::UndefinedVariable));
    806
        }
    807
    808
        #[test]
    809
        fn test_set_rules_shadowing() {
    810
            let mut linter = Linter::new();
    811
            linter.set_rules("shadowing");
    812
            assert_eq!(linter.rules.len(), 1);
    813
            assert!(matches!(linter.rules[0], LintRule::VariableShadowing));
    814
        }
    815
    816
        #[test]
    817
        fn test_set_rules_complexity() {
    818
            let mut linter = Linter::new();
    819
            linter.set_rules("complexity");
    820
            assert_eq!(linter.rules.len(), 1);
    821
            assert!(matches!(linter.rules[0], LintRule::ComplexityLimit));
    822
        }
    823
    824
        #[test]
    825
        fn test_set_rules_multiple() {
    826
            let mut linter = Linter::new();
    827
            linter.set_rules("undefined,shadowing,complexity");
    828
            assert_eq!(linter.rules.len(), 3);
    829
        }
    830
    831
        #[test]
    832
        fn test_set_rules_unknown() {
    833
            let mut linter = Linter::new();
    834
            linter.set_rules("unknown_rule");
    835
            assert_eq!(linter.rules.len(), 0);
    836
        }
    837
    838
        #[test]
    839
        fn test_set_rules_style_security_performance() {
    840
            let mut linter = Linter::new();
    841
            linter.set_rules("style,security,performance");
    842
            assert_eq!(linter.rules.len(), 3);
    843
            assert!(linter.rules.iter().any(|r| matches!(r, LintRule::StyleViolation)));
    844
            assert!(linter.rules.iter().any(|r| matches!(r, LintRule::Security)));
    845
            assert!(linter.rules.iter().any(|r| matches!(r, LintRule::Performance)));
    846
        }
    847
    848
        // ========== Scope Tests ==========
    849
    850
        #[test]
    851
        fn test_scope_creation() {
    852
            let scope = Scope::new();
    853
            assert!(scope.variables.is_empty());
    854
            assert!(scope.parent.is_none());
    855
        }
    856
    857
        #[test]
    858
        fn test_scope_with_parent() {
    859
            let parent_scope = Scope::new();
    860
            let child_scope = Scope::with_parent(parent_scope);
    861
            assert!(child_scope.parent.is_some());
    862
        }
    863
    864
        #[test]
    865
        fn test_scope_define_variable() {
    866
            let mut scope = Scope::new();
    867
            scope.define("x".to_string(), 1, 1, VarType::Local);
    868
            assert!(scope.variables.contains_key("x"));
    869
            assert!(!scope.variables["x"].used);
    870
        }
    871
    872
        #[test]
    873
        fn test_scope_mark_used() {
    874
            let mut scope = Scope::new();
    875
            scope.define("x".to_string(), 1, 1, VarType::Local);
    876
            assert!(scope.mark_used("x"));
    877
            assert!(scope.variables["x"].used);
    878
        }
    879
    880
        #[test]
    881
        fn test_scope_mark_used_undefined() {
    882
            let mut scope = Scope::new();
    883
            assert!(!scope.mark_used("undefined_var"));
    884
        }
    885
    886
        #[test]
    887
        fn test_scope_mark_used_in_parent() {
    888
            let mut parent_scope = Scope::new();
    889
            parent_scope.define("x".to_string(), 1, 1, VarType::Local);
    890
    891
            let mut child_scope = Scope::with_parent(parent_scope);
    892
            assert!(child_scope.mark_used("x"));
    893
        }
    894
    895
        #[test]
    896
        fn test_scope_is_defined() {
    897
            let mut scope = Scope::new();
    898
            scope.define("x".to_string(), 1, 1, VarType::Local);
    899
            assert!(scope.is_defined("x"));
    900
            assert!(!scope.is_defined("y"));
    901
        }
    902
    903
        #[test]
    904
        fn test_scope_is_defined_in_parent() {
    905
            let mut parent_scope = Scope::new();
    906
            parent_scope.define("x".to_string(), 1, 1, VarType::Local);
    907
    908
            let child_scope = Scope::with_parent(parent_scope);
    909
            assert!(child_scope.is_defined("x"));
    910
        }
    911
    912
        #[test]
    913
        fn test_scope_is_shadowing() {
    914
            let mut parent_scope = Scope::new();
    915
            parent_scope.define("x".to_string(), 1, 1, VarType::Local);
    916
    917
            let child_scope = Scope::with_parent(parent_scope);
    918
            assert!(child_scope.is_shadowing("x"));
    919
            assert!(!child_scope.is_shadowing("y"));
    920
        }
    921
    922
        // ========== Lint Issue Tests ==========
    923
    924
        #[test]
    925
        fn test_lint_issue_serialization() {
    926
            let issue = LintIssue {
    927
                line: 5,
    928
                column: 10,
    929
                severity: "warning".to_string(),
    930
                rule: "unused_variable".to_string(),
    931
                message: "unused variable: x".to_string(),
    932
                suggestion: "Remove unused variable 'x'".to_string(),
    933
                issue_type: "unused_variable".to_string(),
    934
                name: "x".to_string(),
    935
            };
    936
    937
            let json = serde_json::to_string(&issue);
    938
            assert!(json.is_ok());
    939
    940
            let deserialized: Result<LintIssue, _> = serde_json::from_str(&json.unwrap());
    941
            assert!(deserialized.is_ok());
    942
        }
    943
    944
        // ========== Basic Linting Tests ==========
    945
    946
        #[test]
    947
        fn test_lint_empty_expression() {
    948
            let linter = create_test_linter();
    949
            let expr = create_test_expr_literal_int(42);
    950
    951
            let issues = linter.lint(&expr, "42").unwrap();
    952
            assert_eq!(issues.len(), 0);
    953
        }
    954
    955
        #[test]
    956
        fn test_lint_undefined_variable() {
    957
            let linter = create_test_linter_with_rules("undefined");
    958
            let expr = create_test_expr_identifier("undefined_var");
    959
    960
            let issues = linter.lint(&expr, "undefined_var").unwrap();
    961
            assert_eq!(issues.len(), 1);
    962
            assert_eq!(issues[0].rule, "undefined");
    963
            assert_eq!(issues[0].name, "undefined_var");
    964
            assert_eq!(issues[0].severity, "error");
    965
        }
    966
    967
        #[test]
    968
        fn test_lint_builtin_functions() {
    969
            let linter = create_test_linter_with_rules("undefined");
    970
            let println_expr = create_test_expr_identifier("println");
    971
            let print_expr = create_test_expr_identifier("print");
    972
            let eprintln_expr = create_test_expr_identifier("eprintln");
    973
    974
            assert_eq!(linter.lint(&println_expr, "println").unwrap().len(), 0);
    975
            assert_eq!(linter.lint(&print_expr, "print").unwrap().len(), 0);
    976
            assert_eq!(linter.lint(&eprintln_expr, "eprintln").unwrap().len(), 0);
    977
        }
    978
    979
        #[test]
    980
        fn test_lint_unused_variable() {
    981
            let linter = create_test_linter_with_rules("unused");
    982
            let expr = create_test_expr_let(
    983
                "x",
    984
                create_test_expr_literal_int(42),
    985
                create_test_expr_literal_int(0)
    986
            );
    987
    988
            let issues = linter.lint(&expr, "let x = 42; 0").unwrap();
    989
            assert!(issues.iter().any(|i| i.rule == "unused_variable" && i.name == "x"));
    990
        }
    991
    992
        #[test]
    993
        fn test_lint_used_variable() {
    994
            let linter = create_test_linter_with_rules("unused");
    995
            let expr = create_test_expr_let(
    996
                "x",
    997
                create_test_expr_literal_int(42),
    998
                create_test_expr_identifier("x")
    999
            );
    1000
    1001
            let issues = linter.lint(&expr, "let x = 42; x").unwrap();
    1002
            assert!(!issues.iter().any(|i| i.rule == "unused_variable" && i.name == "x"));
    1003
        }
    1004
    1005
        #[test]
    1006
        fn test_lint_variable_shadowing() {
    1007
            let linter = create_test_linter_with_rules("shadowing");
    1008
    1009
            // Direct scope test - this should trigger shadowing
    1010
            let mut parent_scope = Scope::new();
    1011
            parent_scope.define("x".to_string(), 1, 1, VarType::Local);
    1012
            let child_scope = Scope::with_parent(parent_scope);
    1013
            assert!(child_scope.is_shadowing("x"));
    1014
    1015
            // Direct test without function wrapper
    1016
            let outer_let = create_test_expr_let(
    1017
                "x",
    1018
                create_test_expr_literal_int(1),
    1019
                create_test_expr_let(
    1020
                    "x",  // This should shadow the outer x
    1021
                    create_test_expr_literal_int(2),
    1022
                    create_test_expr_identifier("x")
    1023
                )
    1024
            );
    1025
    1026
            let issues = linter.lint(&outer_let, "let x = 1; let x = 2; x").unwrap();
    1027
            eprintln!("Debug - Issues found: {issues:?}");
    1028
            assert!(issues.iter().any(|i| i.rule == "shadowing" && i.name == "x"));
    1029
        }
    1030
    1031
        // ========== Function Linting Tests ==========
    1032
    1033
        #[test]
    1034
        fn test_lint_function_definition() {
    1035
            let linter = create_test_linter_with_rules("unused");
    1036
            let expr = create_test_expr_function(
    1037
                "test_func",
    1038
                vec![create_test_param("x")],
    1039
                create_test_expr_literal_int(42)
    1040
            );
    1041
    1042
            let issues = linter.lint(&expr, "fn test_func(x) { 42 }").unwrap();
    1043
            // Parameters are not flagged as unused in function scope analysis
    1044
            assert!(!issues.iter().any(|i| i.rule == "unused_parameter"));
    1045
        }
    1046
    1047
        #[test]
    1048
        fn test_lint_function_unused_local_variable() {
    1049
            let linter = create_test_linter_with_rules("unused");
    1050
            let body = create_test_expr_let(
    1051
                "local_var",
    1052
                create_test_expr_literal_int(1),
    1053
                create_test_expr_literal_int(42)
    1054
            );
    1055
            let expr = create_test_expr_function(
    1056
                "test_func",
    1057
                vec![],
    1058
                body
    1059
            );
    1060
    1061
            let issues = linter.lint(&expr, "fn test_func() { let local_var = 1; 42 }").unwrap();
    1062
            assert!(issues.iter().any(|i| i.rule == "unused_variable" && i.name == "local_var"));
    1063
        }
    1064
    1065
        // ========== Loop Linting Tests ==========
    1066
    1067
        #[test]
    1068
        fn test_lint_for_loop_unused_variable() {
    1069
            let linter = create_test_linter_with_rules("unused");
    1070
            let expr = create_test_expr_for(
    1071
                "i",
    1072
                Some(Pattern::Identifier("i".to_string())),
    1073
                create_test_expr_literal_int(42),
    1074
                create_test_expr_literal_int(0)
    1075
            );
    1076
    1077
            let issues = linter.lint(&expr, "for i in items { 0 }").unwrap();
    1078
            assert!(issues.iter().any(|i| i.rule.contains("unused") && i.name == "i"));
    1079
        }
    1080
    1081
        #[test]
    1082
        fn test_lint_for_loop_used_variable() {
    1083
            let linter = create_test_linter_with_rules("unused");
    1084
            let expr = create_test_expr_for(
    1085
                "i",
    1086
                Some(Pattern::Identifier("i".to_string())),
    1087
                create_test_expr_literal_int(42),
    1088
                create_test_expr_identifier("i")
    1089
            );
    1090
    1091
            let issues = linter.lint(&expr, "for i in items { i }").unwrap();
    1092
            assert!(!issues.iter().any(|i| i.rule.contains("unused") && i.name == "i"));
    1093
        }
    1094
    1095
        #[test]
    1096
        fn test_lint_for_loop_underscore_variable() {
    1097
            let linter = create_test_linter_with_rules("unused");
    1098
            let expr = create_test_expr_for(
    1099
                "_",
    1100
                Some(Pattern::Identifier("_".to_string())),
    1101
                create_test_expr_literal_int(42),
    1102
                create_test_expr_literal_int(0)
    1103
            );
    1104
    1105
            let issues = linter.lint(&expr, "for _ in items { 0 }").unwrap();
    1106
            assert!(!issues.iter().any(|i| i.name == "_"));
    1107
        }
    1108
    1109
        // ========== Match Expression Tests ==========
    1110
    1111
        #[test]
    1112
        fn test_lint_match_unused_binding() {
    1113
            let linter = create_test_linter_with_rules("unused");
    1114
            let arm = create_test_match_arm(
    1115
                Pattern::Identifier("x".to_string()),
    1116
                create_test_expr_literal_int(42)
    1117
            );
    1118
            let expr = create_test_expr_match(
    1119
                create_test_expr_literal_int(1),
    1120
                vec![arm]
    1121
            );
    1122
    1123
            let issues = linter.lint(&expr, "match value { x => 42 }").unwrap();
    1124
            assert!(issues.iter().any(|i| i.rule.contains("unused") && i.name == "x"));
    1125
        }
    1126
    1127
        #[test]
    1128
        fn test_lint_match_used_binding() {
    1129
            let linter = create_test_linter_with_rules("unused");
    1130
            let arm = create_test_match_arm(
    1131
                Pattern::Identifier("x".to_string()),
    1132
                create_test_expr_identifier("x")
    1133
            );
    1134
            let expr = create_test_expr_match(
    1135
                create_test_expr_literal_int(1),
    1136
                vec![arm]
    1137
            );
    1138
    1139
            let issues = linter.lint(&expr, "match value { x => x }").unwrap();
    1140
            assert!(!issues.iter().any(|i| i.rule.contains("unused") && i.name == "x"));
    1141
        }
    1142
    1143
        #[test]
    1144
        fn test_lint_match_underscore_binding() {
    1145
            let linter = create_test_linter_with_rules("unused");
    1146
            let arm = create_test_match_arm(
    1147
                Pattern::Identifier("_".to_string()),
    1148
                create_test_expr_literal_int(42)
    1149
            );
    1150
            let expr = create_test_expr_match(
    1151
                create_test_expr_literal_int(1),
    1152
                vec![arm]
    1153
            );
    1154
    1155
            let issues = linter.lint(&expr, "match value { _ => 42 }").unwrap();
    1156
            assert!(!issues.iter().any(|i| i.name == "_"));
    1157
        }
    1158
    1159
        // ========== Lambda Expression Tests ==========
    1160
    1161
        #[test]
    1162
        fn test_lint_lambda_unused_parameter() {
    1163
            let linter = create_test_linter_with_rules("unused");
    1164
            let expr = create_test_expr_lambda(
    1165
                vec![create_test_param("x")],
    1166
                create_test_expr_literal_int(42)
    1167
            );
    1168
    1169
            let issues = linter.lint(&expr, "|x| 42").unwrap();
    1170
            assert!(issues.iter().any(|i| i.rule.contains("unused") && i.name == "x"));
    1171
        }
    1172
    1173
        #[test]
    1174
        fn test_lint_lambda_used_parameter() {
    1175
            let linter = create_test_linter_with_rules("unused");
    1176
            let expr = create_test_expr_lambda(
    1177
                vec![create_test_param("x")],
    1178
                create_test_expr_identifier("x")
    1179
            );
    1180
    1181
            let issues = linter.lint(&expr, "|x| x").unwrap();
    1182
            assert!(!issues.iter().any(|i| i.rule.contains("unused") && i.name == "x"));
    1183
        }
    1184
    1185
        // ========== Complexity Tests ==========
    1186
    1187
        #[test]
    1188
        fn test_complexity_calculation_simple() {
    1189
            let linter = create_test_linter();
    1190
            let expr = create_test_expr_literal_int(42);
    1191
            assert_eq!(linter.calculate_complexity(&expr), 0);
    1192
        }
    1193
    1194
        #[test]
    1195
        fn test_complexity_calculation_if() {
    1196
            let linter = create_test_linter();
    1197
            let expr = create_test_expr_if(
    1198
                create_test_expr_literal_int(1),
    1199
                create_test_expr_literal_int(2),
    1200
                Some(create_test_expr_literal_int(3))
    1201
            );
    1202
            assert_eq!(linter.calculate_complexity(&expr), 1);
    1203
        }
    1204
    1205
        #[test]
    1206
        fn test_complexity_calculation_match() {
    1207
            let linter = create_test_linter();
    1208
            let arm = create_test_match_arm(
    1209
                Pattern::Identifier("_".to_string()),
    1210
                create_test_expr_literal_int(42)
    1211
            );
    1212
            let expr = create_test_expr_match(
    1213
                create_test_expr_literal_int(1),
    1214
                vec![arm]
    1215
            );
    1216
            assert_eq!(linter.calculate_complexity(&expr), 2);
    1217
        }
    1218
    1219
        #[test]
    1220
        fn test_complexity_calculation_while() {
    1221
            let linter = create_test_linter();
    1222
            let expr = create_test_expr_while(
    1223
                create_test_expr_literal_int(1),
    1224
                create_test_expr_literal_int(2)
    1225
            );
    1226
            assert_eq!(linter.calculate_complexity(&expr), 2);
    1227
        }
    1228
    1229
        #[test]
    1230
        fn test_complexity_calculation_for() {
    1231
            let linter = create_test_linter();
    1232
            let expr = create_test_expr_for(
    1233
                "i",
    1234
                Some(Pattern::Identifier("i".to_string())),
    1235
                create_test_expr_literal_int(42),
    1236
                create_test_expr_literal_int(0)
    1237
            );
    1238
            assert_eq!(linter.calculate_complexity(&expr), 2);
    1239
        }
    1240
    1241
        #[test]
    1242
        fn test_complexity_limit_violation() {
    1243
            let mut linter = create_test_linter_with_rules("complexity");
    1244
            linter.max_complexity = 1; // Very low limit
    1245
    1246
            let complex_expr = create_test_expr_if(
    1247
                create_test_expr_literal_int(1),
    1248
                create_test_expr_if(
    1249
                    create_test_expr_literal_int(2),
    1250
                    create_test_expr_literal_int(3),
    1251
                    None
    1252
                ),
    1253
                None
    1254
            );
    1255
    1256
            let issues = linter.lint(&complex_expr, "if 1 { if 2 { 3 } }").unwrap();
    1257
            assert!(issues.iter().any(|i| i.rule == "complexity"));
    1258
        }
    1259
    1260
        #[test]
    1261
        fn test_complexity_limit_strict_mode() {
    1262
            let mut linter = create_test_linter_with_rules("complexity");
    1263
            linter.set_strict_mode(true);
    1264
            linter.max_complexity = 0;
    1265
    1266
            let expr = create_test_expr_literal_int(42);
    1267
            let issues = linter.lint(&expr, "42").unwrap();
    1268
            // Simple expression should not trigger complexity
    1269
            assert!(!issues.iter().any(|i| i.rule == "complexity"));
    1270
        }
    1271
    1272
        // ========== Pattern Extraction Tests ==========
    1273
    1274
        #[test]
    1275
        fn test_extract_loop_bindings_tuple() {
    1276
            let linter = create_test_linter();
    1277
            let mut scope = Scope::new();
    1278
            let pattern = Pattern::Tuple(vec![
    1279
                Pattern::Identifier("x".to_string()),
    1280
                Pattern::Identifier("y".to_string())
    1281
            ]);
    1282
    1283
            linter.extract_loop_bindings(&pattern, &mut scope);
    1284
            assert!(scope.is_defined("x"));
    1285
            assert!(scope.is_defined("y"));
    1286
        }
    1287
    1288
        #[test]
    1289
        fn test_extract_loop_bindings_list() {
    1290
            let linter = create_test_linter();
    1291
            let mut scope = Scope::new();
    1292
            let pattern = Pattern::List(vec![
    1293
                Pattern::Identifier("first".to_string()),
    1294
                Pattern::Identifier("second".to_string())
    1295
            ]);
    1296
    1297
            linter.extract_loop_bindings(&pattern, &mut scope);
    1298
            assert!(scope.is_defined("first"));
    1299
            assert!(scope.is_defined("second"));
    1300
        }
    1301
    1302
        #[test]
    1303
        fn test_extract_loop_bindings_struct() {
    1304
            let linter = create_test_linter();
    1305
            let mut scope = Scope::new();
    1306
            let pattern = Pattern::Struct {
    1307
                name: "Point".to_string(),
    1308
                fields: vec![
    1309
                    StructPatternField {
    1310
                        name: "x".to_string(),
    1311
                        pattern: Some(Pattern::Identifier("x_val".to_string())),
    1312
                    },
    1313
                    StructPatternField {
    1314
                        name: "y".to_string(),
    1315
                        pattern: None,
    1316
                    },
    1317
                ],
    1318
                has_rest: false,
    1319
            };
    1320
    1321
            linter.extract_loop_bindings(&pattern, &mut scope);
    1322
            assert!(scope.is_defined("x_val"));
    1323
            assert!(scope.is_defined("y"));
    1324
        }
    1325
    1326
        #[test]
    1327
        fn test_extract_param_bindings_underscore() {
    1328
            let linter = create_test_linter();
    1329
            let mut scope = Scope::new();
    1330
            let pattern = Pattern::Identifier("_".to_string());
    1331
    1332
            linter.extract_param_bindings(&pattern, &mut scope);
    1333
            assert!(!scope.is_defined("_"));
    1334
        }
    1335
    1336
        #[test]
    1337
        fn test_extract_pattern_bindings_nested_option() {
    1338
            let linter = create_test_linter();
    1339
            let mut scope = Scope::new();
    1340
            let pattern = Pattern::Some(Box::new(Pattern::Identifier("value".to_string())));
    1341
    1342
            linter.extract_pattern_bindings(&pattern, &mut scope);
    1343
            assert!(scope.is_defined("value"));
    1344
        }
    1345
    1346
        #[test]
    1347
        fn test_extract_pattern_bindings_ok_err() {
    1348
            let linter = create_test_linter();
    1349
            let mut scope = Scope::new();
    1350
    1351
            let ok_pattern = Pattern::Ok(Box::new(Pattern::Identifier("success".to_string())));
    1352
            linter.extract_pattern_bindings(&ok_pattern, &mut scope);
    1353
            assert!(scope.is_defined("success"));
    1354
    1355
            let err_pattern = Pattern::Err(Box::new(Pattern::Identifier("error".to_string())));
    1356
            linter.extract_pattern_bindings(&err_pattern, &mut scope);
    1357
            assert!(scope.is_defined("error"));
    1358
        }
    1359
    1360
        // ========== Expression Analysis Tests ==========
    1361
    1362
        #[test]
    1363
        fn test_analyze_binary_expression() {
    1364
            let linter = create_test_linter_with_rules("undefined");
    1365
            let expr = create_test_expr_binary(
    1366
                BinaryOp::Add,
    1367
                create_test_expr_identifier("undefined_left"),
    1368
                create_test_expr_identifier("undefined_right")
    1369
            );
    1370
    1371
            let issues = linter.lint(&expr, "undefined_left + undefined_right").unwrap();
    1372
            assert_eq!(issues.len(), 2);
    1373
            assert!(issues.iter().any(|i| i.name == "undefined_left"));
    1374
            assert!(issues.iter().any(|i| i.name == "undefined_right"));
    1375
        }
    1376
    1377
        #[test]
    1378
        fn test_analyze_call_expression() {
    1379
            let linter = create_test_linter_with_rules("undefined");
    1380
            let expr = create_test_expr_call(
    1381
                create_test_expr_identifier("undefined_func"),
    1382
                vec![create_test_expr_identifier("undefined_arg")]
    1383
            );
    1384
    1385
            let issues = linter.lint(&expr, "undefined_func(undefined_arg)").unwrap();
    1386
            assert_eq!(issues.len(), 2);
    1387
            assert!(issues.iter().any(|i| i.name == "undefined_func"));
    1388
            assert!(issues.iter().any(|i| i.name == "undefined_arg"));
    1389
        }
    1390
    1391
        #[test]
    1392
        fn test_analyze_method_call_expression() {
    1393
            let linter = create_test_linter_with_rules("undefined");
    1394
            let expr = create_test_expr_method_call(
    1395
                create_test_expr_identifier("undefined_obj"),
    1396
                "method",
    1397
                vec![create_test_expr_identifier("undefined_arg")]
    1398
            );
    1399
    1400
            let issues = linter.lint(&expr, "undefined_obj.method(undefined_arg)").unwrap();
    1401
            assert_eq!(issues.len(), 2);
    1402
            assert!(issues.iter().any(|i| i.name == "undefined_obj"));
    1403
            assert!(issues.iter().any(|i| i.name == "undefined_arg"));
    1404
        }
    1405
    1406
        #[test]
    1407
        fn test_analyze_string_interpolation() {
    1408
            let linter = create_test_linter_with_rules("undefined");
    1409
            let expr = Expr::new(ExprKind::StringInterpolation {
    1410
                parts: vec![
    1411
                    StringPart::Text("Hello ".to_string()),
    1412
                    StringPart::Expr(Box::new(create_test_expr_identifier("undefined_name"))),
    1413
                    StringPart::ExprWithFormat {
    1414
                        expr: Box::new(create_test_expr_identifier("undefined_age")),
    1415
                        format_spec: "d".to_string()
    1416
                    },
    1417
                ]
    1418
            }, create_test_span());
    1419
    1420
            let issues = linter.lint(&expr, "f\"Hello {undefined_name} {undefined_age:d}\"").unwrap();
    1421
            assert_eq!(issues.len(), 2);
    1422
            assert!(issues.iter().any(|i| i.name == "undefined_name"));
    1423
            assert!(issues.iter().any(|i| i.name == "undefined_age"));
    1424
        }
    1425
    1426
        #[test]
    1427
        fn test_analyze_return_expression() {
    1428
            let linter = create_test_linter_with_rules("undefined");
    1429
            let expr = create_test_expr_return(Some(create_test_expr_identifier("undefined_var")));
    1430
    1431
            let issues = linter.lint(&expr, "return undefined_var").unwrap();
    1432
            assert_eq!(issues.len(), 1);
    1433
            assert!(issues.iter().any(|i| i.name == "undefined_var"));
    1434
        }
    1435
    1436
        #[test]
    1437
        fn test_analyze_list_and_tuple() {
    1438
            let linter = create_test_linter_with_rules("undefined");
    1439
    1440
            let list_expr = Expr::new(ExprKind::List(vec![
    1441
                create_test_expr_identifier("undefined_item")
    1442
            ]), create_test_span());
    1443
    1444
            let tuple_expr = Expr::new(ExprKind::Tuple(vec![
    1445
                create_test_expr_identifier("undefined_elem")
    1446
            ]), create_test_span());
    1447
    1448
            let list_issues = linter.lint(&list_expr, "[undefined_item]").unwrap();
    1449
            assert!(list_issues.iter().any(|i| i.name == "undefined_item"));
    1450
    1451
            let tuple_issues = linter.lint(&tuple_expr, "(undefined_elem,)").unwrap();
    1452
            assert!(tuple_issues.iter().any(|i| i.name == "undefined_elem"));
    1453
        }
    1454
    1455
        #[test]
    1456
        fn test_analyze_field_and_index_access() {
    1457
            let linter = create_test_linter_with_rules("undefined");
    1458
    1459
            let field_expr = Expr::new(ExprKind::FieldAccess {
    1460
                object: Box::new(create_test_expr_identifier("undefined_obj")),
    1461
                field: "property".to_string(),
    1462
            }, create_test_span());
    1463
    1464
            let index_expr = Expr::new(ExprKind::IndexAccess {
    1465
                object: Box::new(create_test_expr_identifier("undefined_arr")),
    1466
                index: Box::new(create_test_expr_identifier("undefined_idx")),
    1467
            }, create_test_span());
    1468
    1469
            let field_issues = linter.lint(&field_expr, "undefined_obj.property").unwrap();
    1470
            assert!(field_issues.iter().any(|i| i.name == "undefined_obj"));
    1471
    1472
            let index_issues = linter.lint(&index_expr, "undefined_arr[undefined_idx]").unwrap();
    1473
            assert_eq!(index_issues.len(), 2);
    1474
            assert!(index_issues.iter().any(|i| i.name == "undefined_arr"));
    1475
            assert!(index_issues.iter().any(|i| i.name == "undefined_idx"));
    1476
        }
    1477
    1478
        #[test]
    1479
        fn test_analyze_assign_expression() {
    1480
            let linter = create_test_linter_with_rules("undefined");
    1481
            let expr = Expr::new(ExprKind::Assign {
    1482
                target: Box::new(create_test_expr_identifier("undefined_target")),
    1483
                value: Box::new(create_test_expr_identifier("undefined_value")),
    1484
            }, create_test_span());
    1485
    1486
            let issues = linter.lint(&expr, "undefined_target = undefined_value").unwrap();
    1487
            assert_eq!(issues.len(), 2);
    1488
            assert!(issues.iter().any(|i| i.name == "undefined_target"));
    1489
            assert!(issues.iter().any(|i| i.name == "undefined_value"));
    1490
        }
    1491
    1492
        // ========== Block Scope Tests ==========
    1493
    1494
        #[test]
    1495
        fn test_analyze_block_unused_variable() {
    1496
            let linter = create_test_linter_with_rules("unused");
    1497
            let block = create_test_expr_block(vec![
    1498
                create_test_expr_let("unused_var", create_test_expr_literal_int(42), create_test_expr_literal_int(0))
    1499
            ]);
    1500
    1501
            let issues = linter.lint(&block, "{ let unused_var = 42; 0 }").unwrap();
    1502
            assert!(issues.iter().any(|i| i.rule == "unused_variable" && i.name == "unused_var"));
    1503
        }
    1504
    1505
        #[test]
    1506
        fn test_analyze_if_branches() {
    1507
            let linter = create_test_linter_with_rules("undefined");
    1508
            let expr = create_test_expr_if(
    1509
                create_test_expr_identifier("undefined_cond"),
    1510
                create_test_expr_identifier("undefined_then"),
    1511
                Some(create_test_expr_identifier("undefined_else"))
    1512
            );
    1513
    1514
            let issues = linter.lint(&expr, "if undefined_cond { undefined_then } else { undefined_else }").unwrap();
    1515
            assert_eq!(issues.len(), 3);
    1516
            assert!(issues.iter().any(|i| i.name == "undefined_cond"));
    1517
            assert!(issues.iter().any(|i| i.name == "undefined_then"));
    1518
            assert!(issues.iter().any(|i| i.name == "undefined_else"));
    1519
        }
    1520
    1521
        // ========== Auto-fix Tests ==========
    1522
    1523
        #[test]
    1524
        fn test_auto_fix_style_issue() {
    1525
            let linter = create_test_linter();
    1526
            let issues = vec![LintIssue {
    1527
                line: 1,
    1528
                column: 1,
    1529
                severity: "warning".to_string(),
    1530
                rule: "style".to_string(),
    1531
                message: "double spaces".to_string(),
    1532
                suggestion: "Use single spaces".to_string(),
    1533
                issue_type: "style".to_string(),
    1534
                name: "spacing".to_string(),
    1535
            }];
    1536
    1537
            let fixed = linter.auto_fix("let  x  =  42", &issues).unwrap();
    1538
            assert_eq!(fixed, "let x = 42");
    1539
        }
    1540
    1541
        #[test]
    1542
        fn test_auto_fix_no_issues() {
    1543
            let linter = create_test_linter();
    1544
            let issues = vec![];
    1545
    1546
            let fixed = linter.auto_fix("let x = 42", &issues).unwrap();
    1547
            assert_eq!(fixed, "let x = 42");
    1548
        }
    1549
    1550
        // ========== Integration Tests ==========
    1551
    1552
        #[test]
    1553
        fn test_comprehensive_linting() {
    1554
            let linter = create_test_linter_with_rules("unused,undefined,shadowing");
    1555
    1556
            // Create nested let expressions for comprehensive testing
    1557
            let unused_let = create_test_expr_let(
    1558
                "unused",
    1559
                create_test_expr_identifier("undefined"),  // This creates undefined variable
    1560
                create_test_expr_identifier("x")
    1561
            );
    1562
            let shadow_let = create_test_expr_let(
    1563
                "x",  // This shadows the outer x
    1564
                create_test_expr_literal_int(2),
    1565
                unused_let
    1566
            );
    1567
            let outer_let = create_test_expr_let(
    1568
                "x",  // Outer variable
    1569
                create_test_expr_literal_int(1),
    1570
                shadow_let
    1571
            );
    1572
    1573
            let issues = linter.lint(&outer_let, "complex code").unwrap();
    1574
            assert!(issues.iter().any(|i| i.rule == "shadowing"));
    1575
            assert!(issues.iter().any(|i| i.rule == "undefined"));
    1576
            assert!(issues.iter().any(|i| i.rule == "unused_variable"));
    1577
        }
    1578
    1579
        #[test]
    1580
        fn test_variable_type_classification() {
    1581
            let var_info = VariableInfo {
    1582
                defined_at: (1, 1),
    1583
                used: false,
    1584
                var_type: VarType::Parameter,
    1585
            };
    1586
    1587
            assert_eq!(var_info.defined_at, (1, 1));
    1588
            assert!(!var_info.used);
    1589
            assert!(matches!(var_info.var_type, VarType::Parameter));
    1590
        }
    1591
    1592
        #[test]
    1593
        fn test_empty_issues_json_compatibility() {
    1594
            let linter = create_test_linter();
    1595
            let expr = create_test_expr_literal_int(42);
    1596
    1597
            let issues = linter.lint(&expr, "42").unwrap();
    1598
            assert_eq!(issues.len(), 0);
    1599
    1600
            let json = serde_json::to_string(&issues).unwrap();
    1601
            assert_eq!(json, "[]");
    1602
        }
    1603
    }