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
}