Skip to main content

bkmr_lsp/
lib.rs

1pub mod backend;
2pub mod domain;
3pub mod repositories;
4pub mod services;
5
6pub use backend::*;
7
8#[cfg(test)]
9mod tests {
10    use crate::domain::{BkmrSnippet, LanguageRegistry, SnippetFilter};
11    use crate::services::LanguageTranslator;
12    use tower_lsp::lsp_types::Url;
13
14    #[test]
15    fn given_universal_and_regular_snippets_when_checking_tags_then_detects_universal_correctly() {
16        // Arrange
17        let universal_snippet = BkmrSnippet {
18            id: 1,
19            title: "Test Universal Snippet".to_string(),
20            url: "// This is a test".to_string(),
21            description: "Test description".to_string(),
22            tags: vec!["universal".to_string(), "test".to_string()],
23            access_count: 0,
24        };
25
26        let regular_snippet = BkmrSnippet {
27            id: 2,
28            title: "Test Regular Snippet".to_string(),
29            url: "// This is a test".to_string(),
30            description: "Test description".to_string(),
31            tags: vec!["rust".to_string(), "test".to_string()],
32            access_count: 0,
33        };
34
35        // Act & Assert
36        assert!(universal_snippet.tags.contains(&"universal".to_string()));
37        assert!(!regular_snippet.tags.contains(&"universal".to_string()));
38    }
39
40    #[test]
41    fn given_rust_line_comments_when_translating_to_python_then_converts_to_hash_comments() {
42        // Arrange
43        let uri = Url::parse("file:///test/example.py").expect("valid test URI");
44        let rust_content = r#"// This is a line comment
45    // Indented comment
46let x = 5; // End of line comment"#;
47
48        // Act
49        let python_result =
50            LanguageTranslator::translate_rust_patterns(rust_content, "python", &uri)
51                .expect("translation should succeed");
52
53        // Assert
54        assert!(python_result.contains("# This is a line comment"));
55        assert!(python_result.contains("    # Indented comment"));
56        assert!(python_result.contains("let x = 5; # End of line comment"));
57
58        // Test with HTML (no line comments)
59        let html_result = LanguageTranslator::translate_rust_patterns(rust_content, "html", &uri)
60            .expect("translation should succeed");
61        assert!(html_result.contains("<!-- This is a line comment -->"));
62        assert!(html_result.contains("  <!-- Indented comment -->")); // HTML uses 2 spaces
63        assert!(html_result.contains("let x = 5; <!-- End of line comment -->"));
64    }
65
66    #[test]
67    fn test_rust_block_comment_translation() {
68        let uri = Url::parse("file:///test/example.py").expect("valid test URI");
69
70        let rust_content = r#"/* This is a block comment */
71/*
72Multi-line
73block comment
74*/"#;
75
76        let python_result =
77            LanguageTranslator::translate_rust_patterns(rust_content, "python", &uri)
78                .expect("translation should succeed");
79        assert!(python_result.contains("\"\"\" This is a block comment \"\"\""));
80        assert!(python_result.contains("\"\"\"\nMulti-line\nblock comment\n\"\"\""));
81
82        let html_result = LanguageTranslator::translate_rust_patterns(rust_content, "html", &uri)
83            .expect("translation should succeed");
84        assert!(html_result.contains("<!-- This is a block comment -->"));
85        assert!(html_result.contains("<!--\nMulti-line\nblock comment\n-->"));
86    }
87
88    #[test]
89    fn test_rust_indentation_translation() {
90        let uri = Url::parse("file:///test/example.go").expect("valid Go test URI");
91
92        let rust_content = r#"fn example() {
93    let x = 5;
94        let y = 10;
95            let z = 15;
96}"#;
97
98        // Go uses tabs
99        let go_result = LanguageTranslator::translate_rust_patterns(rust_content, "go", &uri)
100            .expect("translation should succeed");
101        assert!(go_result.contains("fn example() {"));
102        assert!(go_result.contains("\tlet x = 5;"));
103        assert!(go_result.contains("\t\tlet y = 10;"));
104        assert!(go_result.contains("\t\t\tlet z = 15;"));
105
106        // JavaScript uses 2 spaces
107        let js_result =
108            LanguageTranslator::translate_rust_patterns(rust_content, "javascript", &uri)
109                .expect("translation should succeed");
110        assert!(js_result.contains("  let x = 5;"));
111        assert!(js_result.contains("    let y = 10;"));
112        assert!(js_result.contains("      let z = 15;"));
113    }
114
115    #[test]
116    fn test_filename_replacement() {
117        let uri = Url::parse("file:///path/to/example.rs").expect("valid Rust test URI");
118
119        let content = "// File: {{ filename }}";
120        let result = LanguageTranslator::translate_rust_patterns(content, "rust", &uri)
121            .expect("translation should succeed");
122        assert!(result.contains("// File: example.rs"));
123    }
124
125    #[test]
126    fn test_mixed_pattern_translation() {
127        let uri = Url::parse("file:///test/example.py").expect("valid test URI");
128
129        let rust_content = r#"// Function: {{ function_name }}
130// File: {{ filename }}
131fn {{ function_name }}() {
132    // TODO: implement
133    /* Block comment here */
134        let value = "hello";
135}"#;
136
137        let python_result =
138            LanguageTranslator::translate_rust_patterns(rust_content, "python", &uri)
139                .expect("translation should succeed");
140
141        // Check comment translation
142        assert!(python_result.contains("# Function: {{ function_name }}"));
143        assert!(python_result.contains("# File: example.py"));
144        assert!(python_result.contains("    # TODO: implement"));
145        assert!(python_result.contains("\"\"\" Block comment here \"\"\""));
146
147        // Check that bkmr templates are preserved
148        assert!(python_result.contains("{{ function_name }}"));
149
150        // Check indentation (Python uses 4 spaces like Rust, so no change)
151        assert!(python_result.contains("        let value = \"hello\";"));
152    }
153
154    #[test]
155    fn test_language_info_retrieval() {
156        let rust_info = LanguageRegistry::get_language_info("rust");
157        assert_eq!(rust_info.line_comment, Some("//".to_string()));
158        assert_eq!(
159            rust_info.block_comment,
160            Some(("/*".to_string(), "*/".to_string()))
161        );
162        assert_eq!(rust_info.indent_char, "    ");
163
164        let python_info = LanguageRegistry::get_language_info("python");
165        assert_eq!(python_info.line_comment, Some("#".to_string()));
166        assert_eq!(
167            python_info.block_comment,
168            Some(("\"\"\"".to_string(), "\"\"\"".to_string()))
169        );
170        assert_eq!(python_info.indent_char, "    ");
171
172        let go_info = LanguageRegistry::get_language_info("go");
173        assert_eq!(go_info.line_comment, Some("//".to_string()));
174        assert_eq!(go_info.indent_char, "\t");
175
176        let html_info = LanguageRegistry::get_language_info("html");
177        assert_eq!(html_info.line_comment, None);
178        assert_eq!(
179            html_info.block_comment,
180            Some(("<!--".to_string(), "-->".to_string()))
181        );
182        assert_eq!(html_info.indent_char, "  ");
183    }
184
185    #[test]
186    fn test_edge_cases() {
187        let uri = Url::parse("file:///test/example.py").expect("valid test URI");
188
189        // Empty content
190        let result = LanguageTranslator::translate_rust_patterns("", "python", &uri)
191            .expect("translation should succeed");
192        assert_eq!(result, "");
193
194        // No Rust patterns
195        let no_patterns = "Just plain text here";
196        let result = LanguageTranslator::translate_rust_patterns(no_patterns, "python", &uri)
197            .expect("translation should succeed");
198        assert_eq!(result, no_patterns);
199
200        // Comments in strings (should not be translated)
201        let string_comments = r#"let url = "https://example.com"; // Real comment"#;
202        let result = LanguageTranslator::translate_rust_patterns(string_comments, "python", &uri)
203            .expect("translation should succeed");
204        assert!(result.contains("\"https://example.com\""));
205        assert!(result.contains("# Real comment"));
206
207        // Multiple line patterns
208        let multi_line = "//Comment1\n//Comment2\n    //Comment3";
209        let result = LanguageTranslator::translate_rust_patterns(multi_line, "python", &uri)
210            .expect("translation should succeed");
211        assert!(result.contains("# Comment1"));
212        assert!(result.contains("# Comment2"));
213        assert!(result.contains("    # Comment3"));
214    }
215
216    #[test]
217    fn test_fts_query_builder() {
218        // Test with specific language
219        let filter = SnippetFilter::new(Some("markdown".to_string()), None, 50);
220        let query = filter.build_fts_query();
221        assert_eq!(
222            query,
223            Some(
224                r#"(tags:markdown AND tags:"_snip_") OR (tags:universal AND tags:"_snip_")"#
225                    .to_string()
226            )
227        );
228
229        // Test with rust language
230        let filter = SnippetFilter::new(Some("rust".to_string()), None, 50);
231        let query = filter.build_fts_query();
232        assert_eq!(
233            query,
234            Some(
235                r#"(tags:rust AND tags:"_snip_") OR (tags:universal AND tags:"_snip_")"#
236                    .to_string()
237            )
238        );
239
240        // Test with empty language
241        let filter = SnippetFilter::new(Some("".to_string()), None, 50);
242        let query = filter.build_fts_query();
243        assert_eq!(query, Some(r#"tags:"_snip_""#.to_string()));
244
245        // Test with whitespace-only language
246        let filter = SnippetFilter::new(Some("   ".to_string()), None, 50);
247        let query = filter.build_fts_query();
248        assert_eq!(query, Some(r#"tags:"_snip_""#.to_string()));
249
250        // Test with None language
251        let filter = SnippetFilter::new(None, None, 50);
252        let query = filter.build_fts_query();
253        assert_eq!(query, Some(r#"tags:"_snip_""#.to_string()));
254
255        // Test with complex language names
256        let filter = SnippetFilter::new(Some("typescript".to_string()), None, 50);
257        let query = filter.build_fts_query();
258        assert_eq!(
259            query,
260            Some(
261                r#"(tags:typescript AND tags:"_snip_") OR (tags:universal AND tags:"_snip_")"#
262                    .to_string()
263            )
264        );
265    }
266
267    #[test]
268    fn test_multiline_universal_snippet_processing() {
269        // Test the exact content from the failing snippet
270        let uri = Url::parse("file:///test/example.py").expect("valid test URI");
271        let multiline_content = "{% raw %}\n// Fold description {{{ //\n\nContent\n// }}} Fold description //\n$0\n{% endraw %}";
272
273        println!("Input content: {:?}", multiline_content);
274        println!(
275            "Input lines: {:?}",
276            multiline_content.split('\n').collect::<Vec<_>>()
277        );
278
279        let result = LanguageTranslator::translate_rust_patterns(multiline_content, "python", &uri)
280            .expect("translation should succeed");
281
282        println!("Output content: {:?}", result);
283        println!("Output lines: {:?}", result.split('\n').collect::<Vec<_>>());
284
285        // Check that newlines are preserved
286        let input_line_count = multiline_content.matches('\n').count();
287        let output_line_count = result.matches('\n').count();
288
289        println!(
290            "Input newlines: {}, Output newlines: {}",
291            input_line_count, output_line_count
292        );
293
294        // Should preserve the same number of newlines
295        assert_eq!(
296            input_line_count, output_line_count,
297            "Number of newlines should be preserved"
298        );
299
300        // Should contain translated comments (preserving the // at the end)
301        assert!(result.contains("# Fold description {{{ //"));
302        assert!(result.contains("# }}} Fold description //"));
303
304        // Should preserve the empty line between first comment and "Content"
305        assert!(result.contains("\n\nContent\n"));
306    }
307}