use super::*;
use std::convert::Infallible;
use full_moon::{
ast::{self, punctuated::Punctuated, Ast},
node::Node,
tokenizer::{Symbol, TokenType},
visitors::Visitor,
};
pub struct UnbalancedAssignmentsLint;
impl Rule for UnbalancedAssignmentsLint {
type Config = ();
type Error = Infallible;
fn new(_: Self::Config) -> Result<Self, Self::Error> {
Ok(UnbalancedAssignmentsLint)
}
fn pass(&self, ast: &Ast, _: &Context) -> Vec<Diagnostic> {
let mut visitor = UnbalancedAssignmentsVisitor {
assignments: Vec::new(),
};
visitor.visit_ast(&ast);
visitor
.assignments
.drain(..)
.map(|assignment| {
if assignment.more {
Diagnostic::new(
"unbalanced_assignments",
"too many values on the right side of the assignment".to_owned(),
Label::new(assignment.range),
)
} else {
let secondary_labels = match assignment.first_call {
Some(range) => vec![Label::new_with_message(
range,
"help: if this function returns more than one value, \
the only first return value is actually used"
.to_owned(),
)],
None => Vec::new(),
};
Diagnostic::new_complete(
"unbalanced_assignments",
"values on right side don't match up to the left side of the assignment"
.to_owned(),
Label::new(assignment.range),
Vec::new(),
secondary_labels,
)
}
})
.collect()
}
fn severity(&self) -> Severity {
Severity::Warning
}
fn rule_type(&self) -> RuleType {
RuleType::Complexity
}
}
struct UnbalancedAssignmentsVisitor {
assignments: Vec<UnbalancedAssignment>,
}
fn expression_is_call(expression: &ast::Expression) -> bool {
match expression {
ast::Expression::Parentheses { expression, .. } => expression_is_call(expression),
ast::Expression::Value { value, binop } => {
if binop.is_none() {
if let ast::Value::FunctionCall(_) = &**value {
true
} else {
false
}
} else {
false
}
}
_ => false,
}
}
fn expression_is_nil(expression: &ast::Expression) -> bool {
match expression {
ast::Expression::Parentheses { expression, .. } => expression_is_call(expression),
ast::Expression::Value { value, binop } => {
if binop.is_none() {
if let ast::Value::Symbol(symbol) = &**value {
*symbol.token_type()
== TokenType::Symbol {
symbol: Symbol::Nil,
}
} else {
false
}
} else {
false
}
}
_ => false,
}
}
fn range<N: Node>(node: N) -> (u32, u32) {
let (start, end) = node.range().unwrap();
(start.bytes() as u32, end.bytes() as u32)
}
impl UnbalancedAssignmentsVisitor {
fn lint_assignment(&mut self, lhs: usize, rhs: &Punctuated<ast::Expression>) {
if rhs.is_empty() {
return;
}
let last_rhs = rhs.iter().last().unwrap();
if rhs.len() > lhs {
self.assignments.push(UnbalancedAssignment {
more: true,
range: (
rhs.iter()
.nth(lhs)
.unwrap()
.start_position()
.unwrap()
.bytes() as u32,
last_rhs.end_position().unwrap().bytes() as u32,
),
..UnbalancedAssignment::default()
});
} else if rhs.len() < lhs && !expression_is_call(last_rhs) && !expression_is_nil(last_rhs) {
self.assignments.push(UnbalancedAssignment {
first_call: rhs.iter().find(|e| expression_is_call(e)).map(range),
range: range(rhs),
..UnbalancedAssignment::default()
});
}
}
}
impl Visitor<'_> for UnbalancedAssignmentsVisitor {
fn visit_assignment(&mut self, assignment: &ast::Assignment) {
self.lint_assignment(assignment.var_list().len(), assignment.expr_list());
}
fn visit_local_assignment(&mut self, assignment: &ast::LocalAssignment) {
self.lint_assignment(assignment.name_list().len(), assignment.expr_list());
}
}
#[derive(Clone, Copy, Default)]
struct UnbalancedAssignment {
first_call: Option<(u32, u32)>,
more: bool,
range: (u32, u32),
}
#[cfg(test)]
mod tests {
use super::{super::test_util::test_lint, *};
#[test]
fn test_unbalanced_assignments() {
test_lint(
UnbalancedAssignmentsLint::new(()).unwrap(),
"unbalanced_assignments",
"unbalanced_assignments",
);
}
}