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}