use std::collections::{HashMap, HashSet};
use crate::directives::{Directive, DirectiveTracker, extract_directive_from_node};
use crate::syntax::{SyntaxKind, SyntaxNode, SyntaxToken};
pub struct LintIndex {
nodes_by_kind: HashMap<SyntaxKind, Vec<SyntaxNode>>,
text_tokens: Vec<SyntaxToken>,
ignored_ranges: Vec<(usize, usize)>,
}
impl LintIndex {
pub fn build(tree: &SyntaxNode, want_kinds: &HashSet<SyntaxKind>, want_tokens: bool) -> Self {
let mut nodes_by_kind: HashMap<SyntaxKind, Vec<SyntaxNode>> = HashMap::new();
let mut text_tokens = Vec::new();
let mut tracker = DirectiveTracker::new();
let mut ignored_ranges = Vec::new();
let mut current_ignore_start: Option<usize> = None;
for event in tree.preorder_with_tokens() {
let rowan::WalkEvent::Enter(element) = event else {
continue;
};
match element {
rowan::NodeOrToken::Node(node) => {
let kind = node.kind();
if want_kinds.contains(&kind) {
nodes_by_kind.entry(kind).or_default().push(node.clone());
}
track_ignore_region(
&node,
&mut tracker,
&mut ignored_ranges,
&mut current_ignore_start,
);
}
rowan::NodeOrToken::Token(token) => {
if want_tokens && token.kind() == SyntaxKind::TEXT {
text_tokens.push(token);
}
}
}
}
if let Some(start) = current_ignore_start {
log::debug!("Unclosed ignore region from byte {}", start);
ignored_ranges.push((start, usize::MAX));
}
log::debug!("Total ignored ranges: {:?}", ignored_ranges);
Self {
nodes_by_kind,
text_tokens,
ignored_ranges,
}
}
pub fn nodes(&self, kind: SyntaxKind) -> &[SyntaxNode] {
self.nodes_by_kind
.get(&kind)
.map(Vec::as_slice)
.unwrap_or(&[])
}
pub fn text_tokens(&self) -> &[SyntaxToken] {
&self.text_tokens
}
pub fn ignored_ranges(&self) -> &[(usize, usize)] {
&self.ignored_ranges
}
}
fn track_ignore_region(
node: &SyntaxNode,
tracker: &mut DirectiveTracker,
ignored_ranges: &mut Vec<(usize, usize)>,
current_ignore_start: &mut Option<usize>,
) {
let Some(directive) = extract_directive_from_node(node) else {
return;
};
tracker.process_directive(&directive);
if matches!(directive, Directive::Start(_))
&& tracker.is_linting_ignored()
&& current_ignore_start.is_none()
{
let start: usize = node.text_range().end().into();
*current_ignore_start = Some(start);
log::debug!("Ignore region starts at byte {}", start);
} else if matches!(directive, Directive::End(_))
&& !tracker.is_linting_ignored()
&& let Some(start) = *current_ignore_start
{
let end: usize = node.text_range().start().into();
log::debug!(
"Ignore region ends at byte {}, adding range ({}, {})",
end,
start,
end
);
ignored_ranges.push((start, end));
*current_ignore_start = None;
}
}