use super::*;
use crate::ast_util::scopes;
use full_moon::ast::Ast;
use regex::Regex;
use serde::Deserialize;
#[derive(Clone, Deserialize)]
#[serde(default)]
pub struct UnusedVariableConfig {
allow_unused_self: bool,
ignore_pattern: String,
}
impl Default for UnusedVariableConfig {
fn default() -> Self {
Self {
allow_unused_self: true,
ignore_pattern: "^_".to_owned(),
}
}
}
pub struct UnusedVariableLint {
allow_unused_self: bool,
ignore_pattern: Regex,
}
impl Rule for UnusedVariableLint {
type Config = UnusedVariableConfig;
type Error = regex::Error;
fn new(config: Self::Config) -> Result<Self, Self::Error> {
Ok(Self {
allow_unused_self: config.allow_unused_self,
ignore_pattern: Regex::new(&config.ignore_pattern)?,
})
}
fn pass(&self, ast: &Ast, _: &Context) -> Vec<Diagnostic> {
let scope_manager = scopes::ScopeManager::new(ast);
let mut diagnostics = Vec::new();
for (_, variable) in scope_manager
.variables
.iter()
.filter(|(_, variable)| !self.ignore_pattern.is_match(&variable.name))
{
let mut references = variable
.references
.iter()
.copied()
.map(|id| &scope_manager.references[id]);
if !references.clone().any(|reference| reference.read) {
let mut notes = Vec::new();
if variable.name == "self" {
if self.allow_unused_self {
continue;
}
notes.push("`self` is implicitly defined when defining a method".to_owned());
notes
.push("if you don't need it, consider using `.` instead of `:`".to_owned());
}
diagnostics.push(Diagnostic::new_complete(
"unused_variable",
if references.any(|reference| reference.write) {
format!("{} is assigned a value, but never used", variable.name)
} else {
format!("{} is defined, but never used", variable.name)
},
Label::new(variable.identifiers[0]),
notes,
Vec::new(),
));
};
}
diagnostics
}
fn severity(&self) -> Severity {
Severity::Warning
}
fn rule_type(&self) -> RuleType {
RuleType::Style
}
}
#[cfg(test)]
mod tests {
use super::{super::test_util::test_lint, *};
#[test]
fn test_blocks() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"blocks",
);
}
#[test]
fn test_locals() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"locals",
);
}
#[test]
fn test_edge_cases() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"edge_cases",
);
}
#[test]
fn test_generic_for_shadowing() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"generic_for_shadowing",
);
}
#[test]
fn test_if() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"if",
);
}
#[test]
fn test_ignore() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"ignore",
);
}
#[test]
fn test_mutating_functions() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"mutating_functions",
);
}
#[test]
fn test_objects() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"objects",
);
}
#[test]
fn test_overriding() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"overriding",
);
}
#[test]
fn test_self() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig {
allow_unused_self: false,
..UnusedVariableConfig::default()
})
.unwrap(),
"unused_variable",
"self",
);
}
#[test]
fn test_self_ignored() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"self_ignored",
);
}
#[test]
fn test_shadowing() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"shadowing",
);
}
#[test]
fn test_varargs() {
test_lint(
UnusedVariableLint::new(UnusedVariableConfig::default()).unwrap(),
"unused_variable",
"varargs",
);
}
#[test]
fn test_invalid_regex() {
assert!(UnusedVariableLint::new(UnusedVariableConfig {
ignore_pattern: "(".to_owned(),
..UnusedVariableConfig::default()
})
.is_err());
}
}