use rowan::ast::AstNode as _;
use crate::ast::CallExpr;
use crate::linter::diagnostic::{Diagnostic, Fix, ViolationData};
use crate::linter::rules::matchers;
use crate::linter::rules::{Example, Rule, RuleContext};
use crate::syntax::{SyntaxElement, SyntaxKind};
pub struct Browser;
const EXAMPLES: &[Example] = &[Example {
caption: "A `browser()` call left in after debugging:",
source: "f <- function(x) {\n browser()\n x + 1\n}\n",
}];
impl Rule for Browser {
fn id(&self) -> &'static str {
"browser"
}
fn description(&self) -> &'static str {
"Flag a leftover `browser()` call. `browser()` opens R's interactive \
debugger and is meant to be removed before code is committed; in a \
non-interactive session it silently does nothing, so it lingers \
unnoticed.\n\nOnly a call that resolves to base R's `browser` is \
flagged—a same-named user function is left alone. The safe-delete fix \
is offered only for a `browser()` that is a direct statement (of a block \
or the file top level); in any other position it is withheld so the edit \
can't break syntax."
}
fn examples(&self) -> &'static [Example] {
EXAMPLES
}
fn interests(&self) -> &'static [SyntaxKind] {
&[SyntaxKind::CALL_EXPR]
}
fn check(&self, el: &SyntaxElement, ctx: &RuleContext<'_>, sink: &mut Vec<Diagnostic>) {
let Some(call) = el.as_node().cloned().and_then(CallExpr::cast) else {
return;
};
if matchers::callee_name(&call).as_deref() != Some("browser") {
return;
}
if !ctx.resolves_to_base(&call) {
return;
}
let range = call.syntax().text_range();
let is_statement = call
.syntax()
.parent()
.is_some_and(|p| matches!(p.kind(), SyntaxKind::BLOCK_EXPR | SyntaxKind::ROOT));
let fix = is_statement.then(|| {
let src = ctx.root.text().to_string();
let (start, end) = matchers::deletion_span(&src, range);
Fix::safe(start, end, "", "Remove the `browser()` call")
});
sink.push(Diagnostic {
rule: "browser",
severity: Default::default(),
path: Default::default(),
range,
message: ViolationData::new("browser", "leftover `browser()` debugging call")
.with_suggestion("Remove the `browser()` call."),
fix,
});
}
}