selene_lib/lints/
duplicate_keys.rs1use 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 Number,
55 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
75fn 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}