use std::collections::HashMap;
use std::path::Path;
use rowan::{NodeOrToken, TextRange};
use crate::project::source::{TopLevelEvent, top_level_source_edge_key};
use crate::semantic::{Binding, BindingKind, IdentRef, 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 stmt_ranges: Vec<TextRange> = root.children().map(|n| n.text_range()).collect();
let covering = |range: TextRange| -> Option<usize> {
let idx = stmt_ranges.partition_point(|r| r.start() <= range.start());
(idx > 0 && stmt_ranges[idx - 1].contains_range(range)).then(|| idx - 1)
};
let mut reads_by_stmt: Vec<Vec<&IdentRef>> = vec![Vec::new(); stmt_ranges.len()];
let mut bare_reads: HashMap<TextRange, &IdentRef> = HashMap::new();
for ident in model.idents() {
match covering(ident.range) {
Some(i) if model.scope(ident.scope).kind == ScopeKind::File => {
reads_by_stmt[i].push(ident);
}
Some(_) => {}
None => {
bare_reads.insert(ident.range, ident);
}
}
}
let mut defines_by_stmt: Vec<Vec<&Binding>> = vec![Vec::new(); stmt_ranges.len()];
for binding in model.bindings() {
if matches!(binding.kind, BindingKind::Local | BindingKind::Implicit)
&& model.scope(binding.scope).kind == ScopeKind::File
&& let Some(i) = covering(binding.def_range)
{
defines_by_stmt[i].push(binding);
}
}
let mut events: Vec<(TopLevelEvent, Option<TextRange>)> = Vec::new();
let mut stmt_idx = 0usize;
for element in root.children_with_tokens() {
match element {
NodeOrToken::Token(tok) if tok.kind() == SyntaxKind::IDENT => {
if let Some(ident) = bare_reads.get(&tok.text_range())
&& model.resolve_local(ident).is_none()
{
events.push((
TopLevelEvent::Read(ident.name.to_string()),
Some(ident.range),
));
}
}
NodeOrToken::Node(child) => {
let i = stmt_idx;
stmt_idx += 1;
if let Some(key) = top_level_source_edge_key(&child, base_dir) {
events.push((TopLevelEvent::SourceEdge(key), None));
continue;
}
let mut reads = std::mem::take(&mut reads_by_stmt[i]);
reads.sort_by_key(|ident| ident.range.start());
events.extend(
reads
.into_iter()
.filter(|ident| model.resolve_local(ident).is_none())
.map(|ident| {
(
TopLevelEvent::Read(ident.name.to_string()),
Some(ident.range),
)
}),
);
events.extend(
defines_by_stmt[i]
.iter()
.map(|binding| (TopLevelEvent::Define(binding.name.to_string()), None)),
);
}
_ => {}
}
}
events
}
#[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 mixed_statement_shapes_keep_document_order() {
let src = "\
top1
x <- g(y)
source(paste0(prefix, \"a.R\"))
a <- b <- h(z)
top2
";
let e = events(src);
let mut it = e.into_iter();
assert_eq!(it.next(), Some(read("top1")));
assert_eq!(it.next(), Some(read("g")));
assert_eq!(it.next(), Some(read("y")));
assert_eq!(it.next(), Some(define("x")));
assert!(matches!(it.next(), Some(TopLevelEvent::SourceEdge(_))));
assert_eq!(it.next(), Some(read("h")));
assert_eq!(it.next(), Some(read("z")));
assert_eq!(it.next(), Some(define("b")));
assert_eq!(it.next(), Some(define("a")));
assert_eq!(it.next(), Some(read("top2")));
assert_eq!(it.next(), None);
}
#[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);
}
}