use std::cmp::Ordering;
use super::extents::{Visibility, is_rust_item, rust_use_aliases};
use super::{
BTreeSet, Declaration, ProgramFact, ProgramRange, ProgramScope, ProgramSourceMapping,
SemanticToken, TokenKind,
};
const RUST_PATH_ROOTS: &[&str] = &["self", "super", "crate"];
struct Entry<'a> {
name: &'a str,
scope: usize,
start: usize,
visibility: Visibility,
target: usize,
}
struct Resolver<'a> {
tokens: &'a [SemanticToken],
language: &'a str,
declarations: &'a [Declaration],
parents: Vec<Option<usize>>,
depths: Vec<usize>,
entries: Vec<Entry<'a>>,
module_bodies: BTreeSet<usize>,
}
pub(super) fn resolve_references(
tokens: &[SemanticToken],
syntax: &[ProgramSourceMapping],
language: &str,
scopes: &[ProgramScope],
declarations: &[Declaration],
) -> (Vec<Vec<ProgramRange>>, Vec<ProgramFact>) {
let mut resolver = Resolver {
tokens,
language,
declarations,
parents: scopes
.iter()
.map(|scope| {
scope
.parent
.as_deref()
.and_then(|parent| scopes.iter().position(|candidate| candidate.id == parent))
})
.collect(),
depths: scopes.iter().map(|scope| scope.depth).collect(),
entries: declarations
.iter()
.enumerate()
.map(|(index, declaration)| Entry {
name: &tokens[declaration.token].text,
scope: declaration.scope,
start: tokens[declaration.token].range.start,
visibility: declaration.visibility,
target: index,
})
.collect(),
module_bodies: declarations
.iter()
.filter_map(|declaration| declaration.visibility.body)
.collect(),
};
if language == "Rust" {
let aliases = rust_use_aliases(tokens, syntax)
.into_iter()
.filter_map(|index| {
let token = &tokens[index];
resolver.path_target(index).map(|target| Entry {
name: &token.text,
scope: token.scope,
start: token.range.start,
visibility: Visibility::default(),
target,
})
})
.collect::<Vec<_>>();
resolver.entries.extend(aliases);
}
let ignored_terms: &[&str] = match language {
"JavaScript" => &["property_identifier", "private_property_identifier"],
"Rust" => &["field_identifier", "fragment_specifier"],
_ => &[],
};
let ignored = syntax
.iter()
.filter(|fact| ignored_terms.contains(&fact.term.as_str()))
.map(|fact| fact.range)
.collect::<BTreeSet<_>>();
let declared = declarations
.iter()
.map(|declaration| declaration.token)
.collect::<BTreeSet<_>>();
let mut references = vec![Vec::new(); declarations.len()];
let mut unresolved = Vec::new();
for (index, token) in tokens.iter().enumerate() {
if token.kind != TokenKind::Identifier
|| declared.contains(&index)
|| ignored.contains(&token.range)
{
continue;
}
if language == "Rust" && index > 0 && tokens[index - 1].text == "::" {
if let Some(target) = resolver.path_target(index) {
references[target].push(token.range);
}
continue;
}
if let Some(target) = resolver.lexical(index) {
references[target].push(token.range);
} else {
unresolved.push(ProgramFact::new("unresolved", &token.text, token.range));
}
}
(references, unresolved)
}
impl Resolver<'_> {
fn contains(&self, outer: usize, inner: usize) -> bool {
let mut current = Some(inner);
while let Some(scope) = current {
if scope == outer {
return true;
}
current = self.parents[scope];
}
false
}
fn prefer(&self, left: &Entry<'_>, right: &Entry<'_>, start: usize) -> Ordering {
self.depths[left.scope]
.cmp(&self.depths[right.scope])
.then_with(|| {
let left_before = left.start <= start;
let right_before = right.start <= start;
left_before.cmp(&right_before).then_with(|| {
if left_before {
left.start.cmp(&right.start)
} else {
right.start.cmp(&left.start)
}
})
})
}
fn lexical(&self, index: usize) -> Option<usize> {
let token = &self.tokens[index];
let sigil = self.language == "Rust"
&& index > 0
&& self.tokens[index - 1].text == "$"
&& self.tokens[index - 1].range.end == token.range.start;
self.entries
.iter()
.filter(|entry| {
entry.name == token.text
&& entry.visibility.sigil == sigil
&& entry.visibility.from <= token.range.start
&& token.range.start < entry.visibility.until
&& self.contains(entry.scope, token.scope)
})
.rev()
.max_by(|left, right| self.prefer(left, right, token.range.start))
.map(|entry| entry.target)
}
fn nearest_module(&self, scope: usize) -> usize {
let mut current = scope;
while let Some(parent) = self.parents[current] {
if self.module_bodies.contains(¤t) {
break;
}
current = parent;
}
current
}
fn parent_module(&self, module: usize) -> Option<usize> {
self.parents[module].map(|parent| self.nearest_module(parent))
}
fn member(&self, module: usize, name: &str) -> Option<usize> {
self.declarations.iter().position(|declaration| {
declaration.scope == module
&& self.tokens[declaration.token].text == name
&& is_rust_item(&declaration.kind)
})
}
fn body(&self, declaration: Option<usize>) -> Option<usize> {
declaration.and_then(|declaration| self.declarations[declaration].visibility.body)
}
fn path_target(&self, index: usize) -> Option<usize> {
let tokens = self.tokens;
let mut segments = vec![index];
let mut cursor = index;
while cursor >= 2
&& tokens[cursor - 1].text == "::"
&& (tokens[cursor - 2].kind == TokenKind::Identifier
|| RUST_PATH_ROOTS.contains(&tokens[cursor - 2].text.as_str()))
{
cursor -= 2;
segments.insert(0, cursor);
}
if segments.len() < 2 || (cursor > 0 && tokens[cursor - 1].text == "::") {
return None;
}
let head = &tokens[segments[0]];
let mut module = match head.text.as_str() {
"crate" => Some(0),
"self" => Some(self.nearest_module(head.scope)),
"super" => self.parent_module(self.nearest_module(head.scope)),
_ if head.kind == TokenKind::Identifier => self.body(self.lexical(segments[0])),
_ => None,
};
for &segment in &segments[1..segments.len() - 1] {
let current = module?;
module = if tokens[segment].text == "super" {
self.parent_module(current)
} else {
self.body(self.member(current, &tokens[segment].text))
};
}
self.member(module?, &tokens[index].text)
}
}