selene_lib/lints/
deprecated.rs

1use std::convert::Infallible;
2
3use full_moon::{ast, visitors::Visitor};
4use serde::Deserialize;
5
6use crate::ast_util::{name_paths::*, range, scopes::ScopeManager};
7
8use super::{super::standard_library::*, *};
9
10#[derive(Clone, Default, Deserialize)]
11#[serde(default)]
12pub struct DeprecatedLintConfig {
13    pub allow: Vec<String>,
14}
15
16pub struct DeprecatedLint {
17    config: DeprecatedLintConfig,
18}
19
20impl Lint for DeprecatedLint {
21    type Config = DeprecatedLintConfig;
22    type Error = Infallible;
23
24    const SEVERITY: Severity = Severity::Warning;
25    const LINT_TYPE: LintType = LintType::Correctness;
26
27    fn new(config: Self::Config) -> Result<Self, Self::Error> {
28        Ok(DeprecatedLint { config })
29    }
30
31    fn pass(&self, ast: &Ast, context: &Context, ast_context: &AstContext) -> Vec<Diagnostic> {
32        let mut visitor = DeprecatedVisitor::new(
33            &self.config,
34            &ast_context.scope_manager,
35            &context.standard_library,
36        );
37
38        visitor.visit_ast(ast);
39
40        visitor.diagnostics
41    }
42}
43
44struct DeprecatedVisitor<'a> {
45    allow: Vec<Vec<String>>,
46    diagnostics: Vec<Diagnostic>,
47    scope_manager: &'a ScopeManager,
48    standard_library: &'a StandardLibrary,
49}
50
51struct Argument {
52    display: String,
53    range: (usize, usize),
54}
55
56impl<'a> DeprecatedVisitor<'a> {
57    fn new(
58        config: &DeprecatedLintConfig,
59        scope_manager: &'a ScopeManager,
60        standard_library: &'a StandardLibrary,
61    ) -> Self {
62        Self {
63            diagnostics: Vec::new(),
64            scope_manager,
65            standard_library,
66
67            allow: config
68                .allow
69                .iter()
70                .map(|allow| allow.split('.').map(ToOwned::to_owned).collect())
71                .collect(),
72        }
73    }
74
75    fn allowed(&self, name_path: &[String]) -> bool {
76        'next_allow_path: for allow_path in &self.allow {
77            if allow_path.len() > name_path.len() {
78                continue;
79            }
80
81            for (allow_word, name_word) in allow_path.iter().zip(name_path.iter()) {
82                if allow_word == "*" {
83                    continue;
84                }
85
86                if allow_word != name_word {
87                    continue 'next_allow_path;
88                }
89            }
90
91            return true;
92        }
93
94        false
95    }
96
97    fn check_name_path<N: Node>(
98        &mut self,
99        node: &N,
100        what: &str,
101        name_path: &[String],
102        arguments: &[Argument],
103    ) {
104        assert!(!name_path.is_empty());
105
106        if self.allowed(name_path) {
107            return;
108        }
109
110        for bound in 1..=name_path.len() {
111            profiling::scope!("DeprecatedVisitor::check_name_path check in bound");
112            let deprecated = match self.standard_library.find_global(&name_path[0..bound]) {
113                Some(Field {
114                    deprecated: Some(deprecated),
115                    ..
116                }) => deprecated,
117
118                _ => continue,
119            };
120
121            let mut notes = vec![deprecated.message.to_owned()];
122
123            if let Some(replace_with) = deprecated.try_instead(
124                &arguments
125                    .iter()
126                    .map(|arg| arg.display.clone())
127                    .collect::<Vec<_>>(),
128            ) {
129                notes.push(format!("try: {replace_with}"));
130            }
131
132            self.diagnostics.push(Diagnostic::new_complete(
133                "deprecated",
134                format!(
135                    "standard library {what} `{}` is deprecated",
136                    name_path.join(".")
137                ),
138                Label::from_node(node, None),
139                notes,
140                Vec::new(),
141            ));
142        }
143
144        if let Some(Field {
145            field_kind: FieldKind::Function(function),
146            ..
147        }) = self.standard_library.find_global(name_path)
148        {
149            for (arg, arg_std) in arguments
150                .iter()
151                .zip(&function.arguments)
152                .filter(|(arg, _)| arg.display != "nil")
153            {
154                if let Some(deprecated) = &arg_std.deprecated {
155                    self.diagnostics.push(Diagnostic::new_complete(
156                        "deprecated",
157                        "this parameter is deprecated".to_string(),
158                        Label::new(arg.range),
159                        vec![deprecated.message.clone()],
160                        Vec::new(),
161                    ));
162                };
163            }
164        }
165    }
166}
167
168impl Visitor for DeprecatedVisitor<'_> {
169    fn visit_expression(&mut self, expression: &ast::Expression) {
170        if let Some(reference) = self
171            .scope_manager
172            .reference_at_byte(expression.start_position().unwrap().bytes())
173        {
174            if reference.resolved.is_some() {
175                return;
176            }
177        }
178
179        let name_path = match name_path(expression) {
180            Some(name_path) => name_path,
181            None => return,
182        };
183
184        self.check_name_path(expression, "expression", &name_path, &[]);
185    }
186
187    fn visit_function_call(&mut self, call: &ast::FunctionCall) {
188        if let Some(reference) = self
189            .scope_manager
190            .reference_at_byte(call.start_position().unwrap().bytes())
191        {
192            if reference.resolved.is_some() {
193                return;
194            }
195        }
196
197        let mut keep_going = true;
198        let mut suffixes: Vec<&ast::Suffix> = call
199            .suffixes()
200            .take_while(|suffix| take_while_keep_going(suffix, &mut keep_going))
201            .collect();
202
203        let name_path = match name_path_from_prefix_suffix(call.prefix(), suffixes.iter().copied())
204        {
205            Some(name_path) => name_path,
206            None => return,
207        };
208
209        let call_suffix = suffixes.pop().unwrap();
210
211        let function_args = match call_suffix {
212            ast::Suffix::Call(call) =>
213            {
214                #[cfg_attr(
215                    feature = "force_exhaustive_checks",
216                    deny(non_exhaustive_omitted_patterns)
217                )]
218                match call {
219                    ast::Call::AnonymousCall(args) => args,
220                    ast::Call::MethodCall(method_call) => method_call.args(),
221                    _ => return,
222                }
223            }
224
225            _ => unreachable!("function_call.call_suffix != ast::Suffix::Call"),
226        };
227
228        #[cfg_attr(
229            feature = "force_exhaustive_checks",
230            deny(non_exhaustive_omitted_patterns)
231        )]
232        let arguments = match function_args {
233            ast::FunctionArgs::Parentheses { arguments, .. } => arguments
234                .iter()
235                .map(|argument| Argument {
236                    display: argument.to_string().trim_end().to_string(),
237                    range: range(argument),
238                })
239                .collect(),
240
241            ast::FunctionArgs::String(token) => vec![
242                (Argument {
243                    display: token.to_string(),
244                    range: range(token),
245                }),
246            ],
247            ast::FunctionArgs::TableConstructor(table_constructor) => {
248                vec![Argument {
249                    display: table_constructor.to_string(),
250                    range: range(table_constructor),
251                }]
252            }
253
254            _ => Vec::new(),
255        };
256
257        self.check_name_path(call, "function", &name_path, &arguments);
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::{super::test_util::*, *};
264
265    #[test]
266    fn test_deprecated_fields() {
267        test_lint(
268            DeprecatedLint::new(DeprecatedLintConfig::default()).unwrap(),
269            "deprecated",
270            "deprecated_fields",
271        );
272    }
273
274    #[test]
275    fn test_deprecated_functions() {
276        test_lint(
277            DeprecatedLint::new(DeprecatedLintConfig::default()).unwrap(),
278            "deprecated",
279            "deprecated_functions",
280        );
281    }
282
283    #[test]
284    fn test_deprecated_params() {
285        test_lint(
286            DeprecatedLint::new(DeprecatedLintConfig::default()).unwrap(),
287            "deprecated",
288            "deprecated_params",
289        );
290    }
291
292    #[test]
293    fn test_specific_allow() {
294        test_lint(
295            DeprecatedLint::new(DeprecatedLintConfig {
296                allow: vec![
297                    "deprecated_allowed".to_owned(),
298                    "more.*".to_owned(),
299                    "wow.*.deprecated_allowed".to_owned(),
300                    "deprecated_param".to_owned(),
301                ],
302            })
303            .unwrap(),
304            "deprecated",
305            "specific_allow",
306        );
307    }
308
309    #[test]
310    fn test_toml_forwards_compatibility() {
311        test_lint(
312            DeprecatedLint::new(DeprecatedLintConfig::default()).unwrap(),
313            "deprecated",
314            "toml_forwards_compatibility",
315        );
316    }
317}