use crate::lang::Language;
use crate::source::SourceFile;
use crate::symbols::tree_sitter_language;
use serde::Serialize;
use tree_sitter::{Node, Parser};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum OccurrenceKind {
Definition,
Reference,
Shadowed,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Occurrence {
pub(crate) start_byte: usize,
pub(crate) end_byte: usize,
pub(crate) kind: OccurrenceKind,
}
#[derive(Debug)]
pub(crate) struct Binding {
pub(crate) name: String,
pub(crate) occurrences: Vec<Occurrence>,
}
#[derive(Debug)]
pub(crate) struct Conflict {
pub(crate) byte: usize,
pub(crate) reason: String,
}
pub(crate) fn supported(language: Language) -> bool {
binding_table(language).is_some()
}
pub(crate) fn resolve(source: &SourceFile, byte: usize) -> Option<Binding> {
resolve_with_conflicts(source, byte, None).map(|(binding, _)| binding)
}
pub(crate) fn resolve_with_conflicts(
source: &SourceFile,
byte: usize,
new_name: Option<&str>,
) -> Option<(Binding, Vec<Conflict>)> {
let table = binding_table(source.detection.language)?;
let language = tree_sitter_language(source.detection.language)?;
let mut parser = Parser::new();
parser.set_language(&language).ok()?;
let tree = parser.parse(&source.text, None)?;
let root = tree.root_node();
let src = source.text.as_bytes();
let lookup = byte.min(source.text.len().saturating_sub(1));
let cursor = identifier_leaf(root.descendant_for_byte_range(lookup, lookup)?, table)?;
let name = cursor.utf8_text(src).ok()?.to_owned();
let mut declarations = Vec::new();
collect_declarations(root, src, table, &mut declarations);
let target_def = resolve_node(cursor, &name, &declarations)?;
let mut occurrences = Vec::new();
collect_occurrences(
root,
src,
table,
&name,
target_def,
&declarations,
&mut occurrences,
);
occurrences.sort_by_key(|occurrence| occurrence.start_byte);
let conflicts = new_name
.map(|new_name| find_conflicts(root, new_name, &occurrences, &declarations))
.unwrap_or_default();
Some((Binding { name, occurrences }, conflicts))
}
fn find_conflicts(
root: Node<'_>,
new_name: &str,
occurrences: &[Occurrence],
declarations: &[Declaration<'_>],
) -> Vec<Conflict> {
let mut conflicts = Vec::new();
for occurrence in occurrences {
let byte = occurrence.start_byte;
let Some(node) = root.descendant_for_byte_range(byte, byte) else {
continue;
};
if resolve_node(node, new_name, declarations).is_some() {
conflicts.push(Conflict {
byte,
reason: format!("`{new_name}` already resolves to a binding here"),
});
}
}
conflicts
}
struct Declaration<'tree> {
name: String,
ident: Node<'tree>,
scope: usize,
}
fn scope_of(node: Node<'_>, table: &BindingTable) -> usize {
let mut current = node.parent();
while let Some(parent) = current {
if table.scope_kinds.contains(&parent.kind()) {
return parent.id();
}
current = parent.parent();
}
node_root(node).id()
}
fn node_root(node: Node<'_>) -> Node<'_> {
let mut current = node;
while let Some(parent) = current.parent() {
current = parent;
}
current
}
fn collect_declarations<'tree>(
node: Node<'tree>,
src: &[u8],
table: &BindingTable,
out: &mut Vec<Declaration<'tree>>,
) {
for ident in (table.declared_idents)(node) {
if let Ok(name) = ident.utf8_text(src) {
out.push(Declaration {
name: name.to_owned(),
ident,
scope: scope_of(ident, table),
});
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
collect_declarations(child, src, table, out);
}
}
fn resolve_node(node: Node<'_>, name: &str, declarations: &[Declaration<'_>]) -> Option<usize> {
let use_start = node.start_byte();
let mut scope = Some(node);
while let Some(current) = scope {
if let Some(declaration) = declarations
.iter()
.filter(|declaration| {
declaration.name == name
&& declaration.scope == current.id()
&& declaration.ident.start_byte() <= use_start
})
.max_by_key(|declaration| declaration.ident.start_byte())
{
return Some(declaration.ident.start_byte());
}
scope = current.parent();
}
None
}
#[allow(clippy::too_many_arguments)]
fn collect_occurrences(
node: Node<'_>,
src: &[u8],
table: &BindingTable,
name: &str,
target_def: usize,
declarations: &[Declaration<'_>],
out: &mut Vec<Occurrence>,
) {
if is_identifier_kind(node.kind(), table)
&& node.child_count() == 0
&& node.utf8_text(src) == Ok(name)
{
let resolved = resolve_node(node, name, declarations);
let kind = if resolved == Some(target_def) {
if node.start_byte() == target_def {
OccurrenceKind::Definition
} else {
OccurrenceKind::Reference
}
} else {
OccurrenceKind::Shadowed
};
out.push(Occurrence {
start_byte: node.start_byte(),
end_byte: node.end_byte(),
kind,
});
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
collect_occurrences(child, src, table, name, target_def, declarations, out);
}
}
fn identifier_leaf<'tree>(node: Node<'tree>, table: &BindingTable) -> Option<Node<'tree>> {
let mut current = node;
while current.named_child_count() > 0 {
let byte = current.start_byte();
match current.named_descendant_for_byte_range(byte, byte) {
Some(child) if child.id() != current.id() => current = child,
_ => break,
}
}
is_identifier_kind(current.kind(), table).then_some(current)
}
fn is_identifier_kind(kind: &str, table: &BindingTable) -> bool {
table.identifier_kinds.contains(&kind)
}
struct BindingTable {
languages: &'static [Language],
scope_kinds: &'static [&'static str],
identifier_kinds: &'static [&'static str],
declared_idents: fn(Node<'_>) -> Vec<Node<'_>>,
}
fn binding_table(language: Language) -> Option<&'static BindingTable> {
BINDING_TABLES
.iter()
.find(|table| table.languages.contains(&language))
}
static BINDING_TABLES: &[BindingTable] = &[
BindingTable {
languages: &[Language::Rust],
scope_kinds: &[
"block",
"function_item",
"closure_expression",
"match_arm",
"for_expression",
"while_let_expression",
"if_let_expression",
],
identifier_kinds: &["identifier"],
declared_idents: rust_declared_idents,
},
BindingTable {
languages: &[Language::C, Language::Cpp],
scope_kinds: &[
"compound_statement",
"function_definition",
"for_statement",
"for_range_loop",
"lambda_expression",
],
identifier_kinds: &["identifier"],
declared_idents: c_declared_idents,
},
];
fn rust_declared_idents(node: Node<'_>) -> Vec<Node<'_>> {
let mut out = Vec::new();
match node.kind() {
"let_declaration" | "for_expression" => {
if let Some(pattern) = node.child_by_field_name("pattern") {
collect_pattern_idents(pattern, &mut out);
}
}
"parameter" | "closure_parameters" => {
if let Some(pattern) = node.child_by_field_name("pattern") {
collect_pattern_idents(pattern, &mut out);
} else {
collect_pattern_idents(node, &mut out);
}
}
_ => {}
}
out
}
fn collect_pattern_idents<'tree>(node: Node<'tree>, out: &mut Vec<Node<'tree>>) {
if node.kind() == "identifier" {
out.push(node);
return;
}
if matches!(node.kind(), "scoped_identifier" | "type_identifier") {
return;
}
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
collect_pattern_idents(child, out);
}
}
fn c_declared_idents(node: Node<'_>) -> Vec<Node<'_>> {
let mut out = Vec::new();
match node.kind() {
"declaration" | "parameter_declaration" => {
let mut cursor = node.walk();
for child in node.children_by_field_name("declarator", &mut cursor) {
if let Some(ident) = c_declarator_ident(child) {
out.push(ident);
}
}
}
_ => {}
}
out
}
fn c_declarator_ident(node: Node<'_>) -> Option<Node<'_>> {
match node.kind() {
"identifier" => Some(node),
"function_declarator" => None,
_ => c_declarator_ident(node.child_by_field_name("declarator")?),
}
}