use rowan::NodeOrToken;
use crate::linter::diagnostic::{Diagnostic, Fix, Severity, ViolationData};
use crate::linter::rules::{Rule, RuleContext};
use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxNode};
pub struct AssignmentInCondition;
impl Rule for AssignmentInCondition {
fn id(&self) -> &'static str {
"assignment-in-condition"
}
fn default_severity(&self) -> Severity {
Severity::Warning
}
fn interests(&self) -> &'static [SyntaxKind] {
&[SyntaxKind::IF_EXPR, SyntaxKind::WHILE_EXPR]
}
fn check(&self, el: &SyntaxElement, _ctx: &RuleContext<'_>, sink: &mut Vec<Diagnostic>) {
let Some(node) = el.as_node() else {
return;
};
if let Some(assign) = direct_assignment_in_condition(node) {
let fix = assign
.children_with_tokens()
.find(|e| e.kind() == SyntaxKind::ASSIGN_EQ)
.and_then(|e| e.into_token())
.map(|tok| {
let r = tok.text_range();
Fix::safe(
usize::from(r.start()),
usize::from(r.end()),
"==",
"Replace `=` with `==`",
)
});
sink.push(Diagnostic {
rule: "assignment-in-condition",
severity: Severity::Warning,
path: Default::default(),
range: assign.text_range(),
message: ViolationData::new(
"assignment-in-condition",
"assignment used as a condition; did you mean `==`?",
)
.with_suggestion("Replace `=` with `==` or move the assignment out."),
fix,
});
}
}
}
fn direct_assignment_in_condition(if_or_while: &SyntaxNode) -> Option<SyntaxNode> {
let elements: Vec<_> = if_or_while.children_with_tokens().collect();
let lparen_idx = elements
.iter()
.position(|e| e.kind() == SyntaxKind::LPAREN)?;
let mut depth = 0usize;
let mut rparen_idx = None;
for (i, el) in elements.iter().enumerate().skip(lparen_idx) {
match el.kind() {
SyntaxKind::LPAREN => depth += 1,
SyntaxKind::RPAREN => {
depth = depth.saturating_sub(1);
if depth == 0 {
rparen_idx = Some(i);
break;
}
}
_ => {}
}
}
let rparen_idx = rparen_idx?;
for el in &elements[lparen_idx + 1..rparen_idx] {
if let NodeOrToken::Node(node) = el
&& node.kind() == SyntaxKind::ASSIGNMENT_EXPR
{
return Some(node.clone());
}
}
None
}