Coverage Report

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