use crate::ast::{AssignmentExpr, AstNode as _};
use crate::linter::diagnostic::{Diagnostic, ViolationData};
use crate::linter::rules::{Example, Rule, RuleContext};
use crate::syntax::{SyntaxElement, SyntaxKind};
pub struct ImplicitAssignment;
const EXAMPLES: &[Example] = &[Example {
caption: "An assignment hidden inside a call argument:",
source: "mean(x <- 1:10)\n",
}];
impl Rule for ImplicitAssignment {
fn id(&self) -> &'static str {
"implicit-assignment"
}
fn description(&self) -> &'static str {
"Flag an assignment (`<-`, `=`, `<<-`, `->`, `->>`) nested inside a call \
or subscript argument, e.g. `mean(x <- 1:10)`. The binding runs as a \
side effect of the argument and is easy to miss; assign on its own \
line instead. The `if`/`while` condition case is covered by \
`assignment-in-condition`, and the data.table / rlang walrus (`:=`) is \
left alone."
}
fn examples(&self) -> &'static [Example] {
EXAMPLES
}
fn default_enabled(&self) -> bool {
false
}
fn interests(&self) -> &'static [SyntaxKind] {
&[SyntaxKind::ASSIGNMENT_EXPR]
}
fn check(&self, el: &SyntaxElement, _ctx: &RuleContext<'_>, sink: &mut Vec<Diagnostic>) {
let Some(node) = el.as_node() else {
return;
};
if node.parent().map(|p| p.kind()) != Some(SyntaxKind::ARG) {
return;
}
let Some(assign) = AssignmentExpr::cast(node.clone()) else {
return;
};
if assign.op_kind() == Some(SyntaxKind::WALRUS) {
return;
}
sink.push(Diagnostic {
rule: "implicit-assignment",
severity: Default::default(),
path: Default::default(),
range: node.text_range(),
message: ViolationData::new(
"implicit-assignment",
"assignment nested in a call argument; assign on its own line",
)
.with_suggestion("Move the assignment to its own statement."),
fix: None,
});
}
}