selene_lib/lints/
duplicate_keys.rs

1use crate::ast_util::range;
2
3use super::*;
4use std::{collections::HashMap, convert::Infallible};
5
6use full_moon::{
7    ast::{self, Ast},
8    tokenizer,
9    visitors::Visitor,
10};
11
12pub struct DuplicateKeysLint;
13
14impl Lint for DuplicateKeysLint {
15    type Config = ();
16    type Error = Infallible;
17
18    const SEVERITY: Severity = Severity::Error;
19    const LINT_TYPE: LintType = LintType::Correctness;
20
21    fn new(_: Self::Config) -> Result<Self, Self::Error> {
22        Ok(DuplicateKeysLint)
23    }
24
25    fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
26        let mut visitor = DuplicateKeysVisitor {
27            duplicates: Vec::new(),
28        };
29
30        visitor.visit_ast(ast);
31
32        visitor
33            .duplicates
34            .iter()
35            .map(|duplicate| {
36                Diagnostic::new_complete(
37                    "duplicate_keys",
38                    format!("key `{}` is already declared", duplicate.name),
39                    Label::new(duplicate.position),
40                    Vec::new(),
41                    vec![Label::new_with_message(
42                        duplicate.original_declaration,
43                        format!("`{}` originally declared here", duplicate.name),
44                    )],
45                )
46            })
47            .collect()
48    }
49}
50
51#[derive(Debug, PartialEq, Eq, Hash)]
52enum KeyType {
53    /// A number key type, such as `4` in `{ [4] = "foo" }`, or `1` inferred from `{"foo"}`
54    Number,
55    /// A string key type, or a named identifier, such as `foo` in `{ ["foo"] = "bar" }` or `{ foo = "bar" }`
56    String,
57}
58
59#[derive(Debug, PartialEq, Eq, Hash)]
60struct Key {
61    key_type: KeyType,
62    name: String,
63}
64
65struct DuplicateKey {
66    name: String,
67    position: (usize, usize),
68    original_declaration: (usize, usize),
69}
70
71struct DuplicateKeysVisitor {
72    duplicates: Vec<DuplicateKey>,
73}
74
75/// Attempts to evaluate an expression key such as `"foobar"` in `["foobar"] = true` to a named identifier, `foobar`.
76/// Also extracts `5` from `[5] = true`.
77/// Only works for string literal expression keys, or constant number keys.
78fn expression_to_key(expression: &ast::Expression) -> Option<Key> {
79    if let ast::Expression::String(token) | ast::Expression::Number(token) = expression {
80        return match token.token().token_type() {
81            tokenizer::TokenType::StringLiteral { literal, .. } => Some(Key {
82                key_type: KeyType::String,
83                name: literal.to_string(),
84            }),
85            tokenizer::TokenType::Number { text, .. } => Some(Key {
86                key_type: KeyType::Number,
87                name: text.to_string(),
88            }),
89            _ => None,
90        };
91    }
92
93    None
94}
95
96impl DuplicateKeysVisitor {
97    fn check_field(
98        &mut self,
99        declared_fields: &mut HashMap<Key, (usize, usize)>,
100        key: Key,
101        field_range: (usize, usize),
102    ) {
103        if let Some(original_declaration) = declared_fields.get(&key) {
104            self.duplicates.push(DuplicateKey {
105                name: key.name,
106                position: field_range,
107                original_declaration: *original_declaration,
108            });
109        } else {
110            declared_fields.insert(key, field_range);
111        }
112    }
113}
114
115impl Visitor for DuplicateKeysVisitor {
116    fn visit_table_constructor(&mut self, node: &ast::TableConstructor) {
117        let mut declared_fields = HashMap::new();
118        let mut number_index: usize = 0;
119
120        for field in node.fields() {
121            let field_range = range(field);
122
123            #[cfg_attr(
124                feature = "force_exhaustive_checks",
125                deny(non_exhaustive_omitted_patterns)
126            )]
127            match field {
128                ast::Field::NameKey { key, .. } => {
129                    let key = Key {
130                        key_type: KeyType::String,
131                        name: key.token().to_string(),
132                    };
133                    self.check_field(&mut declared_fields, key, field_range);
134                }
135
136                ast::Field::ExpressionKey { key, .. } => {
137                    if let Some(key) = expression_to_key(key) {
138                        self.check_field(&mut declared_fields, key, field_range);
139                    }
140                }
141
142                ast::Field::NoKey(_) => {
143                    number_index += 1;
144                    let key = Key {
145                        key_type: KeyType::Number,
146                        name: number_index.to_string(),
147                    };
148                    self.check_field(&mut declared_fields, key, field_range)
149                }
150
151                _ => {}
152            }
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::{super::test_util::test_lint, *};
160
161    #[test]
162    fn test_duplicate_keys() {
163        test_lint(
164            DuplicateKeysLint::new(()).unwrap(),
165            "duplicate_keys",
166            "duplicate_keys",
167        );
168    }
169
170    #[test]
171    fn test_duplicate_keys_number_indices() {
172        test_lint(
173            DuplicateKeysLint::new(()).unwrap(),
174            "duplicate_keys",
175            "number_indices",
176        );
177    }
178}