use crate::ast::{AstNode, AstToken, BinaryExpr, Expr};
use crate::linter::diagnostic::{Applicability, Diagnostic, Fix};
use crate::linter::rules::matchers;
use crate::linter::rules::{Example, Rule, RuleContext};
use crate::syntax::{SyntaxElement, SyntaxKind};
pub struct MissingComparison;
impl Rule for MissingComparison {
fn id(&self) -> &'static str {
"missing-comparison"
}
fn description(&self) -> &'static str {
"Flag `x == missing` / `x != missing`. `missing` propagates through \
`==`, so the comparison is always `missing` no matter what `x` is, and \
using it as a condition raises a `TypeError`. Use `ismissing` (or the \
identity test `===` / `!==`) instead. The rule reports an unsafe fix \
rewriting `==` to `===` and `!=` to `!==`: the rewrite turns a \
`missing` result into a `Bool`, which is the intent but is still a \
change in behavior."
}
fn examples(&self) -> &'static [Example] {
&[Example {
caption: "Comparing against `missing` by value:",
source: "if x == missing\n 1\nend\n",
}]
}
fn interests(&self) -> &'static [SyntaxKind] {
&[SyntaxKind::BINARY_EXPR]
}
fn check(&self, el: &SyntaxElement, _ctx: &RuleContext<'_>, sink: &mut Vec<Diagnostic>) {
let Some(bin) = el.as_node().cloned().and_then(BinaryExpr::cast) else {
return;
};
let Some(op) = bin.op() else { return };
let replacement = match op.syntax().kind() {
SyntaxKind::EQ_EQ => "===",
SyntaxKind::NOT_EQ => "!==",
_ => return,
};
let is_missing =
|operand: Option<Expr>| operand.is_some_and(|expr| matchers::is_name(&expr, "missing"));
if !is_missing(bin.lhs()) && !is_missing(bin.rhs()) {
return;
}
let op_range = op.syntax().text_range();
let mut diag = Diagnostic::new(
self.id(),
bin.syntax().text_range(),
format!(
"comparison against `missing` by value is always `missing`; \
use `ismissing` or `{replacement}`"
),
);
diag.fixes.push(Fix {
description: format!("Replace `{}` with `{replacement}`", op.text()),
content: replacement.to_string(),
start: op_range.start().into(),
end: op_range.end().into(),
applicability: Applicability::Unsafe,
});
sink.push(diag);
}
}