Skip to main content

depyler_tooling/
ide.rs

1//! IDE integration support for Depyler
2//!
3//! This module provides IDE integration features including:
4//! - Symbol indexing for navigation
5//! - Hover information generation
6//! - Code completion suggestions
7//! - Diagnostic reporting
8
9use depyler_hir::error::ErrorKind;
10use depyler_hir::hir::{HirClass, HirFunction, HirMethod, HirModule};
11use rustpython_parser::text_size::{TextRange, TextSize};
12use std::collections::HashMap;
13
14/// Symbol information for IDE features
15#[derive(Debug, Clone)]
16pub struct Symbol {
17    pub name: String,
18    pub kind: SymbolKind,
19    pub range: TextRange,
20    pub detail: Option<String>,
21    pub documentation: Option<String>,
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum SymbolKind {
26    Function,
27    Class,
28    Method,
29    Variable,
30    Parameter,
31    Field,
32    Module,
33}
34
35/// IDE integration provider
36#[derive(Default)]
37pub struct IdeIntegration {
38    symbols: HashMap<String, Vec<Symbol>>,
39    diagnostics: Vec<Diagnostic>,
40}
41
42#[derive(Debug, Clone)]
43pub struct Diagnostic {
44    pub range: TextRange,
45    pub severity: DiagnosticSeverity,
46    pub message: String,
47    pub code: Option<String>,
48    pub source: String,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq)]
52pub enum DiagnosticSeverity {
53    Error,
54    Warning,
55    Information,
56    Hint,
57}
58
59impl IdeIntegration {
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    /// Index symbols from HIR for navigation
65    pub fn index_symbols(&mut self, module: &HirModule, source: &str) {
66        // Index functions
67        for func in &module.functions {
68            self.index_function(func, source);
69        }
70
71        // Index classes
72        for class in &module.classes {
73            self.index_class(class, source);
74        }
75    }
76
77    fn index_function(&mut self, func: &HirFunction, _source: &str) {
78        // Create a placeholder range (would need actual source positions)
79        let range = TextRange::new(TextSize::from(0), TextSize::from(100));
80
81        let mut params = Vec::new();
82        for param in &func.params {
83            params.push(format!("{}: {:?}", param.name, param.ty));
84        }
85        let detail = format!(
86            "fn {}({}) -> {:?}",
87            func.name,
88            params.join(", "),
89            func.ret_type
90        );
91
92        let symbol = Symbol {
93            name: func.name.clone(),
94            kind: SymbolKind::Function,
95            range,
96            detail: Some(detail),
97            documentation: None, // Could extract from docstring if available
98        };
99
100        self.symbols
101            .entry(func.name.clone())
102            .or_default()
103            .push(symbol);
104    }
105
106    fn index_class(&mut self, class: &HirClass, _source: &str) {
107        // Create a placeholder range
108        let range = TextRange::new(TextSize::from(0), TextSize::from(100));
109
110        let symbol = Symbol {
111            name: class.name.clone(),
112            kind: SymbolKind::Class,
113            range,
114            detail: Some(format!("class {}", class.name)),
115            documentation: None,
116        };
117
118        self.symbols
119            .entry(class.name.clone())
120            .or_default()
121            .push(symbol);
122
123        // Index class methods
124        for method in &class.methods {
125            self.index_method(method, &class.name, _source);
126        }
127
128        // Index fields
129        for field in &class.fields {
130            let field_symbol = Symbol {
131                name: field.name.clone(),
132                kind: SymbolKind::Field,
133                range, // Use class range for now
134                detail: Some(format!("{}: {:?}", field.name, field.field_type)),
135                documentation: None,
136            };
137            self.symbols
138                .entry(field.name.clone())
139                .or_default()
140                .push(field_symbol);
141        }
142    }
143
144    fn index_method(&mut self, method: &HirMethod, class_name: &str, _source: &str) {
145        // Create a placeholder range
146        let range = TextRange::new(TextSize::from(0), TextSize::from(100));
147
148        let mut params = Vec::new();
149        for param in &method.params {
150            params.push(format!("{}: {:?}", param.name, param.ty));
151        }
152        let detail = format!(
153            "{}::{}({}) -> {:?}",
154            class_name,
155            method.name,
156            params.join(", "),
157            method.ret_type
158        );
159
160        let symbol = Symbol {
161            name: format!("{}::{}", class_name, method.name),
162            kind: SymbolKind::Method,
163            range,
164            detail: Some(detail),
165            documentation: None,
166        };
167
168        self.symbols
169            .entry(method.name.clone())
170            .or_default()
171            .push(symbol);
172    }
173
174    /// Get symbol at position for hover/goto definition
175    pub fn symbol_at_position(&self, position: TextSize) -> Option<&Symbol> {
176        for symbols in self.symbols.values() {
177            for symbol in symbols {
178                if symbol.range.contains(position) {
179                    return Some(symbol);
180                }
181            }
182        }
183        None
184    }
185
186    /// Find all references to a symbol
187    pub fn find_references(&self, symbol_name: &str) -> Vec<&Symbol> {
188        self.symbols
189            .get(symbol_name)
190            .map(|symbols| symbols.iter().collect())
191            .unwrap_or_default()
192    }
193
194    /// Get completion suggestions at position
195    pub fn completions_at_position(
196        &self,
197        _position: TextSize,
198        prefix: &str,
199    ) -> Vec<CompletionItem> {
200        let mut completions = Vec::new();
201
202        for (name, symbols) in &self.symbols {
203            if name.starts_with(prefix) {
204                for symbol in symbols {
205                    completions.push(CompletionItem {
206                        label: name.clone(),
207                        kind: match symbol.kind {
208                            SymbolKind::Function => CompletionKind::Function,
209                            SymbolKind::Class => CompletionKind::Class,
210                            SymbolKind::Method => CompletionKind::Method,
211                            SymbolKind::Variable => CompletionKind::Variable,
212                            SymbolKind::Parameter => CompletionKind::Variable,
213                            SymbolKind::Field => CompletionKind::Field,
214                            SymbolKind::Module => CompletionKind::Module,
215                        },
216                        detail: symbol.detail.clone(),
217                        documentation: symbol.documentation.clone(),
218                    });
219                }
220            }
221        }
222
223        completions
224    }
225
226    /// Add a diagnostic
227    pub fn add_diagnostic(&mut self, diagnostic: Diagnostic) {
228        self.diagnostics.push(diagnostic);
229    }
230
231    /// Get all diagnostics
232    pub fn diagnostics(&self) -> &[Diagnostic] {
233        &self.diagnostics
234    }
235
236    /// Convert errors to diagnostics
237    pub fn add_error(&mut self, error: &ErrorKind, range: TextRange) {
238        let diagnostic = Diagnostic {
239            range,
240            severity: DiagnosticSeverity::Error,
241            message: error.to_string(),
242            code: None,
243            source: "depyler".to_string(),
244        };
245        self.add_diagnostic(diagnostic);
246    }
247
248    /// Add a warning diagnostic
249    pub fn add_warning(&mut self, message: String, range: TextRange) {
250        let diagnostic = Diagnostic {
251            range,
252            severity: DiagnosticSeverity::Warning,
253            message,
254            code: None,
255            source: "depyler".to_string(),
256        };
257        self.add_diagnostic(diagnostic);
258    }
259}
260
261#[derive(Debug, Clone)]
262pub struct CompletionItem {
263    pub label: String,
264    pub kind: CompletionKind,
265    pub detail: Option<String>,
266    pub documentation: Option<String>,
267}
268
269#[derive(Debug, Clone, Copy, PartialEq)]
270pub enum CompletionKind {
271    Function,
272    Class,
273    Method,
274    Variable,
275    Field,
276    Module,
277}
278
279/// Generate hover information for a symbol
280pub fn generate_hover_info(symbol: &Symbol) -> String {
281    let mut hover = String::new();
282
283    // Add symbol type and signature
284    if let Some(detail) = &symbol.detail {
285        hover.push_str(&format!("```rust\n{}\n```\n\n", detail));
286    }
287
288    // Add documentation if available
289    if let Some(doc) = &symbol.documentation {
290        hover.push_str(doc);
291    }
292
293    hover
294}
295
296/// IDE-specific context extensions
297pub trait IdeContext {
298    fn get_symbol_at(&self, position: TextSize) -> Option<&Symbol>;
299    fn get_completions(&self, position: TextSize, prefix: &str) -> Vec<CompletionItem>;
300    fn get_diagnostics(&self) -> &[Diagnostic];
301}
302
303/// Create IDE integration from transpilation result
304pub fn create_ide_integration(module: &HirModule, source: &str) -> IdeIntegration {
305    let mut ide = IdeIntegration::new();
306    ide.index_symbols(module, source);
307    ide
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313    use depyler_hir::hir::{FunctionProperties, HirClass, HirField, HirMethod, HirParam, Type};
314    use smallvec::smallvec;
315
316    // === Symbol tests ===
317
318    #[test]
319    fn test_symbol_new() {
320        let symbol = Symbol {
321            name: "my_symbol".to_string(),
322            kind: SymbolKind::Function,
323            range: TextRange::new(TextSize::from(0), TextSize::from(50)),
324            detail: Some("fn my_symbol()".to_string()),
325            documentation: Some("A test symbol".to_string()),
326        };
327        assert_eq!(symbol.name, "my_symbol");
328        assert_eq!(symbol.kind, SymbolKind::Function);
329        assert!(symbol.detail.is_some());
330        assert!(symbol.documentation.is_some());
331    }
332
333    #[test]
334    fn test_symbol_clone() {
335        let symbol = Symbol {
336            name: "test".to_string(),
337            kind: SymbolKind::Class,
338            range: TextRange::new(TextSize::from(10), TextSize::from(20)),
339            detail: None,
340            documentation: None,
341        };
342        let cloned = symbol.clone();
343        assert_eq!(cloned.name, symbol.name);
344        assert_eq!(cloned.kind, symbol.kind);
345    }
346
347    #[test]
348    fn test_symbol_debug() {
349        let symbol = Symbol {
350            name: "debug_test".to_string(),
351            kind: SymbolKind::Variable,
352            range: TextRange::new(TextSize::from(0), TextSize::from(10)),
353            detail: None,
354            documentation: None,
355        };
356        let debug = format!("{:?}", symbol);
357        assert!(debug.contains("debug_test"));
358        assert!(debug.contains("Variable"));
359    }
360
361    // === SymbolKind tests ===
362
363    #[test]
364    fn test_symbol_kind_function() {
365        assert_eq!(SymbolKind::Function, SymbolKind::Function);
366        assert_ne!(SymbolKind::Function, SymbolKind::Class);
367    }
368
369    #[test]
370    fn test_symbol_kind_class() {
371        let kind = SymbolKind::Class;
372        assert_eq!(kind.clone(), SymbolKind::Class);
373    }
374
375    #[test]
376    fn test_symbol_kind_method() {
377        let kind = SymbolKind::Method;
378        let debug = format!("{:?}", kind);
379        assert!(debug.contains("Method"));
380    }
381
382    #[test]
383    fn test_symbol_kind_variable() {
384        assert_eq!(SymbolKind::Variable, SymbolKind::Variable);
385    }
386
387    #[test]
388    fn test_symbol_kind_parameter() {
389        assert_eq!(SymbolKind::Parameter, SymbolKind::Parameter);
390    }
391
392    #[test]
393    fn test_symbol_kind_field() {
394        assert_eq!(SymbolKind::Field, SymbolKind::Field);
395    }
396
397    #[test]
398    fn test_symbol_kind_module() {
399        assert_eq!(SymbolKind::Module, SymbolKind::Module);
400    }
401
402    // === DiagnosticSeverity tests ===
403
404    #[test]
405    fn test_diagnostic_severity_error() {
406        assert_eq!(DiagnosticSeverity::Error, DiagnosticSeverity::Error);
407        assert_ne!(DiagnosticSeverity::Error, DiagnosticSeverity::Warning);
408    }
409
410    #[test]
411    fn test_diagnostic_severity_warning() {
412        assert_eq!(DiagnosticSeverity::Warning, DiagnosticSeverity::Warning);
413    }
414
415    #[test]
416    fn test_diagnostic_severity_information() {
417        assert_eq!(
418            DiagnosticSeverity::Information,
419            DiagnosticSeverity::Information
420        );
421    }
422
423    #[test]
424    fn test_diagnostic_severity_hint() {
425        let severity = DiagnosticSeverity::Hint;
426        let cloned = severity;
427        assert_eq!(cloned, DiagnosticSeverity::Hint);
428    }
429
430    // === Diagnostic tests ===
431
432    #[test]
433    fn test_diagnostic_new() {
434        let diag = Diagnostic {
435            range: TextRange::new(TextSize::from(0), TextSize::from(10)),
436            severity: DiagnosticSeverity::Error,
437            message: "Test error".to_string(),
438            code: Some("E0001".to_string()),
439            source: "depyler".to_string(),
440        };
441        assert_eq!(diag.message, "Test error");
442        assert_eq!(diag.severity, DiagnosticSeverity::Error);
443        assert_eq!(diag.code, Some("E0001".to_string()));
444    }
445
446    #[test]
447    fn test_diagnostic_clone() {
448        let diag = Diagnostic {
449            range: TextRange::new(TextSize::from(0), TextSize::from(5)),
450            severity: DiagnosticSeverity::Warning,
451            message: "Warning".to_string(),
452            code: None,
453            source: "test".to_string(),
454        };
455        let cloned = diag.clone();
456        assert_eq!(cloned.message, diag.message);
457        assert_eq!(cloned.severity, diag.severity);
458    }
459
460    #[test]
461    fn test_diagnostic_debug() {
462        let diag = Diagnostic {
463            range: TextRange::new(TextSize::from(0), TextSize::from(1)),
464            severity: DiagnosticSeverity::Information,
465            message: "Info".to_string(),
466            code: None,
467            source: "test".to_string(),
468        };
469        let debug = format!("{:?}", diag);
470        assert!(debug.contains("Information"));
471        assert!(debug.contains("Info"));
472    }
473
474    // === CompletionKind tests ===
475
476    #[test]
477    fn test_completion_kind_function() {
478        assert_eq!(CompletionKind::Function, CompletionKind::Function);
479    }
480
481    #[test]
482    fn test_completion_kind_class() {
483        assert_eq!(CompletionKind::Class, CompletionKind::Class);
484    }
485
486    #[test]
487    fn test_completion_kind_method() {
488        assert_eq!(CompletionKind::Method, CompletionKind::Method);
489    }
490
491    #[test]
492    fn test_completion_kind_variable() {
493        assert_eq!(CompletionKind::Variable, CompletionKind::Variable);
494    }
495
496    #[test]
497    fn test_completion_kind_field() {
498        assert_eq!(CompletionKind::Field, CompletionKind::Field);
499    }
500
501    #[test]
502    fn test_completion_kind_module() {
503        let kind = CompletionKind::Module;
504        let debug = format!("{:?}", kind);
505        assert!(debug.contains("Module"));
506    }
507
508    // === CompletionItem tests ===
509
510    #[test]
511    fn test_completion_item_new() {
512        let item = CompletionItem {
513            label: "my_func".to_string(),
514            kind: CompletionKind::Function,
515            detail: Some("fn my_func()".to_string()),
516            documentation: Some("Does something".to_string()),
517        };
518        assert_eq!(item.label, "my_func");
519        assert_eq!(item.kind, CompletionKind::Function);
520    }
521
522    #[test]
523    fn test_completion_item_clone() {
524        let item = CompletionItem {
525            label: "test".to_string(),
526            kind: CompletionKind::Variable,
527            detail: None,
528            documentation: None,
529        };
530        let cloned = item.clone();
531        assert_eq!(cloned.label, item.label);
532    }
533
534    // === IdeIntegration tests ===
535
536    #[test]
537    fn test_ide_integration_new() {
538        let ide = IdeIntegration::new();
539        assert!(ide.diagnostics.is_empty());
540        assert!(ide.symbols.is_empty());
541    }
542
543    #[test]
544    fn test_ide_integration_default() {
545        let ide = IdeIntegration::default();
546        assert!(ide.diagnostics.is_empty());
547    }
548
549    #[test]
550    fn test_add_diagnostic() {
551        let mut ide = IdeIntegration::new();
552        let diag = Diagnostic {
553            range: TextRange::new(TextSize::from(0), TextSize::from(10)),
554            severity: DiagnosticSeverity::Error,
555            message: "Test".to_string(),
556            code: None,
557            source: "test".to_string(),
558        };
559        ide.add_diagnostic(diag);
560        assert_eq!(ide.diagnostics().len(), 1);
561    }
562
563    #[test]
564    fn test_diagnostics_getter() {
565        let mut ide = IdeIntegration::new();
566        assert!(ide.diagnostics().is_empty());
567
568        ide.add_diagnostic(Diagnostic {
569            range: TextRange::new(TextSize::from(0), TextSize::from(1)),
570            severity: DiagnosticSeverity::Warning,
571            message: "Warn".to_string(),
572            code: None,
573            source: "test".to_string(),
574        });
575        assert_eq!(ide.diagnostics().len(), 1);
576        assert_eq!(ide.diagnostics()[0].message, "Warn");
577    }
578
579    #[test]
580    fn test_add_warning() {
581        let mut ide = IdeIntegration::new();
582        let range = TextRange::new(TextSize::from(5), TextSize::from(15));
583        ide.add_warning("Unused variable".to_string(), range);
584
585        assert_eq!(ide.diagnostics().len(), 1);
586        assert_eq!(ide.diagnostics()[0].severity, DiagnosticSeverity::Warning);
587        assert_eq!(ide.diagnostics()[0].message, "Unused variable");
588        assert_eq!(ide.diagnostics()[0].source, "depyler");
589    }
590
591    #[test]
592    fn test_add_error() {
593        let mut ide = IdeIntegration::new();
594        let range = TextRange::new(TextSize::from(0), TextSize::from(10));
595        let error = depyler_hir::error::ErrorKind::ParseError;
596        ide.add_error(&error, range);
597
598        assert_eq!(ide.diagnostics().len(), 1);
599        assert_eq!(ide.diagnostics()[0].severity, DiagnosticSeverity::Error);
600    }
601
602    #[test]
603    fn test_symbol_at_position_found() {
604        let mut ide = IdeIntegration::new();
605        ide.symbols.insert(
606            "test".to_string(),
607            vec![Symbol {
608                name: "test".to_string(),
609                kind: SymbolKind::Function,
610                range: TextRange::new(TextSize::from(10), TextSize::from(50)),
611                detail: None,
612                documentation: None,
613            }],
614        );
615
616        let symbol = ide.symbol_at_position(TextSize::from(25));
617        assert!(symbol.is_some());
618        assert_eq!(symbol.unwrap().name, "test");
619    }
620
621    #[test]
622    fn test_symbol_at_position_not_found() {
623        let mut ide = IdeIntegration::new();
624        ide.symbols.insert(
625            "test".to_string(),
626            vec![Symbol {
627                name: "test".to_string(),
628                kind: SymbolKind::Function,
629                range: TextRange::new(TextSize::from(10), TextSize::from(20)),
630                detail: None,
631                documentation: None,
632            }],
633        );
634
635        let symbol = ide.symbol_at_position(TextSize::from(100));
636        assert!(symbol.is_none());
637    }
638
639    #[test]
640    fn test_find_references_found() {
641        let mut ide = IdeIntegration::new();
642        ide.symbols.insert(
643            "my_func".to_string(),
644            vec![
645                Symbol {
646                    name: "my_func".to_string(),
647                    kind: SymbolKind::Function,
648                    range: TextRange::new(TextSize::from(0), TextSize::from(10)),
649                    detail: None,
650                    documentation: None,
651                },
652                Symbol {
653                    name: "my_func".to_string(),
654                    kind: SymbolKind::Function,
655                    range: TextRange::new(TextSize::from(50), TextSize::from(60)),
656                    detail: None,
657                    documentation: None,
658                },
659            ],
660        );
661
662        let refs = ide.find_references("my_func");
663        assert_eq!(refs.len(), 2);
664    }
665
666    #[test]
667    fn test_find_references_not_found() {
668        let ide = IdeIntegration::new();
669        let refs = ide.find_references("nonexistent");
670        assert!(refs.is_empty());
671    }
672
673    #[test]
674    fn test_completions_empty_prefix() {
675        let mut ide = IdeIntegration::new();
676        ide.symbols.insert(
677            "abc".to_string(),
678            vec![Symbol {
679                name: "abc".to_string(),
680                kind: SymbolKind::Variable,
681                range: TextRange::new(TextSize::from(0), TextSize::from(5)),
682                detail: None,
683                documentation: None,
684            }],
685        );
686
687        let completions = ide.completions_at_position(TextSize::from(0), "");
688        // Empty prefix matches nothing since "abc".starts_with("") is true
689        assert!(!completions.is_empty());
690    }
691
692    #[test]
693    fn test_completions_no_match() {
694        let mut ide = IdeIntegration::new();
695        ide.symbols.insert(
696            "foo".to_string(),
697            vec![Symbol {
698                name: "foo".to_string(),
699                kind: SymbolKind::Function,
700                range: TextRange::new(TextSize::from(0), TextSize::from(5)),
701                detail: None,
702                documentation: None,
703            }],
704        );
705
706        let completions = ide.completions_at_position(TextSize::from(0), "bar");
707        assert!(completions.is_empty());
708    }
709
710    #[test]
711    fn test_index_class_with_methods() {
712        let mut ide = IdeIntegration::new();
713
714        let class = HirClass {
715            name: "MyClass".to_string(),
716            fields: vec![HirField {
717                name: "value".to_string(),
718                field_type: Type::Int,
719                default_value: None,
720                is_class_var: false,
721            }],
722            methods: vec![HirMethod {
723                name: "get_value".to_string(),
724                params: smallvec![],
725                ret_type: Type::Int,
726                body: vec![],
727                docstring: None,
728                is_static: false,
729                is_classmethod: false,
730                is_property: false,
731                is_async: false,
732            }],
733            base_classes: vec![],
734            is_dataclass: false,
735            docstring: None,
736            type_params: vec![],
737        };
738
739        let module = HirModule {
740            functions: vec![],
741            classes: vec![class],
742            imports: vec![],
743            type_aliases: vec![],
744            protocols: vec![],
745            constants: vec![],
746            top_level_stmts: vec![],
747        };
748
749        ide.index_symbols(&module, "");
750
751        // Should have class, method, and field indexed
752        assert!(ide.symbols.contains_key("MyClass"));
753        assert!(ide.symbols.contains_key("get_value"));
754        assert!(ide.symbols.contains_key("value"));
755    }
756
757    #[test]
758    fn test_create_ide_integration() {
759        let module = HirModule {
760            functions: vec![HirFunction {
761                name: "main".to_string(),
762                params: smallvec![],
763                ret_type: Type::None,
764                body: vec![],
765                annotations: depyler_annotations::TranspilationAnnotations::default(),
766                docstring: None,
767                properties: FunctionProperties::default(),
768            }],
769            classes: vec![],
770            imports: vec![],
771            type_aliases: vec![],
772            protocols: vec![],
773            constants: vec![],
774            top_level_stmts: vec![],
775        };
776
777        let ide = create_ide_integration(&module, "");
778        assert!(ide.symbols.contains_key("main"));
779    }
780
781    // === generate_hover_info tests ===
782
783    #[test]
784    fn test_generate_hover_info_with_detail_only() {
785        let symbol = Symbol {
786            name: "func".to_string(),
787            kind: SymbolKind::Function,
788            range: TextRange::new(TextSize::from(0), TextSize::from(10)),
789            detail: Some("fn func() -> i32".to_string()),
790            documentation: None,
791        };
792
793        let hover = generate_hover_info(&symbol);
794        assert!(hover.contains("```rust"));
795        assert!(hover.contains("fn func() -> i32"));
796        assert!(hover.contains("```"));
797    }
798
799    #[test]
800    fn test_generate_hover_info_with_doc_only() {
801        let symbol = Symbol {
802            name: "var".to_string(),
803            kind: SymbolKind::Variable,
804            range: TextRange::new(TextSize::from(0), TextSize::from(5)),
805            detail: None,
806            documentation: Some("A counter variable".to_string()),
807        };
808
809        let hover = generate_hover_info(&symbol);
810        assert!(hover.contains("A counter variable"));
811    }
812
813    #[test]
814    fn test_generate_hover_info_empty() {
815        let symbol = Symbol {
816            name: "empty".to_string(),
817            kind: SymbolKind::Variable,
818            range: TextRange::new(TextSize::from(0), TextSize::from(5)),
819            detail: None,
820            documentation: None,
821        };
822
823        let hover = generate_hover_info(&symbol);
824        assert!(hover.is_empty());
825    }
826
827    // === Original tests ===
828
829    #[test]
830    fn test_symbol_indexing() {
831        let mut ide = IdeIntegration::new();
832
833        let func = HirFunction {
834            name: "test_func".to_string(),
835            params: smallvec![HirParam::new("x".to_string(), Type::Int)],
836            ret_type: Type::Int,
837            body: vec![],
838            annotations: depyler_annotations::TranspilationAnnotations::default(),
839            docstring: None,
840            properties: FunctionProperties::default(),
841        };
842
843        let module = HirModule {
844            functions: vec![func],
845            classes: vec![],
846            imports: vec![],
847            type_aliases: vec![],
848            protocols: vec![],
849            constants: vec![],
850            top_level_stmts: vec![],
851        };
852
853        ide.index_symbols(&module, "def test_func(x: int) -> int:\n    pass");
854
855        assert_eq!(ide.symbols.len(), 1);
856        assert!(ide.symbols.contains_key("test_func"));
857
858        let symbols = &ide.symbols["test_func"];
859        assert_eq!(symbols.len(), 1);
860        assert_eq!(symbols[0].kind, SymbolKind::Function);
861    }
862
863    #[test]
864    fn test_hover_generation() {
865        let symbol = Symbol {
866            name: "my_func".to_string(),
867            kind: SymbolKind::Function,
868            range: TextRange::new(TextSize::from(0), TextSize::from(10)),
869            detail: Some("fn my_func(x: int) -> int".to_string()),
870            documentation: Some("Calculates something".to_string()),
871        };
872
873        let hover = generate_hover_info(&symbol);
874        assert!(hover.contains("```rust"));
875        assert!(hover.contains("fn my_func(x: int) -> int"));
876        assert!(hover.contains("Calculates something"));
877    }
878
879    #[test]
880    fn test_completions() {
881        let mut ide = IdeIntegration::new();
882
883        // Add some test symbols
884        ide.symbols.insert(
885            "test_func".to_string(),
886            vec![Symbol {
887                name: "test_func".to_string(),
888                kind: SymbolKind::Function,
889                range: TextRange::new(TextSize::from(0), TextSize::from(10)),
890                detail: Some("fn test_func()".to_string()),
891                documentation: None,
892            }],
893        );
894
895        ide.symbols.insert(
896            "test_class".to_string(),
897            vec![Symbol {
898                name: "test_class".to_string(),
899                kind: SymbolKind::Class,
900                range: TextRange::new(TextSize::from(20), TextSize::from(30)),
901                detail: Some("class test_class".to_string()),
902                documentation: None,
903            }],
904        );
905
906        let completions = ide.completions_at_position(TextSize::from(0), "test");
907        assert_eq!(completions.len(), 2);
908
909        let completions = ide.completions_at_position(TextSize::from(0), "test_f");
910        assert_eq!(completions.len(), 1);
911        assert_eq!(completions[0].label, "test_func");
912    }
913}