selene_lib/lints/
shadowing.rs1use super::*;
2
3use full_moon::ast::Ast;
4use regex::Regex;
5use serde::Deserialize;
6
7#[derive(Clone, Deserialize)]
8#[serde(default)]
9pub struct ShadowingConfig {
10 ignore_pattern: String,
11}
12
13impl Default for ShadowingConfig {
14 fn default() -> Self {
15 Self {
16 ignore_pattern: "^_".to_owned(),
17 }
18 }
19}
20
21pub struct ShadowingLint {
22 ignore_pattern: Regex,
23}
24
25impl Lint for ShadowingLint {
26 type Config = ShadowingConfig;
27 type Error = regex::Error;
28
29 const SEVERITY: Severity = Severity::Warning;
30 const LINT_TYPE: LintType = LintType::Style;
31
32 fn new(config: Self::Config) -> Result<Self, Self::Error> {
33 Ok(ShadowingLint {
34 ignore_pattern: Regex::new(&config.ignore_pattern)?,
35 })
36 }
37
38 fn pass(&self, _: &Ast, _: &Context, ast_context: &AstContext) -> Vec<Diagnostic> {
39 let mut shadows = Vec::new();
40
41 for (_, variable) in &ast_context.scope_manager.variables {
42 if let Some(shadow_id) = variable.shadowed {
43 let shadow = &ast_context.scope_manager.variables[shadow_id];
44 let definition = shadow.identifiers[0];
45
46 let name = variable.name.to_owned();
47
48 if self.ignore_pattern.is_match(&name) || name == "..." {
49 continue;
50 }
51
52 shadows.push(Shadow {
53 first_defined: (definition.0 as u32, definition.1 as u32),
54 name,
55 range: variable.identifiers[0],
56 });
57 }
58 }
59
60 shadows
61 .iter()
62 .map(|shadow| {
63 Diagnostic::new_complete(
64 "shadowing",
65 format!("shadowing variable `{}`", shadow.name),
66 Label::new(shadow.range),
67 Vec::new(),
68 vec![Label::new_with_message(
69 shadow.first_defined,
70 "previously defined here".to_owned(),
71 )],
72 )
73 })
74 .collect()
75 }
76}
77
78struct Shadow {
79 first_defined: (u32, u32),
80 name: String,
81 range: (usize, usize),
82}
83
84#[cfg(test)]
85mod tests {
86 use super::{super::test_util::test_lint, *};
87
88 #[test]
89 fn test_shadowing() {
90 test_lint(
91 ShadowingLint::new(ShadowingConfig::default()).unwrap(),
92 "shadowing",
93 "shadowing",
94 );
95 }
96
97 #[test]
98 fn test_empty_else() {
99 test_lint(
100 ShadowingLint::new(ShadowingConfig::default()).unwrap(),
101 "shadowing",
102 "empty_else",
103 );
104 }
105}