use rowan::TextRange;
use crate::ast::{AstNode, AstToken, CallExpr, MacroCall};
use crate::project::include_target;
use crate::syntax::{SyntaxKind, SyntaxNode};
pub(crate) struct FileScan {
macro_calls: Vec<TextRange>,
quotes: Vec<TextRange>,
pub(crate) calls_eval: bool,
pub(crate) literal_include: bool,
pub(crate) dynamic_include: bool,
}
impl FileScan {
pub(crate) fn collect(root: &SyntaxNode) -> Self {
let mut scan = FileScan {
macro_calls: Vec::new(),
quotes: Vec::new(),
calls_eval: false,
literal_include: false,
dynamic_include: false,
};
for node in root.descendants() {
match node.kind() {
SyntaxKind::MACRO_CALL => {
scan.macro_calls.push(node.text_range());
let name = MacroCall::cast(node)
.and_then(|call| call.name())
.and_then(|name| name.macro_token());
if name.is_some_and(|token| token.text() == "eval") {
scan.calls_eval = true;
}
}
SyntaxKind::QUOTE_EXPR | SyntaxKind::QUOTE_SYM => {
scan.quotes.push(node.text_range());
}
SyntaxKind::CALL_EXPR => {
let Some(call) = CallExpr::cast(node) else {
continue;
};
let Some(callee) = call.callee_ident() else {
continue;
};
match callee.text() {
"eval" => scan.calls_eval = true,
"include" => {
if include_target(&call).is_some() {
scan.literal_include = true;
} else {
scan.dynamic_include = true;
}
}
_ => {}
}
}
_ => {}
}
}
scan
}
pub(crate) fn in_quote(&self, range: TextRange) -> bool {
within(&self.quotes, range)
}
pub(crate) fn in_macro_call(&self, range: TextRange) -> bool {
within(&self.macro_calls, range)
}
pub(crate) fn in_skipped(&self, range: TextRange) -> bool {
self.in_quote(range) || self.in_macro_call(range)
}
}
fn within(extents: &[TextRange], range: TextRange) -> bool {
extents.iter().any(|e| e.contains_range(range))
}