use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum HitCountCondition {
Equal(u32),
GreaterThan(u32),
Multiple(u32),
}
impl HitCountCondition {
pub fn should_break(&self, hit_count: u32) -> bool {
match self {
HitCountCondition::Equal(n) => hit_count == *n,
HitCountCondition::GreaterThan(n) => hit_count > *n,
HitCountCondition::Multiple(n) => *n > 0 && hit_count % *n == 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConditionalBreakpoint {
pub id: String,
pub file: String,
pub line: u32,
pub column: Option<u32>,
pub condition: String,
pub hit_count_condition: Option<HitCountCondition>,
pub log_message: Option<String>,
pub enabled: bool,
pub hit_count: u32,
}
impl ConditionalBreakpoint {
pub fn new(id: String, file: String, line: u32, condition: String) -> Self {
Self {
id,
file,
line,
column: None,
condition,
hit_count_condition: None,
log_message: None,
enabled: true,
hit_count: 0,
}
}
pub fn new_logpoint(id: String, file: String, line: u32, message: String) -> Self {
Self {
id,
file,
line,
column: None,
condition: String::new(),
hit_count_condition: None,
log_message: Some(message),
enabled: true,
hit_count: 0,
}
}
pub fn is_logpoint(&self) -> bool {
self.log_message.is_some()
}
pub fn increment_hit_count(&mut self) {
self.hit_count += 1;
}
pub fn should_break(&self) -> bool {
if !self.enabled {
return false;
}
if self.is_logpoint() {
return false;
}
if let Some(ref condition) = self.hit_count_condition {
return condition.should_break(self.hit_count);
}
true
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExceptionBreakpoint {
pub break_on_caught: bool,
pub break_on_uncaught: bool,
pub exception_filters: Vec<String>,
}
impl Default for ExceptionBreakpoint {
fn default() -> Self {
Self {
break_on_caught: false,
break_on_uncaught: true,
exception_filters: vec![],
}
}
}
impl ExceptionBreakpoint {
pub fn should_break(&self, exception_type: &str, is_caught: bool) -> bool {
if !self.exception_filters.is_empty() {
if !self.exception_filters.iter().any(|f| exception_type.contains(f)) {
return false;
}
}
if is_caught {
self.break_on_caught
} else {
self.break_on_uncaught
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ScopeType {
Global,
Local,
Closure,
Block,
With,
Catch,
Module,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableInfo {
pub name: String,
pub value: String,
pub var_type: String,
pub is_readonly: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScopeInfo {
pub scope_type: ScopeType,
pub name: Option<String>,
pub variables: HashMap<String, VariableInfo>,
}
impl ScopeInfo {
pub fn new(scope_type: ScopeType) -> Self {
Self {
scope_type,
name: None,
variables: HashMap::new(),
}
}
pub fn add_variable(&mut self, var: VariableInfo) {
self.variables.insert(var.name.clone(), var);
}
pub fn get_variable(&self, name: &str) -> Option<&VariableInfo> {
self.variables.get(name)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnhancedStackFrame {
pub index: u32,
pub function_name: String,
pub file_path: String,
pub line: u32,
pub column: u32,
pub is_async: bool,
pub is_promise: bool,
pub is_constructor: bool,
pub scope_chain: Vec<ScopeInfo>,
pub this_value: Option<String>,
pub return_value: Option<String>,
}
impl EnhancedStackFrame {
pub fn new(index: u32, function_name: String, file_path: String, line: u32, column: u32) -> Self {
Self {
index,
function_name,
file_path,
line,
column,
is_async: false,
is_promise: false,
is_constructor: false,
scope_chain: vec![],
this_value: None,
return_value: None,
}
}
pub fn location_string(&self) -> String {
format!("{}:{}:{}", self.file_path, self.line, self.column)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AsyncStackTrace {
pub sync_frames: Vec<EnhancedStackFrame>,
pub async_parent: Option<Box<AsyncStackTrace>>,
pub description: String,
}
impl AsyncStackTrace {
pub fn new(description: String) -> Self {
Self {
sync_frames: vec![],
async_parent: None,
description,
}
}
pub fn add_frame(&mut self, frame: EnhancedStackFrame) {
self.sync_frames.push(frame);
}
pub fn set_async_parent(&mut self, parent: AsyncStackTrace) {
self.async_parent = Some(Box::new(parent));
}
pub fn total_depth(&self) -> usize {
let mut depth = self.sync_frames.len();
if let Some(ref parent) = self.async_parent {
depth += parent.total_depth();
}
depth
}
pub fn format(&self) -> String {
let mut result = String::new();
for frame in &self.sync_frames {
result.push_str(&format!(
" at {} ({})\n",
frame.function_name,
frame.location_string()));
}
if let Some(ref parent) = self.async_parent {
result.push_str(&format!("--- {} ---\n", parent.description));
result.push_str(&parent.format());
}
result
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileSample {
pub timestamp_us: u64,
pub stack_frames: Vec<String>,
pub cpu_time_us: u64,
pub memory_bytes: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HotSpot {
pub function_name: String,
pub file_path: String,
pub line: u32,
pub hit_count: u64,
pub total_time_us: u64,
pub percentage: f64,
}
#[derive(Debug)]
pub struct DebuggerProfiler {
pub enabled: bool,
pub sample_interval_us: u32,
samples: Arc<Mutex<Vec<ProfileSample>>>,
hot_spots: Arc<Mutex<Vec<HotSpot>>>,
}
impl Clone for DebuggerProfiler {
fn clone(&self) -> Self {
Self {
enabled: self.enabled,
sample_interval_us: self.sample_interval_us,
samples: Arc::new(Mutex::new(Vec::new())),
hot_spots: Arc::new(Mutex::new(Vec::new())),
}
}
}
impl DebuggerProfiler {
pub fn new(sample_interval_us: u32) -> Self {
Self {
enabled: false,
sample_interval_us,
samples: Arc::new(Mutex::new(Vec::new())),
hot_spots: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn start(&mut self) {
self.enabled = true;
self.samples.lock().unwrap().clear();
}
pub fn stop(&mut self) {
self.enabled = false;
}
pub fn add_sample(&self, sample: ProfileSample) {
if self.enabled {
self.samples.lock().unwrap().push(sample);
}
}
pub fn sample_count(&self) -> usize {
self.samples.lock().unwrap().len()
}
pub fn analyze_hot_spots(&self) -> Vec<HotSpot> {
let samples: _ = self.samples.lock().unwrap();
let mut function_times: HashMap<String, (u64, u64)> = HashMap::new();
let total_time: u64 = samples.iter().map(|s| s.cpu_time_us).sum();
for sample in samples.iter() {
if let Some(top_frame) = sample.stack_frames.first() {
let entry: _ = function_times.entry(top_frame.clone()).or_insert((0, 0));
entry.0 += 1;
entry.1 += sample.cpu_time_us;
}
}
let mut hot_spots: Vec<HotSpot> = function_times
.iter()
.map(|(name, (count, time))| HotSpot {
function_name: name.clone(),
file_path: String::new(),
line: 0,
hit_count: *count,
total_time_us: *time,
percentage: if total_time > 0 {
(*time as f64 / total_time as f64) * 100.0
} else {
0.0
},
})
.collect();
hot_spots.sort_by(|a, b| b.total_time_us.cmp(&a.total_time_us));
*self.hot_spots.lock().unwrap() = hot_spots.clone();
hot_spots
}
pub fn get_hot_spots(&self) -> Vec<HotSpot> {
self.hot_spots.lock().unwrap().clone()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MappingSegment {
pub generated_line: u32,
pub generated_column: u32,
pub original_line: u32,
pub original_column: u32,
pub source_index: usize,
pub name_index: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OriginalLocation {
pub source: Option<String>,
pub line: u32,
pub column: u32,
pub name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GeneratedLocation {
pub line: u32,
pub column: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceMap {
pub version: u32,
pub sources: Vec<String>,
pub names: Vec<String>,
pub mappings: Vec<MappingSegment>,
pub source_root: Option<String>,
}
impl Default for SourceMap {
fn default() -> Self {
Self::new()
}
}
impl SourceMap {
pub fn new() -> Self {
Self {
version: 3,
sources: Vec::new(),
names: Vec::new(),
mappings: Vec::new(),
source_root: None,
}
}
pub fn add_source(&mut self, source: String) -> usize {
let index: _ = self.sources.len();
self.sources.push(source);
index
}
pub fn add_name(&mut self, name: String) -> usize {
let index: _ = self.names.len();
self.names.push(name);
index
}
pub fn add_mapping(&mut self, segment: MappingSegment) {
self.mappings.push(segment);
}
pub fn find_original_location(
&self,
generated_line: u32,
generated_column: u32,
) -> Option<OriginalLocation> {
let mut best_match: Option<&MappingSegment> = None;
for segment in &self.mappings {
if segment.generated_line == generated_line
&& segment.generated_column <= generated_column
{
match best_match {
None => best_match = Some(segment),
Some(prev) if segment.generated_column > prev.generated_column => {
best_match = Some(segment);
}
_ => {}
}
}
}
best_match.map(|segment| OriginalLocation {
source: self.sources.get(segment.source_index).cloned(),
line: segment.original_line,
column: segment.original_column,
name: segment.name_index.and_then(|i| self.names.get(i).cloned()),
})
}
pub fn find_generated_location(
&self,
source: &str,
original_line: u32,
original_column: u32,
) -> Option<GeneratedLocation> {
let source_index: _ = self.sources.iter().position(|s| s == source)?;
let mut best_match: Option<&MappingSegment> = None;
for segment in &self.mappings {
if segment.source_index == source_index
&& segment.original_line == original_line
&& segment.original_column <= original_column
{
match best_match {
None => best_match = Some(segment),
Some(prev) if segment.original_column > prev.original_column => {
best_match = Some(segment);
}
_ => {}
}
}
}
best_match.map(|segment| GeneratedLocation {
line: segment.generated_line,
column: segment.generated_column,
})
}
}
#[derive(Debug, Clone, Default)]
pub struct SourceMapManager {
maps: HashMap<String, SourceMap>,
}
impl SourceMapManager {
pub fn new() -> Self {
Self {
maps: HashMap::new(),
}
}
pub fn load(&mut self, generated_file: &str, source_map: SourceMap) {
self.maps.insert(generated_file.to_string(), source_map);
}
pub fn unload(&mut self, generated_file: &str) -> Option<SourceMap> {
self.maps.remove(generated_file)
}
pub fn translate(
&self,
generated_file: &str,
line: u32,
column: u32,
) -> Option<OriginalLocation> {
self.maps
.get(generated_file)
.and_then(|sm| sm.find_original_location(line, column))
}
pub fn count(&self) -> usize {
self.maps.len()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DebugProtocol {
ChromeDevTools,
DebugAdapterProtocol,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoteDebugConfig {
pub host: String,
pub port: u16,
pub protocol: DebugProtocol,
pub auth_token: Option<String>,
pub tls_enabled: bool,
}
impl Default for RemoteDebugConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 9229,
protocol: DebugProtocol::ChromeDevTools,
auth_token: None,
tls_enabled: false,
}
}
}
#[derive(Debug, Clone)]
pub struct RemoteDebugSession {
pub config: RemoteDebugConfig,
pub connected: bool,
pub session_id: String,
}
impl RemoteDebugSession {
pub fn new(config: RemoteDebugConfig) -> Self {
Self {
config,
connected: false,
session_id: uuid::Uuid::new_v4().to_string(),
}
}
pub fn connect(&mut self) -> Result<(), String> {
self.connected = true;
Ok(())
}
pub fn disconnect(&mut self) {
self.connected = false;
}
pub fn get_connection_url(&self) -> String {
let protocol: _ = if self.config.tls_enabled { "wss" } else { "ws" };
format!(
"{}://{}:{}",
protocol, self.config.host, self.config.port
)
}
pub fn get_devtools_url(&self) -> String {
format!(
"devtools://devtools/bundled/js_app.html?{}://{}:{}",
if self.config.tls_enabled { "wss" } else { "ws" },
self.config.host,
self.config.port
)
}
}
#[derive(Debug)]
pub struct EnhancedDebugger {
breakpoints: Vec<ConditionalBreakpoint>,
exception_breakpoints: ExceptionBreakpoint,
profiler: DebuggerProfiler,
source_maps: SourceMapManager,
remote_session: Option<RemoteDebugSession>,
current_stack: Option<AsyncStackTrace>,
}
impl Clone for EnhancedDebugger {
fn clone(&self) -> Self {
Self {
breakpoints: self.breakpoints.clone(),
exception_breakpoints: self.exception_breakpoints.clone(),
profiler: self.profiler.clone(),
source_maps: self.source_maps.clone(),
remote_session: self.remote_session.clone(),
current_stack: self.current_stack.clone(),
}
}
}
impl Default for EnhancedDebugger {
fn default() -> Self {
Self::new()
}
}
impl EnhancedDebugger {
pub fn new() -> Self {
Self {
breakpoints: Vec::new(),
exception_breakpoints: ExceptionBreakpoint::default(),
profiler: DebuggerProfiler::new(1000),
source_maps: SourceMapManager::new(),
remote_session: None,
current_stack: None,
}
}
pub fn add_breakpoint(&mut self, bp: ConditionalBreakpoint) -> &ConditionalBreakpoint {
self.breakpoints.push(bp);
self.breakpoints.last().unwrap()
}
pub fn remove_breakpoint(&mut self, id: &str) -> bool {
let initial_len: _ = self.breakpoints.len();
self.breakpoints.retain(|bp| bp.id != id);
self.breakpoints.len() < initial_len
}
pub fn get_breakpoint(&self, id: &str) -> Option<&ConditionalBreakpoint> {
self.breakpoints.iter().find(|bp| bp.id == id)
}
pub fn get_breakpoint_mut(&mut self, id: &str) -> Option<&mut ConditionalBreakpoint> {
self.breakpoints.iter_mut().find(|bp| bp.id == id)
}
pub fn get_all_breakpoints(&self) -> &[ConditionalBreakpoint] {
&self.breakpoints
}
pub fn find_breakpoints(&self, file: &str, line: u32) -> Vec<&ConditionalBreakpoint> {
self.breakpoints
.iter()
.filter(|bp| bp.file == file && bp.line == line)
.collect()
}
pub fn set_exception_breakpoints(&mut self, config: ExceptionBreakpoint) {
self.exception_breakpoints = config;
}
pub fn get_exception_breakpoints(&self) -> &ExceptionBreakpoint {
&self.exception_breakpoints
}
pub fn load_source_map(&mut self, file: &str, source_map: SourceMap) {
self.source_maps.load(file, source_map);
}
pub fn translate_to_original(
&self,
generated_file: &str,
line: u32,
column: u32,
) -> Option<OriginalLocation> {
self.source_maps.translate(generated_file, line, column)
}
pub fn source_map_count(&self) -> usize {
self.source_maps.count()
}
pub fn start_profiling(&mut self) {
self.profiler.start();
}
pub fn stop_profiling(&mut self) -> Vec<HotSpot> {
self.profiler.stop();
self.profiler.analyze_hot_spots()
}
pub fn add_profile_sample(&self, sample: ProfileSample) {
self.profiler.add_sample(sample);
}
pub fn get_hot_spots(&self) -> Vec<HotSpot> {
self.profiler.get_hot_spots()
}
pub fn set_current_stack(&mut self, stack: AsyncStackTrace) {
self.current_stack = Some(stack);
}
pub fn get_current_stack(&self) -> Option<&AsyncStackTrace> {
self.current_stack.as_ref()
}
pub fn clear_current_stack(&mut self) {
self.current_stack = None;
}
pub fn configure_remote(&mut self, config: RemoteDebugConfig) {
self.remote_session = Some(RemoteDebugSession::new(config));
}
pub fn connect_remote(&mut self) -> Result<(), String> {
match &mut self.remote_session {
Some(session) => session.connect(),
None => Err("No remote session configured".to_string()),
}
}
pub fn disconnect_remote(&mut self) {
if let Some(ref mut session) = self.remote_session {
session.disconnect();
}
}
pub fn get_remote_url(&self) -> Option<String> {
self.remote_session.as_ref().map(|s| s.get_connection_url())
}
pub fn is_remote_connected(&self) -> bool {
self.remote_session
.as_ref()
.map(|s| s.connected)
.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_conditional_breakpoint() {
let mut bp = ConditionalBreakpoint::new(
"bp1".to_string(),
"test.ts".to_string(),
10,
"x > 5".to_string(),
);
assert!(bp.enabled);
assert!(!bp.is_logpoint());
assert!(bp.should_break());
bp.enabled = false;
assert!(!bp.should_break());
}
#[test]
fn test_hit_count_conditions() {
let equal: _ = HitCountCondition::Equal(5);
assert!(!equal.should_break(4));
assert!(equal.should_break(5));
let gt: _ = HitCountCondition::GreaterThan(3);
assert!(!gt.should_break(3));
assert!(gt.should_break(4));
let mult: _ = HitCountCondition::Multiple(3);
assert!(mult.should_break(6));
assert!(!mult.should_break(7));
}
#[test]
fn test_source_map() {
let mut sm = SourceMap::new();
sm.add_source("app.ts".to_string());
sm.add_mapping(MappingSegment {
generated_line: 10,
generated_column: 0,
original_line: 5,
original_column: 0,
source_index: 0,
name_index: None,
});
let loc: _ = sm.find_original_location(10, 0);
assert!(loc.is_some());
assert_eq!(loc.unwrap().line, 5);
}
#[test]
fn test_enhanced_debugger() {
let mut debugger = EnhancedDebugger::new();
debugger.add_breakpoint(ConditionalBreakpoint::new(
"bp1".to_string(),
"app.ts".to_string(),
10,
String::new(),
));
assert_eq!(debugger.get_all_breakpoints().len(), 1);
assert!(debugger.remove_breakpoint("bp1"));
assert_eq!(debugger.get_all_breakpoints().len(), 0);
}
}