use super::tree::syntax_node_at_span;
use super::trivia::{is_rust_non_doc_trivia, is_rust_trivia};
use crate::code_lint::ast::tree::{ParsedFile, span_from_rowan_range};
use crate::diagnostic::SourceSpan;
use ra_ap_syntax::ast;
use ra_ap_syntax::{AstNode as _, SyntaxElement, SyntaxKind, SyntaxNode};
pub fn enclosing_statement_header_span(file: &ParsedFile, span: SourceSpan) -> Option<SourceSpan> {
let start_node = syntax_node_at_span(file, span)?;
let statement = start_node.ancestors().find(|candidate| {
candidate
.parent()
.is_some_and(|parent| is_rust_statement_container(parent.kind()))
})?;
Some(rust_header_span(&statement))
}
const fn is_rust_statement_container(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::SOURCE_FILE
| SyntaxKind::STMT_LIST
| SyntaxKind::ASSOC_ITEM_LIST
| SyntaxKind::ITEM_LIST
| SyntaxKind::EXTERN_ITEM_LIST
)
}
fn rust_header_span(statement: &SyntaxNode) -> SourceSpan {
let fallback_start = usize::from(statement.text_range().start());
let start_offset = statement
.descendants_with_tokens()
.filter_map(SyntaxElement::into_token)
.find(|token| !is_rust_non_doc_trivia(token))
.map_or(fallback_start, |token| {
usize::from(token.text_range().start())
});
let mut header_end_offset = start_offset;
for child in statement.children_with_tokens() {
if is_rust_body_container(child.kind()) {
break;
}
if !is_rust_trivia(child.kind()) {
header_end_offset = usize::from(child.text_range().end());
}
}
SourceSpan::new(start_offset, header_end_offset)
}
const fn is_rust_body_container(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::BLOCK_EXPR
| SyntaxKind::STMT_LIST
| SyntaxKind::ASSOC_ITEM_LIST
| SyntaxKind::ITEM_LIST
| SyntaxKind::EXTERN_ITEM_LIST
)
}
pub fn record_decorator_and_attribute_spans(
file: &ParsedFile,
record: &mut impl FnMut(SourceSpan),
) {
let Some(parsed) = file.rs_parsed() else {
return;
};
for attribute in parsed
.tree()
.syntax()
.descendants()
.filter_map(ast::Attr::cast)
{
record(span_from_rowan_range(attribute.syntax().text_range()));
}
}