use std::collections::HashSet;
use crate::config::Config;
use crate::linter::diagnostics::{Diagnostic, Severity};
use crate::linter::index::LintIndex;
use crate::syntax::{SyntaxKind, SyntaxNode, SyntaxToken};
pub mod adjacent_footnote_refs;
pub mod chunk_label_spaces;
pub mod citation_keys;
pub mod crossref_as_link_target;
pub mod duplicate_references;
pub mod emoji_aliases;
pub mod empty_list_item;
pub mod figure_crossref_captions;
pub mod footnote_ref_in_footnote_def;
pub mod heading_eaten_attrs;
pub mod heading_hierarchy;
pub mod heading_strip_comments_residue;
pub mod html_entities;
pub mod link_text_is_url;
pub mod math_content;
pub mod missing_chunk_labels;
pub mod stray_fenced_div_markers;
pub mod undefined_anchor;
pub mod undefined_references;
pub mod unused_definitions;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Requirement {
Always,
HeaderAttributes,
Footnotes,
Citations,
FencedCodeAttributes,
FencedDivs,
Emoji,
TexMath,
ChunkFlavor,
}
impl Requirement {
pub fn is_satisfied(self, config: &Config) -> bool {
use crate::config::Flavor;
let ext = &config.extensions;
match self {
Requirement::Always => true,
Requirement::HeaderAttributes => ext.header_attributes,
Requirement::Footnotes => ext.footnotes,
Requirement::Citations => ext.citations,
Requirement::FencedCodeAttributes => ext.fenced_code_attributes,
Requirement::FencedDivs => ext.fenced_divs,
Requirement::Emoji => ext.emoji,
Requirement::TexMath => {
ext.tex_math_dollars
|| ext.tex_math_gfm
|| ext.tex_math_single_backslash
|| ext.tex_math_double_backslash
}
Requirement::ChunkFlavor => {
matches!(config.flavor, Flavor::Quarto | Flavor::RMarkdown)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DiagnosticCode {
pub code: &'static str,
pub severity: Severity,
}
impl DiagnosticCode {
pub const fn warning(code: &'static str) -> Self {
Self {
code,
severity: Severity::Warning,
}
}
pub const fn error(code: &'static str) -> Self {
Self {
code,
severity: Severity::Error,
}
}
pub const fn info(code: &'static str) -> Self {
Self {
code,
severity: Severity::Info,
}
}
}
#[derive(Debug, Clone)]
pub struct RuleMeta {
pub name: &'static str,
pub default_on: bool,
pub requires: Requirement,
pub auto_fix: bool,
pub codes: &'static [DiagnosticCode],
}
pub trait Rule {
fn name(&self) -> &str;
fn metadata(&self) -> RuleMeta;
fn node_interests(&self) -> &'static [SyntaxKind] {
&[]
}
fn wants_text_tokens(&self) -> bool {
false
}
fn check(&self, cx: &LintContext) -> Vec<Diagnostic>;
fn check_tree(
&self,
tree: &SyntaxNode,
input: &str,
config: &Config,
metadata: Option<&crate::metadata::DocumentMetadata>,
) -> Vec<Diagnostic> {
let want_kinds: HashSet<SyntaxKind> = self.node_interests().iter().copied().collect();
let index = LintIndex::build(tree, &want_kinds, self.wants_text_tokens());
let cx = LintContext {
tree,
input,
config,
metadata,
index: &index,
};
self.check(&cx)
}
}
pub struct LintContext<'a> {
pub tree: &'a SyntaxNode,
pub input: &'a str,
pub config: &'a Config,
pub metadata: Option<&'a crate::metadata::DocumentMetadata>,
pub index: &'a LintIndex,
}
impl LintContext<'_> {
pub fn nodes(&self, kind: SyntaxKind) -> &[SyntaxNode] {
self.index.nodes(kind)
}
pub fn text_tokens(&self) -> &[SyntaxToken] {
self.index.text_tokens()
}
}
pub struct RuleRegistry {
rules: Vec<Box<dyn Rule>>,
}
impl RuleRegistry {
pub fn new() -> Self {
Self { rules: Vec::new() }
}
pub fn register(&mut self, rule: Box<dyn Rule>) {
self.rules.push(rule);
}
pub fn rules(&self) -> &[Box<dyn Rule>] {
&self.rules
}
}
impl Default for RuleRegistry {
fn default() -> Self {
Self::new()
}
}