use crate::config::Config;
use crate::linter::diagnostics::{Diagnostic, DiagnosticNoteKind, Location};
use crate::linter::rules::Rule;
use crate::syntax::{AttributeNode, Heading, InlineHtml, SyntaxNode};
use rowan::ast::AstNode;
pub struct HeadingStripCommentsResidueRule;
impl Rule for HeadingStripCommentsResidueRule {
fn name(&self) -> &str {
"heading-strip-comments-residue"
}
fn check(
&self,
tree: &SyntaxNode,
input: &str,
_config: &Config,
_metadata: Option<&crate::metadata::DocumentMetadata>,
) -> Vec<Diagnostic> {
let mut diagnostics = Vec::new();
for heading in tree.descendants().filter_map(Heading::cast) {
let has_attr = heading
.syntax()
.children()
.any(|child| AttributeNode::cast(child).is_some());
if !has_attr {
continue;
}
let content = match heading.content() {
Some(c) => c,
None => continue,
};
for comment in content
.syntax()
.descendants()
.filter_map(InlineHtml::cast)
.filter(InlineHtml::is_comment)
{
let range = comment.syntax().text_range();
let location = Location::from_range(range, input);
diagnostics.push(
Diagnostic::warning(
location,
"heading-strip-comments-residue",
"Comment on a heading line adjacent to `{...}` attributes; `pandoc --strip-comments` will leave stray whitespace on the heading.",
)
.with_note(
DiagnosticNoteKind::Help,
"Move the comment to its own line before or after the heading.",
),
);
}
}
diagnostics
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
fn lint(input: &str) -> Vec<Diagnostic> {
let config = Config::default();
let tree = crate::parser::parse(input, Some(config.clone()));
HeadingStripCommentsResidueRule.check(&tree, input, &config, None)
}
#[test]
fn comment_before_attrs_warns() {
let input = "# Title <!-- x --> {.unnumbered}\n";
let diags = lint(input);
assert_eq!(diags.len(), 1);
assert_eq!(diags[0].code, "heading-strip-comments-residue");
assert!(
diags[0].message.contains("--strip-comments"),
"got: {}",
diags[0].message
);
}
#[test]
fn trailing_comment_does_not_fire_here() {
let input = "# Title {.unnumbered} <!-- x -->\n";
let diags = lint(input);
assert_eq!(diags.len(), 0);
}
#[test]
fn heading_without_comment_is_ignored() {
let input = "# Title {.unnumbered}\n";
let diags = lint(input);
assert_eq!(diags.len(), 0);
}
#[test]
fn heading_without_attrs_is_ignored() {
let input = "# Title <!-- TODO -->\n";
let diags = lint(input);
assert_eq!(diags.len(), 0);
}
#[test]
fn comment_elsewhere_is_ignored() {
let input = "A paragraph with <!-- comment --> in it.\n\n# Title {.x}\n";
let diags = lint(input);
assert_eq!(diags.len(), 0);
}
}