use std::path::Path;
use rowan::{NodeOrToken, TextRange};
use crate::project::source::{TopLevelEvent, top_level_source_edge_key};
use crate::semantic::{BindingKind, ScopeKind, SemanticModel};
use crate::syntax::{SyntaxKind, SyntaxNode};
pub fn collect_top_level_events(
root: &SyntaxNode,
base_dir: Option<&Path>,
model: &SemanticModel,
) -> Vec<TopLevelEvent> {
collect_top_level_events_spanned(root, base_dir, model)
.into_iter()
.map(|(event, _span)| event)
.collect()
}
pub fn collect_top_level_events_spanned(
root: &SyntaxNode,
base_dir: Option<&Path>,
model: &SemanticModel,
) -> Vec<(TopLevelEvent, Option<TextRange>)> {
let mut events: Vec<(TopLevelEvent, Option<TextRange>)> = Vec::new();
for element in root.children_with_tokens() {
match element {
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::IDENT => {
if let Some(ident) = model
.idents()
.iter()
.find(|ident| ident.range == tok.text_range())
&& model.resolve_local(ident).is_none()
{
events.push((
TopLevelEvent::Read(ident.name.to_string()),
Some(ident.range),
));
}
}
NodeOrToken::Node(child) => extend_with_statement(&mut events, &child, base_dir, model),
_ => {}
}
}
events
}
fn extend_with_statement(
events: &mut Vec<(TopLevelEvent, Option<TextRange>)>,
child: &SyntaxNode,
base_dir: Option<&Path>,
model: &SemanticModel,
) {
if let Some(key) = top_level_source_edge_key(child, base_dir) {
events.push((TopLevelEvent::SourceEdge(key), None));
return;
}
let range = child.text_range();
let mut reads: Vec<(TextRange, &str)> = model
.idents()
.iter()
.filter(|ident| {
model.scope(ident.scope).kind == ScopeKind::File
&& range.contains_range(ident.range)
&& model.resolve_local(ident).is_none()
})
.map(|ident| (ident.range, ident.name.as_str()))
.collect();
reads.sort_by_key(|(range, _)| range.start());
events.extend(
reads
.into_iter()
.map(|(range, name)| (TopLevelEvent::Read(name.to_string()), Some(range))),
);
for binding in model.bindings() {
if matches!(binding.kind, BindingKind::Local | BindingKind::Implicit)
&& model.scope(binding.scope).kind == ScopeKind::File
&& range.contains_range(binding.def_range)
{
events.push((TopLevelEvent::Define(binding.name.to_string()), None));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse;
use crate::project::source::{SourceTarget, TopLevelEvent};
fn events(src: &str) -> Vec<TopLevelEvent> {
let cst = parse(src).cst;
let model = SemanticModel::build(&cst);
collect_top_level_events(&cst, None, &model)
}
fn define(name: &str) -> TopLevelEvent {
TopLevelEvent::Define(name.to_string())
}
fn read(name: &str) -> TopLevelEvent {
TopLevelEvent::Read(name.to_string())
}
#[test]
fn read_before_source_orders_before_the_edge() {
let e = events("before\nsource(\"a.R\")\n");
assert_eq!(e.len(), 2);
assert_eq!(e[0], read("before"));
assert!(matches!(e[1], TopLevelEvent::SourceEdge(_)));
}
#[test]
fn read_after_source_orders_after_the_edge() {
let e = events("source(\"a.R\")\nafter\n");
assert_eq!(e.len(), 2);
assert!(matches!(e[0], TopLevelEvent::SourceEdge(_)));
assert_eq!(e[1], read("after"));
}
#[test]
fn body_reads_are_excluded() {
let e = events("f <- function() bar\n");
assert_eq!(e, vec![define("f")]);
}
#[test]
fn reads_precede_the_define_of_their_statement() {
let e = events("x <- g(y)\n");
assert_eq!(e, vec![read("g"), read("y"), define("x")]);
}
#[test]
fn sys_source_is_a_dynamic_edge() {
let e = events("sys.source(\"a.R\")\n");
assert_eq!(e.len(), 1);
match &e[0] {
TopLevelEvent::SourceEdge(key) => {
assert_eq!(key.target, SourceTarget::Dynamic);
}
other => panic!("expected a dynamic source edge, got {other:?}"),
}
}
#[test]
fn local_true_edge_keeps_its_local_flag() {
let e = events("source(\"a.R\", local = TRUE)\n");
match &e[0] {
TopLevelEvent::SourceEdge(key) => assert!(key.local),
other => panic!("expected a source edge, got {other:?}"),
}
}
#[test]
fn body_edit_leaves_the_sequence_unchanged() {
let a = events("g <- function() 1\nsource(\"x.R\")\nbar\n");
let b = events("g <- function() 1 + 2 + 3\nsource(\"x.R\")\nbar\n");
assert_eq!(a, b);
}
fn spanned(src: &str) -> Vec<(TopLevelEvent, Option<rowan::TextRange>)> {
let cst = parse(src).cst;
let model = SemanticModel::build(&cst);
collect_top_level_events_spanned(&cst, None, &model)
}
#[test]
fn stripping_spans_yields_the_range_free_sequence() {
let src = "x <- g(y)\nsource(\"a.R\")\nbar\n";
let stripped: Vec<TopLevelEvent> = spanned(src).into_iter().map(|(e, _)| e).collect();
assert_eq!(stripped, events(src));
}
#[test]
fn only_reads_carry_a_span() {
for (event, span) in spanned("x <- g(y)\nsource(\"a.R\")\n") {
match event {
TopLevelEvent::Read(_) => assert!(span.is_some(), "a read carries its span"),
TopLevelEvent::Define(_) | TopLevelEvent::SourceEdge(_) => {
assert!(span.is_none(), "a define/edge has no span")
}
}
}
}
#[test]
fn read_span_indexes_the_identifier() {
let src = "x <- foo\n";
let (event, span) = spanned(src)
.into_iter()
.find(|(e, _)| matches!(e, TopLevelEvent::Read(n) if n == "foo"))
.expect("a top-level read of foo");
assert_eq!(event, read("foo"));
let span = span.expect("a read span");
assert_eq!(&src[span], "foo");
}
#[test]
fn body_edit_leaves_the_spanned_sequence_events_unchanged() {
let strip =
|s: &str| -> Vec<TopLevelEvent> { spanned(s).into_iter().map(|(e, _)| e).collect() };
let a = strip("g <- function() 1\nsource(\"x.R\")\nbar\n");
let b = strip("g <- function() 1 + 2 + 3\nsource(\"x.R\")\nbar\n");
assert_eq!(a, b);
}
}