selene_lib/lints/
unbalanced_assignments.rs1use super::*;
2use std::convert::Infallible;
3
4use full_moon::{
5 ast::{self, punctuated::Punctuated, Ast},
6 node::Node,
7 tokenizer::{Symbol, TokenType},
8 visitors::Visitor,
9};
10
11pub struct UnbalancedAssignmentsLint;
12
13impl Lint for UnbalancedAssignmentsLint {
14 type Config = ();
15 type Error = Infallible;
16
17 const SEVERITY: Severity = Severity::Warning;
18 const LINT_TYPE: LintType = LintType::Complexity;
19
20 fn new(_: Self::Config) -> Result<Self, Self::Error> {
21 Ok(UnbalancedAssignmentsLint)
22 }
23
24 fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
25 let mut visitor = UnbalancedAssignmentsVisitor {
26 assignments: Vec::new(),
27 };
28
29 visitor.visit_ast(ast);
30
31 visitor
32 .assignments
33 .drain(..)
34 .map(|assignment| {
35 if assignment.more {
36 Diagnostic::new(
37 "unbalanced_assignments",
38 "too many values on the right side of the assignment".to_owned(),
39 Label::new(assignment.range),
40 )
41 } else {
42 let secondary_labels = match assignment.first_call {
43 Some(range) => vec![Label::new_with_message(
44 range,
45 "help: if this function returns more than one value, \
46 the only first return value is actually used"
47 .to_owned(),
48 )],
49 None => Vec::new(),
50 };
51
52 Diagnostic::new_complete(
53 "unbalanced_assignments",
54 "values on right side don't match up to the left side of the assignment"
55 .to_owned(),
56 Label::new(assignment.range),
57 Vec::new(),
58 secondary_labels,
59 )
60 }
61 })
62 .collect()
63 }
64}
65
66struct UnbalancedAssignmentsVisitor {
67 assignments: Vec<UnbalancedAssignment>,
68}
69
70fn expression_is_call(expression: &ast::Expression) -> bool {
71 match expression {
72 ast::Expression::Parentheses { expression, .. } => expression_is_call(expression),
73 ast::Expression::FunctionCall(_) => true,
74
75 _ => false,
76 }
77}
78
79fn expression_is_nil(expression: &ast::Expression) -> bool {
80 match expression {
81 ast::Expression::Parentheses { expression, .. } => expression_is_call(expression),
82 ast::Expression::Symbol(symbol) => {
83 *symbol.token_type()
84 == TokenType::Symbol {
85 symbol: Symbol::Nil,
86 }
87 }
88
89 _ => false,
90 }
91}
92
93fn expression_is_ellipsis(expression: &ast::Expression) -> bool {
94 if let ast::Expression::Symbol(symbol) = expression {
95 return *symbol.token_type()
96 == TokenType::Symbol {
97 symbol: Symbol::Ellipsis,
98 };
99 }
100
101 false
102}
103
104fn range<N: Node>(node: N) -> (u32, u32) {
105 let (start, end) = node.range().unwrap();
106 (start.bytes() as u32, end.bytes() as u32)
107}
108
109impl UnbalancedAssignmentsVisitor {
110 fn lint_assignment(&mut self, lhs: usize, rhs: &Punctuated<ast::Expression>) {
111 if rhs.is_empty() {
112 return;
113 }
114
115 let last_rhs = rhs.last().unwrap().value();
116
117 if rhs.len() > lhs {
118 self.assignments.push(UnbalancedAssignment {
119 more: true,
120 range: (
121 rhs.iter()
123 .nth(lhs)
124 .unwrap()
125 .start_position()
126 .unwrap()
127 .bytes() as u32,
128 last_rhs.end_position().unwrap().bytes() as u32,
129 ),
130 ..UnbalancedAssignment::default()
131 });
132 } else if rhs.len() < lhs
133 && !expression_is_ellipsis(last_rhs)
134 && !expression_is_call(last_rhs)
135 && !expression_is_nil(last_rhs)
136 {
137 self.assignments.push(UnbalancedAssignment {
138 first_call: rhs.iter().find(|e| expression_is_call(e)).map(range),
139 range: range(rhs),
140 ..UnbalancedAssignment::default()
141 });
142 }
143 }
144}
145
146impl Visitor for UnbalancedAssignmentsVisitor {
147 fn visit_assignment(&mut self, assignment: &ast::Assignment) {
148 self.lint_assignment(assignment.variables().len(), assignment.expressions());
149 }
150
151 fn visit_local_assignment(&mut self, assignment: &ast::LocalAssignment) {
152 self.lint_assignment(assignment.names().len(), assignment.expressions());
153 }
154}
155
156#[derive(Clone, Copy, Default)]
157struct UnbalancedAssignment {
158 first_call: Option<(u32, u32)>,
159 more: bool,
160 range: (u32, u32),
161}
162
163#[cfg(test)]
164mod tests {
165 use super::{super::test_util::test_lint, *};
166
167 #[test]
168 fn test_unbalanced_assignments() {
169 test_lint(
170 UnbalancedAssignmentsLint::new(()).unwrap(),
171 "unbalanced_assignments",
172 "unbalanced_assignments",
173 );
174 }
175}