use arity_formatter::formatter::directive::is_honored_position;
use rowan::NodeOrToken;
use crate::linter::diagnostic::{Diagnostic, ViolationData};
use crate::linter::rules::{Example, Rule, RuleContext};
use crate::linter::suppression::{Directive, Verb};
use crate::syntax::SyntaxKind;
pub struct MisplacedSuppression;
const EXAMPLES: &[Example] = &[
Example {
caption: "The formatter acts on whole statements, so a directive between \
two arguments marks nothing:",
source: "f(\n a = 1,\n # arity-format skip: hand-aligned\n b = 2\n)\n",
},
Example {
caption: "An `on` closes only a region opened with the same prefix, so this \
one closes nothing:",
source: "# arity off\nx <- 1\n# arity-lint on\ny <- 2\n",
},
];
impl Rule for MisplacedSuppression {
fn id(&self) -> &'static str {
"misplaced-suppression"
}
fn description(&self) -> &'static str {
"Flags an `# arity` directive written where it can never take effect. \
A `# arity-format` directive is honored in statement lists — the top level and \
a block body — because that is where the formatter can splice source back \
verbatim; between two call arguments it marks nothing. An `# arity-lint on` \
with no open region closes nothing, which usually means its `off` was written \
with a different prefix (`# arity off` and `# arity-lint off` are separate \
regions). Both fail silently: a directive that does nothing looks exactly like \
one that worked. Report-only — moving the comment would mean guessing which \
statement the author meant."
}
fn examples(&self) -> &'static [Example] {
EXAMPLES
}
fn check_file(&self, ctx: &RuleContext<'_>, sink: &mut Vec<Diagnostic>) {
for directive in ctx.suppressions.directives() {
if directive.verb == Verb::On {
if !directive.matched {
sink.push(report(
directive,
"this `on` closes no open region, so it does nothing",
"open one first, with the same prefix: `# arity-lint off <rule>: <reason>`",
));
}
continue;
}
if directive.tool.affects_format()
&& directive.verb != Verb::SkipFile
&& !honored_here(ctx, directive)
{
sink.push(report(
directive,
"the formatter ignores a directive here; it acts on whole statements",
"move it above the statement, at the top level or in a block body",
));
}
}
}
}
fn honored_here(ctx: &RuleContext<'_>, directive: &Directive) -> bool {
ctx.root
.descendants_with_tokens()
.filter_map(|element| match element {
NodeOrToken::Token(token)
if token.kind() == SyntaxKind::COMMENT
&& token.text_range() == directive.comment =>
{
Some(token)
}
_ => None,
})
.any(|token| is_honored_position(&token))
}
fn report(directive: &Directive, body: &str, suggestion: &str) -> Diagnostic {
Diagnostic {
rule: "misplaced-suppression",
severity: Default::default(),
path: Default::default(),
range: directive.comment,
message: ViolationData::new("misplaced-suppression", body).with_suggestion(suggestion),
fix: None,
}
}