#![allow(dead_code)]
use std::collections::HashMap;
use tower_lsp::lsp_types::Range;
use super::SymbolId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeKind {
Global,
Function,
Block,
Class,
ForLoop,
Catch,
Switch,
With,
}
#[derive(Debug, Clone)]
pub struct Scope {
pub id: u32,
pub kind: ScopeKind,
pub parent: Option<u32>,
pub children: Vec<u32>,
pub range: Range,
pub symbols: HashMap<String, SymbolId>,
pub type_symbols: HashMap<String, SymbolId>,
}
impl Scope {
pub fn new(id: u32, kind: ScopeKind, parent: Option<u32>, range: Range) -> Self {
Self {
id,
kind,
parent,
children: Vec::new(),
range,
symbols: HashMap::new(),
type_symbols: HashMap::new(),
}
}
pub fn add_symbol(&mut self, name: String, symbol_id: SymbolId) {
self.symbols.insert(name, symbol_id);
}
pub fn add_type_symbol(&mut self, name: String, symbol_id: SymbolId) {
self.type_symbols.insert(name, symbol_id);
}
pub fn lookup_local(&self, name: &str) -> Option<SymbolId> {
self.symbols.get(name).copied()
}
pub fn lookup_type_local(&self, name: &str) -> Option<SymbolId> {
self.type_symbols.get(name).copied()
}
pub fn allows_var_hoisting(&self) -> bool {
match self.kind {
ScopeKind::Global | ScopeKind::Function => false,
_ => true,
}
}
pub fn contains_position(&self, pos: tower_lsp::lsp_types::Position) -> bool {
pos >= self.range.start && pos <= self.range.end
}
}
#[cfg(test)]
mod tests {
use super::*;
use tower_lsp::lsp_types::Position;
fn create_test_range() -> Range {
Range {
start: Position::new(0, 0),
end: Position::new(10, 0),
}
}
#[test]
fn test_scope_kind() {
assert_eq!(ScopeKind::Global, ScopeKind::Global);
assert_ne!(ScopeKind::Global, ScopeKind::Function);
}
#[test]
fn test_scope_new() {
let range = create_test_range();
let scope = Scope::new(1, ScopeKind::Function, Some(0), range);
assert_eq!(scope.id, 1);
assert_eq!(scope.kind, ScopeKind::Function);
assert_eq!(scope.parent, Some(0));
assert!(scope.children.is_empty());
assert!(scope.symbols.is_empty());
assert!(scope.type_symbols.is_empty());
}
#[test]
fn test_scope_new_global() {
let range = create_test_range();
let scope = Scope::new(0, ScopeKind::Global, None, range);
assert_eq!(scope.id, 0);
assert_eq!(scope.kind, ScopeKind::Global);
assert!(scope.parent.is_none());
}
#[test]
fn test_scope_add_symbol() {
let range = create_test_range();
let mut scope = Scope::new(0, ScopeKind::Global, None, range);
let symbol_id = SymbolId::new(1);
scope.add_symbol("test".to_string(), symbol_id);
assert!(scope.symbols.contains_key("test"));
assert_eq!(scope.symbols.get("test").copied(), Some(symbol_id));
}
#[test]
fn test_scope_add_type_symbol() {
let range = create_test_range();
let mut scope = Scope::new(0, ScopeKind::Global, None, range);
let symbol_id = SymbolId::new(1);
scope.add_type_symbol("MyType".to_string(), symbol_id);
assert!(scope.type_symbols.contains_key("MyType"));
assert_eq!(scope.type_symbols.get("MyType").copied(), Some(symbol_id));
}
#[test]
fn test_scope_lookup_local() {
let range = create_test_range();
let mut scope = Scope::new(0, ScopeKind::Global, None, range);
let symbol_id = SymbolId::new(1);
scope.add_symbol("x".to_string(), symbol_id);
assert_eq!(scope.lookup_local("x"), Some(symbol_id));
assert_eq!(scope.lookup_local("y"), None);
}
#[test]
fn test_scope_lookup_type_local() {
let range = create_test_range();
let mut scope = Scope::new(0, ScopeKind::Global, None, range);
let symbol_id = SymbolId::new(1);
scope.add_type_symbol("User".to_string(), symbol_id);
assert_eq!(scope.lookup_type_local("User"), Some(symbol_id));
assert_eq!(scope.lookup_type_local("Person"), None);
}
#[test]
fn test_scope_allows_var_hoisting() {
let range = create_test_range();
let global = Scope::new(0, ScopeKind::Global, None, range);
assert!(!global.allows_var_hoisting());
let function = Scope::new(1, ScopeKind::Function, Some(0), range);
assert!(!function.allows_var_hoisting());
let block = Scope::new(2, ScopeKind::Block, Some(1), range);
assert!(block.allows_var_hoisting());
let for_loop = Scope::new(3, ScopeKind::ForLoop, Some(1), range);
assert!(for_loop.allows_var_hoisting()); }
#[test]
fn test_scope_contains_position() {
let range = Range {
start: Position::new(5, 0),
end: Position::new(10, 0),
};
let scope = Scope::new(0, ScopeKind::Function, None, range);
assert!(scope.contains_position(Position::new(7, 5)));
assert!(scope.contains_position(Position::new(5, 0)));
assert!(scope.contains_position(Position::new(10, 0)));
assert!(!scope.contains_position(Position::new(4, 10)));
assert!(!scope.contains_position(Position::new(11, 0)));
}
#[test]
fn test_scope_multiple_symbols() {
let range = create_test_range();
let mut scope = Scope::new(0, ScopeKind::Global, None, range);
scope.add_symbol("a".to_string(), SymbolId::new(1));
scope.add_symbol("b".to_string(), SymbolId::new(2));
scope.add_symbol("c".to_string(), SymbolId::new(3));
assert_eq!(scope.symbols.len(), 3);
assert_eq!(scope.lookup_local("a"), Some(SymbolId::new(1)));
assert_eq!(scope.lookup_local("b"), Some(SymbolId::new(2)));
assert_eq!(scope.lookup_local("c"), Some(SymbolId::new(3)));
}
#[test]
fn test_scope_symbol_override() {
let range = create_test_range();
let mut scope = Scope::new(0, ScopeKind::Global, None, range);
scope.add_symbol("x".to_string(), SymbolId::new(1));
scope.add_symbol("x".to_string(), SymbolId::new(2));
assert_eq!(scope.lookup_local("x"), Some(SymbolId::new(2)));
}
}