use std::convert::Infallible;
use full_moon::{ast, visitors::Visitor};
use serde::Deserialize;
use crate::ast_util::{name_paths::*, range, scopes::ScopeManager};
use super::{super::standard_library::*, *};
#[derive(Clone, Default, Deserialize)]
#[serde(default)]
pub struct DeprecatedLintConfig {
pub allow: Vec<String>,
}
pub struct DeprecatedLint {
config: DeprecatedLintConfig,
}
impl Lint for DeprecatedLint {
type Config = DeprecatedLintConfig;
type Error = Infallible;
const SEVERITY: Severity = Severity::Warning;
const LINT_TYPE: LintType = LintType::Correctness;
fn new(config: Self::Config) -> Result<Self, Self::Error> {
Ok(DeprecatedLint { config })
}
fn pass(&self, ast: &Ast, context: &Context, ast_context: &AstContext) -> Vec<Diagnostic> {
let mut visitor = DeprecatedVisitor::new(
&self.config,
&ast_context.scope_manager,
&context.standard_library,
);
visitor.visit_ast(ast);
visitor.diagnostics
}
}
struct DeprecatedVisitor<'a> {
allow: Vec<Vec<String>>,
diagnostics: Vec<Diagnostic>,
scope_manager: &'a ScopeManager,
standard_library: &'a StandardLibrary,
}
struct Argument {
display: String,
range: (usize, usize),
}
impl<'a> DeprecatedVisitor<'a> {
fn new(
config: &DeprecatedLintConfig,
scope_manager: &'a ScopeManager,
standard_library: &'a StandardLibrary,
) -> Self {
Self {
diagnostics: Vec::new(),
scope_manager,
standard_library,
allow: config
.allow
.iter()
.map(|allow| allow.split('.').map(ToOwned::to_owned).collect())
.collect(),
}
}
fn allowed(&self, name_path: &[String]) -> bool {
'next_allow_path: for allow_path in &self.allow {
if allow_path.len() > name_path.len() {
continue;
}
for (allow_word, name_word) in allow_path.iter().zip(name_path.iter()) {
if allow_word == "*" {
continue;
}
if allow_word != name_word {
continue 'next_allow_path;
}
}
return true;
}
false
}
fn check_name_path<N: Node>(
&mut self,
node: &N,
what: &str,
name_path: &[String],
arguments: &[Argument],
) {
assert!(!name_path.is_empty());
if self.allowed(name_path) {
return;
}
for bound in 1..=name_path.len() {
profiling::scope!("DeprecatedVisitor::check_name_path check in bound");
let deprecated = match self.standard_library.find_global(&name_path[0..bound]) {
Some(Field {
deprecated: Some(deprecated),
..
}) => deprecated,
_ => continue,
};
let mut notes = vec![deprecated.message.to_owned()];
if let Some(replace_with) = deprecated.try_instead(
&arguments
.iter()
.map(|arg| arg.display.clone())
.collect::<Vec<_>>(),
) {
notes.push(format!("try: {replace_with}"));
}
self.diagnostics.push(Diagnostic::new_complete(
"deprecated",
format!(
"standard library {what} `{}` is deprecated",
name_path.join(".")
),
Label::from_node(node, None),
notes,
Vec::new(),
));
}
if let Some(Field {
field_kind: FieldKind::Function(function),
..
}) = self.standard_library.find_global(name_path)
{
for (arg, arg_std) in arguments
.iter()
.zip(&function.arguments)
.filter(|(arg, _)| arg.display != "nil")
{
if let Some(deprecated) = &arg_std.deprecated {
self.diagnostics.push(Diagnostic::new_complete(
"deprecated",
"this parameter is deprecated".to_string(),
Label::new(arg.range),
vec![deprecated.message.clone()],
Vec::new(),
));
};
}
}
}
}
impl Visitor for DeprecatedVisitor<'_> {
fn visit_expression(&mut self, expression: &ast::Expression) {
if let Some(reference) = self
.scope_manager
.reference_at_byte(expression.start_position().unwrap().bytes())
{
if reference.resolved.is_some() {
return;
}
}
let name_path = match name_path(expression) {
Some(name_path) => name_path,
None => return,
};
self.check_name_path(expression, "expression", &name_path, &[]);
}
fn visit_function_call(&mut self, call: &ast::FunctionCall) {
if let Some(reference) = self
.scope_manager
.reference_at_byte(call.start_position().unwrap().bytes())
{
if reference.resolved.is_some() {
return;
}
}
let mut keep_going = true;
let mut suffixes: Vec<&ast::Suffix> = call
.suffixes()
.take_while(|suffix| take_while_keep_going(suffix, &mut keep_going))
.collect();
let name_path = match name_path_from_prefix_suffix(call.prefix(), suffixes.iter().copied())
{
Some(name_path) => name_path,
None => return,
};
let call_suffix = suffixes.pop().unwrap();
let function_args = match call_suffix {
ast::Suffix::Call(call) =>
{
#[cfg_attr(
feature = "force_exhaustive_checks",
deny(non_exhaustive_omitted_patterns)
)]
match call {
ast::Call::AnonymousCall(args) => args,
ast::Call::MethodCall(method_call) => method_call.args(),
_ => return,
}
}
_ => unreachable!("function_call.call_suffix != ast::Suffix::Call"),
};
#[cfg_attr(
feature = "force_exhaustive_checks",
deny(non_exhaustive_omitted_patterns)
)]
let arguments = match function_args {
ast::FunctionArgs::Parentheses { arguments, .. } => arguments
.iter()
.map(|argument| Argument {
display: argument.to_string().trim_end().to_string(),
range: range(argument),
})
.collect(),
ast::FunctionArgs::String(token) => vec![
(Argument {
display: token.to_string(),
range: range(token),
}),
],
ast::FunctionArgs::TableConstructor(table_constructor) => {
vec![Argument {
display: table_constructor.to_string(),
range: range(table_constructor),
}]
}
_ => Vec::new(),
};
self.check_name_path(call, "function", &name_path, &arguments);
}
}
#[cfg(test)]
mod tests {
use super::{super::test_util::*, *};
#[test]
fn test_deprecated_fields() {
test_lint(
DeprecatedLint::new(DeprecatedLintConfig::default()).unwrap(),
"deprecated",
"deprecated_fields",
);
}
#[test]
fn test_deprecated_functions() {
test_lint(
DeprecatedLint::new(DeprecatedLintConfig::default()).unwrap(),
"deprecated",
"deprecated_functions",
);
}
#[test]
fn test_deprecated_params() {
test_lint(
DeprecatedLint::new(DeprecatedLintConfig::default()).unwrap(),
"deprecated",
"deprecated_params",
);
}
#[test]
fn test_specific_allow() {
test_lint(
DeprecatedLint::new(DeprecatedLintConfig {
allow: vec![
"deprecated_allowed".to_owned(),
"more.*".to_owned(),
"wow.*.deprecated_allowed".to_owned(),
"deprecated_param".to_owned(),
],
})
.unwrap(),
"deprecated",
"specific_allow",
);
}
#[test]
fn test_toml_forwards_compatibility() {
test_lint(
DeprecatedLint::new(DeprecatedLintConfig::default()).unwrap(),
"deprecated",
"toml_forwards_compatibility",
);
}
}