1use std::path::Path;
2use tree_sitter::{Language, Parser};
3use tree_sitter_solidity;
4use tree_sitter_elixir;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8pub enum CompressLanguage {
9 Rust,
10 TypeScript,
11 Tsx,
12 JavaScript,
13 Jsx,
14 Python,
15 Go,
16 Java,
17 CSharp,
18 C,
19 Cpp,
20 Ruby,
21 Php,
22 Solidity,
23 Elixir,
24}
25
26pub fn language_for_extension(ext: &str) -> Option<CompressLanguage> {
28 match ext.to_lowercase().as_str() {
29 "rs" => Some(CompressLanguage::Rust),
30 "ts" => Some(CompressLanguage::TypeScript),
31 "tsx" => Some(CompressLanguage::Tsx),
32 "js" => Some(CompressLanguage::JavaScript),
33 "jsx" => Some(CompressLanguage::Jsx),
34 "py" => Some(CompressLanguage::Python),
35 "go" => Some(CompressLanguage::Go),
36 "java" => Some(CompressLanguage::Java),
37 "cs" => Some(CompressLanguage::CSharp),
38 "c" | "h" => Some(CompressLanguage::C),
39 "cpp" | "cc" | "cxx" | "hpp" | "hh" | "hxx" => Some(CompressLanguage::Cpp),
40 "rb" => Some(CompressLanguage::Ruby),
41 "php" => Some(CompressLanguage::Php),
42 "sol" => Some(CompressLanguage::Solidity),
43 "ex" | "exs" => Some(CompressLanguage::Elixir),
44 _ => None,
45 }
46}
47
48pub fn language_for_path(path: &Path) -> Option<CompressLanguage> {
50 path.extension()
51 .and_then(|e| e.to_str())
52 .and_then(language_for_extension)
53}
54
55fn tree_sitter_language(lang: CompressLanguage) -> Language {
57 match lang {
58 CompressLanguage::Rust => tree_sitter_rust::LANGUAGE.into(),
59 CompressLanguage::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
60 CompressLanguage::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
61 CompressLanguage::JavaScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
62 CompressLanguage::Jsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
63 CompressLanguage::Python => tree_sitter_python::LANGUAGE.into(),
64 CompressLanguage::Go => tree_sitter_go::LANGUAGE.into(),
65 CompressLanguage::Java => tree_sitter_java::LANGUAGE.into(),
66 CompressLanguage::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
67 CompressLanguage::C => tree_sitter_c::LANGUAGE.into(),
68 CompressLanguage::Cpp => tree_sitter_cpp::LANGUAGE.into(),
69 CompressLanguage::Ruby => tree_sitter_ruby::LANGUAGE.into(),
70 CompressLanguage::Php => tree_sitter_php::LANGUAGE_PHP.into(),
71 CompressLanguage::Solidity => tree_sitter_solidity::LANGUAGE.into(),
72 CompressLanguage::Elixir => tree_sitter_elixir::LANGUAGE.into(),
73 }
74}
75
76#[derive(Debug)]
78pub enum CompressResult {
79 Compressed(String),
81 Fallback(String, Option<String>),
83}
84
85fn strip_bom(source: &str) -> &str {
87 source.strip_prefix('\u{FEFF}').unwrap_or(source)
88}
89
90pub fn compress_source(source: &str, lang: CompressLanguage) -> CompressResult {
100 let source = strip_bom(source);
101
102 if source.is_empty() {
103 return CompressResult::Compressed(String::new());
104 }
105
106 let source_owned = source.to_string();
108 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
109 compress_source_inner(&source_owned, lang)
110 }));
111
112 match result {
113 Ok(compress_result) => compress_result,
114 Err(_) => CompressResult::Fallback(
115 source.to_string(),
116 Some("tree-sitter panic caught".to_string()),
117 ),
118 }
119}
120
121fn compress_source_inner(source: &str, lang: CompressLanguage) -> CompressResult {
123 let ts_lang = tree_sitter_language(lang);
124
125 let mut parser = Parser::new();
126 if parser.set_language(&ts_lang).is_err() {
127 return CompressResult::Fallback(
128 source.to_string(),
129 Some("failed to set parser language".to_string()),
130 );
131 }
132
133 let tree = match parser.parse(source, None) {
134 Some(t) => t,
135 None => {
136 return CompressResult::Fallback(
137 source.to_string(),
138 Some("tree-sitter returned NULL tree".to_string()),
139 );
140 }
141 };
142
143 let root = tree.root_node();
144
145 if has_error_nodes(root) {
147 return CompressResult::Fallback(
148 source.to_string(),
149 Some("parse tree contains ERROR nodes".to_string()),
150 );
151 }
152
153 let compressed = match lang {
154 CompressLanguage::Rust => compress_rust(source, root),
155 CompressLanguage::TypeScript
156 | CompressLanguage::Tsx
157 | CompressLanguage::JavaScript
158 | CompressLanguage::Jsx => compress_typescript(source, root),
159 CompressLanguage::Python => compress_python(source, root),
160 CompressLanguage::Go => compress_go(source, root),
161 CompressLanguage::Java => compress_java(source, root),
162 CompressLanguage::CSharp => compress_csharp(source, root),
163 CompressLanguage::C => compress_c(source, root),
164 CompressLanguage::Cpp => compress_cpp(source, root),
165 CompressLanguage::Ruby => compress_ruby(source, root),
166 CompressLanguage::Php => compress_php(source, root),
167 CompressLanguage::Solidity => compress_solidity(source, root),
168 CompressLanguage::Elixir => compress_elixir(source, root),
169 };
170
171 if compressed.is_empty() {
172 return CompressResult::Fallback(
173 source.to_string(),
174 Some("compressed output is empty".to_string()),
175 );
176 }
177
178 if compressed.len() >= source.len() {
179 return CompressResult::Compressed(source.to_string());
180 }
181
182 CompressResult::Compressed(compressed)
183}
184
185fn has_error_nodes(node: tree_sitter::Node) -> bool {
187 if node.is_error() {
188 return true;
189 }
190 let mut cursor = node.walk();
191 for child in node.children(&mut cursor) {
192 if has_error_nodes(child) {
193 return true;
194 }
195 }
196 false
197}
198
199fn node_text<'a>(source: &'a str, node: tree_sitter::Node) -> &'a str {
201 &source[node.byte_range()]
202}
203
204fn compress_body(source: &str, node: tree_sitter::Node, body_kinds: &[&str]) -> String {
209 let mut cursor = node.walk();
210 for child in node.children(&mut cursor) {
211 if body_kinds.contains(&child.kind()) {
212 return format!(
213 "{} {{ ... }}",
214 source[node.start_byte()..child.start_byte()].trim_end()
215 );
216 }
217 }
218 node_text(source, node).to_string()
219}
220
221fn push_indented(output: &mut String, indent: &str, text: &str) {
223 output.push_str(indent);
224 output.push_str(text);
225 output.push('\n');
226}
227
228fn push_indented_block(output: &mut String, indent: &str, block: &str) {
230 for line in block.lines() {
231 output.push_str(indent);
232 output.push_str(line);
233 output.push('\n');
234 }
235}
236
237fn compress_rust(source: &str, root: tree_sitter::Node) -> String {
242 let mut output = String::new();
243 let mut cursor = root.walk();
244
245 for child in root.children(&mut cursor) {
246 match child.kind() {
247 "function_item" => {
248 output.push_str(&compress_rust_function(source, child));
249 output.push('\n');
250 }
251 "trait_item" => {
252 output.push_str(&compress_rust_trait(source, child));
253 output.push('\n');
254 }
255 "impl_item" => {
256 output.push_str(&compress_rust_impl(source, child));
257 output.push('\n');
258 }
259 "use_declaration"
260 | "extern_crate_declaration"
261 | "mod_item"
262 | "type_item"
263 | "const_item"
264 | "static_item"
265 | "attribute_item"
266 | "inner_attribute_item"
267 | "macro_definition"
268 | "macro_invocation"
269 | "line_comment"
270 | "block_comment"
271 | "struct_item"
272 | "enum_item" => {
273 output.push_str(node_text(source, child));
274 output.push('\n');
275 }
276 _ => {}
277 }
278 }
279
280 output.trim_end().to_string()
281}
282
283fn compress_rust_function(source: &str, node: tree_sitter::Node) -> String {
284 compress_body(source, node, &["block"])
285}
286
287fn compress_rust_trait(source: &str, node: tree_sitter::Node) -> String {
288 let mut output = String::new();
289 let mut cursor = node.walk();
290
291 for child in node.children(&mut cursor) {
292 if child.kind() == "declaration_list" {
293 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
294 output.push_str(" {\n");
295
296 let mut inner_cursor = child.walk();
297 for item in child.children(&mut inner_cursor) {
298 match item.kind() {
299 "function_item" => {
300 push_indented(&mut output, " ", &compress_rust_function(source, item));
301 }
302 "function_signature_item"
303 | "type_item"
304 | "const_item"
305 | "attribute_item"
306 | "line_comment"
307 | "block_comment" => {
308 push_indented(&mut output, " ", node_text(source, item));
309 }
310 _ => {}
311 }
312 }
313 output.push('}');
314 return output;
315 }
316 }
317
318 node_text(source, node).to_string()
319}
320
321fn compress_rust_impl(source: &str, node: tree_sitter::Node) -> String {
322 let mut output = String::new();
323 let mut cursor = node.walk();
324
325 for child in node.children(&mut cursor) {
326 if child.kind() == "declaration_list" {
327 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
328 output.push_str(" {\n");
329
330 let mut inner_cursor = child.walk();
331 for item in child.children(&mut inner_cursor) {
332 match item.kind() {
333 "function_item" => {
334 push_indented(&mut output, " ", &compress_rust_function(source, item));
335 }
336 "type_item" | "const_item" | "attribute_item" | "line_comment"
337 | "block_comment" => {
338 push_indented(&mut output, " ", node_text(source, item));
339 }
340 _ => {}
341 }
342 }
343 output.push('}');
344 return output;
345 }
346 }
347
348 node_text(source, node).to_string()
349}
350
351fn compress_typescript(source: &str, root: tree_sitter::Node) -> String {
356 let mut output = String::new();
357 let mut cursor = root.walk();
358
359 for child in root.children(&mut cursor) {
360 match child.kind() {
361 "export_statement" => {
362 output.push_str(&compress_ts_export(source, child));
363 output.push('\n');
364 }
365 "function_declaration" => {
366 output.push_str(&compress_ts_function(source, child));
367 output.push('\n');
368 }
369 "class_declaration" => {
370 output.push_str(&compress_ts_class(source, child));
371 output.push('\n');
372 }
373 "lexical_declaration" | "variable_declaration" => {
374 output.push_str(&compress_ts_variable(source, child));
375 output.push('\n');
376 }
377 "import_statement"
378 | "comment"
379 | "interface_declaration"
380 | "type_alias_declaration"
381 | "enum_declaration"
382 | "export_default_declaration"
383 | "module"
384 | "ambient_declaration" => {
385 output.push_str(node_text(source, child));
386 output.push('\n');
387 }
388 _ => {}
389 }
390 }
391
392 output.trim_end().to_string()
393}
394
395fn compress_ts_function(source: &str, node: tree_sitter::Node) -> String {
396 compress_body(source, node, &["statement_block"])
397}
398
399fn compress_ts_class(source: &str, node: tree_sitter::Node) -> String {
400 let mut output = String::new();
401 let mut cursor = node.walk();
402
403 for child in node.children(&mut cursor) {
404 if child.kind() == "class_body" {
405 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
406 output.push_str(" {\n");
407
408 let mut inner_cursor = child.walk();
409 for item in child.children(&mut inner_cursor) {
410 match item.kind() {
411 "method_definition" | "public_field_definition" | "property_definition" => {
412 push_indented(
413 &mut output,
414 " ",
415 &compress_body(source, item, &["statement_block"]),
416 );
417 }
418 "comment" => {
419 push_indented(&mut output, " ", node_text(source, item));
420 }
421 _ => {}
422 }
423 }
424 output.push('}');
425 return output;
426 }
427 }
428
429 node_text(source, node).to_string()
430}
431
432fn compress_ts_variable(source: &str, node: tree_sitter::Node) -> String {
433 let text = node_text(source, node);
435 if text.contains("=>") && text.len() > 80 {
436 let mut cursor = node.walk();
438 if let Some(compressed) = compress_ts_var_inner(source, node, &mut cursor) {
439 return compressed;
440 }
441 }
442 text.to_string()
443}
444
445fn compress_ts_var_inner(
446 source: &str,
447 node: tree_sitter::Node,
448 _cursor: &mut tree_sitter::TreeCursor,
449) -> Option<String> {
450 fn find_arrow_body(node: tree_sitter::Node) -> Option<(usize, usize)> {
452 if node.kind() == "arrow_function" {
453 let mut cursor = node.walk();
454 for child in node.children(&mut cursor) {
455 if child.kind() == "statement_block" {
456 return Some((child.start_byte(), child.end_byte()));
457 }
458 }
459 }
460 let mut cursor = node.walk();
461 for child in node.children(&mut cursor) {
462 if let Some(range) = find_arrow_body(child) {
463 return Some(range);
464 }
465 }
466 None
467 }
468
469 if let Some((body_start, body_end)) = find_arrow_body(node) {
470 let before = &source[node.start_byte()..body_start];
471 let after = &source[body_end..node.end_byte()];
472 Some(format!("{}{{ ... }}{}", before.trim_end(), after))
473 } else {
474 None
475 }
476}
477
478fn compress_ts_export(source: &str, node: tree_sitter::Node) -> String {
479 let mut cursor = node.walk();
480 for inner in node.children(&mut cursor) {
481 match inner.kind() {
482 "function_declaration" => {
483 let mut fcursor = inner.walk();
485 for fchild in inner.children(&mut fcursor) {
486 if fchild.kind() == "statement_block" {
487 let sig = source[node.start_byte()..fchild.start_byte()].trim_end();
489 return format!("{} {{ ... }}", sig);
490 }
491 }
492 return node_text(source, node).to_string();
494 }
495 "class_declaration" => {
496 let prefix = &source[node.start_byte()..inner.start_byte()];
497 return format!("{}{}", prefix, compress_ts_class(source, inner));
498 }
499 _ => {}
500 }
501 }
502 node_text(source, node).to_string()
504}
505
506fn compress_python(source: &str, root: tree_sitter::Node) -> String {
511 let mut output = String::new();
512 let mut cursor = root.walk();
513
514 for child in root.children(&mut cursor) {
515 match child.kind() {
516 "import_statement" | "import_from_statement" | "future_import_statement" => {
518 output.push_str(node_text(source, child));
519 output.push('\n');
520 }
521 "comment" => {
523 output.push_str(node_text(source, child));
524 output.push('\n');
525 }
526 "expression_statement" => {
528 let text = node_text(source, child);
529 if text.starts_with("\"\"\"") || text.starts_with("'''") {
531 output.push_str(text);
532 output.push('\n');
533 } else {
534 let mut inner_cursor = child.walk();
536 for inner_child in child.children(&mut inner_cursor) {
537 if inner_child.kind() == "assignment" && text.len() <= 120 {
538 output.push_str(text);
539 output.push('\n');
540 break;
541 }
542 }
543 }
544 }
545 "function_definition" | "decorated_definition" => {
547 output.push_str(&compress_python_function(source, child));
548 output.push('\n');
549 }
550 "class_definition" => {
552 output.push_str(&compress_python_class(source, child));
553 output.push('\n');
554 }
555 "assignment" => {
557 let text = node_text(source, child);
558 if text.len() <= 120 {
560 output.push_str(text);
561 output.push('\n');
562 }
563 }
564 _ => {}
565 }
566 }
567
568 output.trim_end().to_string()
569}
570
571fn compress_python_function(source: &str, node: tree_sitter::Node) -> String {
572 let mut cursor = node.walk();
573
574 if node.kind() == "decorated_definition" {
576 let mut decorators = String::new();
577 for child in node.children(&mut cursor) {
578 match child.kind() {
579 "decorator" => {
580 decorators.push_str(node_text(source, child));
581 decorators.push('\n');
582 }
583 "function_definition" => {
584 decorators.push_str(&compress_python_function_inner(source, child));
585 return decorators;
586 }
587 "class_definition" => {
588 decorators.push_str(&compress_python_class(source, child));
589 return decorators;
590 }
591 _ => {}
592 }
593 }
594 return decorators;
595 }
596
597 compress_python_function_inner(source, node)
598}
599
600fn compress_python_function_inner(source: &str, node: tree_sitter::Node) -> String {
601 let mut cursor = node.walk();
602
603 for child in node.children(&mut cursor) {
604 if child.kind() == "block" {
605 let sig = source[node.start_byte()..child.start_byte()].trim_end();
606 let mut block_cursor = child.walk();
608 if let Some(block_child) = child.children(&mut block_cursor).next() {
609 if block_child.kind() == "expression_statement" {
610 let text = node_text(source, block_child);
611 if text.starts_with("\"\"\"") || text.starts_with("'''") {
612 return format!("{}\n {}\n ...", sig, text);
613 }
614 }
615 }
616 return format!("{}\n ...", sig);
617 }
618 }
619
620 node_text(source, node).to_string()
621}
622
623fn compress_python_class(source: &str, node: tree_sitter::Node) -> String {
624 let mut output = String::new();
625 let mut cursor = node.walk();
626
627 for child in node.children(&mut cursor) {
628 if child.kind() == "block" {
629 let header = source[node.start_byte()..child.start_byte()].trim_end();
630 output.push_str(header);
631 output.push('\n');
632
633 let mut inner_cursor = child.walk();
634 for item in child.children(&mut inner_cursor) {
635 match item.kind() {
636 "function_definition" | "decorated_definition" => {
637 let compressed = compress_python_function(source, item);
639 for line in compressed.lines() {
640 output.push_str(" ");
641 output.push_str(line);
642 output.push('\n');
643 }
644 }
645 "expression_statement" => {
646 let text = node_text(source, item);
647 if text.starts_with("\"\"\"")
649 || text.starts_with("'''")
650 || text.contains('=')
651 {
652 output.push_str(" ");
653 output.push_str(text);
654 output.push('\n');
655 }
656 }
657 "comment" => {
658 output.push_str(" ");
659 output.push_str(node_text(source, item));
660 output.push('\n');
661 }
662 _ => {}
663 }
664 }
665
666 return output.trim_end().to_string();
667 }
668 }
669
670 node_text(source, node).to_string()
671}
672
673fn compress_go(source: &str, root: tree_sitter::Node) -> String {
678 let mut output = String::new();
679 let mut cursor = root.walk();
680
681 for child in root.children(&mut cursor) {
682 match child.kind() {
683 "function_declaration" | "method_declaration" => {
684 output.push_str(&compress_body(source, child, &["block"]));
685 output.push('\n');
686 }
687 "package_clause" | "import_declaration" | "comment" | "type_declaration"
688 | "const_declaration" | "var_declaration" => {
689 output.push_str(node_text(source, child));
690 output.push('\n');
691 }
692 _ => {}
693 }
694 }
695
696 output.trim_end().to_string()
697}
698
699fn compress_java(source: &str, root: tree_sitter::Node) -> String {
704 let mut output = String::new();
705 let mut cursor = root.walk();
706
707 for child in root.children(&mut cursor) {
708 match child.kind() {
709 "class_declaration"
710 | "interface_declaration"
711 | "enum_declaration"
712 | "record_declaration"
713 | "annotation_type_declaration" => {
714 output.push_str(&compress_java_class(source, child));
715 output.push('\n');
716 }
717 "package_declaration" | "import_declaration" | "line_comment" | "block_comment" => {
718 output.push_str(node_text(source, child));
719 output.push('\n');
720 }
721 _ => {}
722 }
723 }
724
725 output.trim_end().to_string()
726}
727
728fn compress_java_class(source: &str, node: tree_sitter::Node) -> String {
729 let body_kind = match node.kind() {
730 "enum_declaration" => "enum_body",
731 "interface_declaration" => "interface_body",
732 "annotation_type_declaration" => "annotation_type_body",
733 _ => "class_body",
734 };
735
736 let mut output = String::new();
737 let mut cursor = node.walk();
738
739 for child in node.children(&mut cursor) {
740 if child.kind() == body_kind {
741 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
742 output.push_str(" {\n");
743
744 let mut inner_cursor = child.walk();
745 for item in child.children(&mut inner_cursor) {
746 match item.kind() {
747 "method_declaration" | "constructor_declaration" => {
748 push_indented(
749 &mut output,
750 " ",
751 &compress_body(source, item, &["block", "constructor_body"]),
752 );
753 }
754 "enum_constant"
755 | "field_declaration"
756 | "constant_declaration"
757 | "line_comment"
758 | "block_comment" => {
759 push_indented(&mut output, " ", node_text(source, item));
760 }
761 "enum_body_declarations" => {
762 let mut decl_cursor = item.walk();
764 for decl in item.children(&mut decl_cursor) {
765 match decl.kind() {
766 "method_declaration" | "constructor_declaration" => {
767 push_indented(
768 &mut output,
769 " ",
770 &compress_body(
771 source,
772 decl,
773 &["block", "constructor_body"],
774 ),
775 );
776 }
777 "field_declaration"
778 | "constant_declaration"
779 | "line_comment"
780 | "block_comment" => {
781 push_indented(&mut output, " ", node_text(source, decl));
782 }
783 _ => {}
784 }
785 }
786 }
787 "class_declaration"
788 | "interface_declaration"
789 | "enum_declaration"
790 | "record_declaration" => {
791 push_indented_block(
792 &mut output,
793 " ",
794 &compress_java_class(source, item),
795 );
796 }
797 _ => {}
798 }
799 }
800 output.push('}');
801 return output;
802 }
803 }
804
805 node_text(source, node).to_string()
806}
807
808fn compress_csharp(source: &str, root: tree_sitter::Node) -> String {
813 let mut output = String::new();
814 let mut cursor = root.walk();
815
816 for child in root.children(&mut cursor) {
817 match child.kind() {
818 "namespace_declaration" | "file_scoped_namespace_declaration" => {
819 output.push_str(&compress_csharp_namespace(source, child));
820 output.push('\n');
821 }
822 "class_declaration"
823 | "interface_declaration"
824 | "struct_declaration"
825 | "enum_declaration"
826 | "record_declaration" => {
827 output.push_str(&compress_csharp_class(source, child));
828 output.push('\n');
829 }
830 "using_directive" | "comment" => {
831 output.push_str(node_text(source, child));
832 output.push('\n');
833 }
834 _ => {}
835 }
836 }
837
838 output.trim_end().to_string()
839}
840
841fn compress_csharp_namespace(source: &str, node: tree_sitter::Node) -> String {
842 let mut output = String::new();
843 let mut cursor = node.walk();
844
845 for child in node.children(&mut cursor) {
846 if child.kind() == "declaration_list" {
847 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
848 output.push_str(" {\n");
849
850 let mut inner_cursor = child.walk();
851 for item in child.children(&mut inner_cursor) {
852 match item.kind() {
853 "class_declaration"
854 | "interface_declaration"
855 | "struct_declaration"
856 | "enum_declaration"
857 | "record_declaration" => {
858 push_indented_block(
859 &mut output,
860 " ",
861 &compress_csharp_class(source, item),
862 );
863 }
864 "using_directive" | "comment" => {
865 push_indented(&mut output, " ", node_text(source, item));
866 }
867 _ => {}
868 }
869 }
870 output.push('}');
871 return output;
872 }
873 }
874
875 node_text(source, node).to_string()
876}
877
878fn compress_csharp_class(source: &str, node: tree_sitter::Node) -> String {
879 let mut output = String::new();
880 let mut cursor = node.walk();
881
882 for child in node.children(&mut cursor) {
883 if child.kind() == "declaration_list" {
884 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
885 output.push_str(" {\n");
886
887 let mut inner_cursor = child.walk();
888 for item in child.children(&mut inner_cursor) {
889 match item.kind() {
890 "method_declaration" | "constructor_declaration" => {
891 push_indented(
892 &mut output,
893 " ",
894 &compress_body(source, item, &["block"]),
895 );
896 }
897 "property_declaration" => {
898 push_indented(
899 &mut output,
900 " ",
901 &compress_body(source, item, &["accessor_list"]),
902 );
903 }
904 "field_declaration"
905 | "event_declaration"
906 | "event_field_declaration"
907 | "comment" => {
908 push_indented(&mut output, " ", node_text(source, item));
909 }
910 "class_declaration"
911 | "interface_declaration"
912 | "struct_declaration"
913 | "enum_declaration"
914 | "record_declaration" => {
915 push_indented_block(
916 &mut output,
917 " ",
918 &compress_csharp_class(source, item),
919 );
920 }
921 _ => {}
922 }
923 }
924 output.push('}');
925 return output;
926 }
927 }
928
929 node_text(source, node).to_string()
930}
931
932fn compress_c(source: &str, root: tree_sitter::Node) -> String {
937 let mut output = String::new();
938 let mut cursor = root.walk();
939
940 for child in root.children(&mut cursor) {
941 match child.kind() {
942 "function_definition" => {
943 output.push_str(&compress_body(source, child, &["compound_statement"]));
944 output.push('\n');
945 }
946 "preproc_include"
947 | "preproc_def"
948 | "preproc_ifdef"
949 | "preproc_if"
950 | "preproc_ifndef"
951 | "preproc_function_def"
952 | "preproc_call"
953 | "comment"
954 | "declaration"
955 | "type_definition"
956 | "struct_specifier"
957 | "enum_specifier"
958 | "union_specifier" => {
959 output.push_str(node_text(source, child));
960 output.push('\n');
961 }
962 _ => {}
963 }
964 }
965
966 output.trim_end().to_string()
967}
968
969fn compress_cpp(source: &str, root: tree_sitter::Node) -> String {
974 let mut output = String::new();
975 let mut cursor = root.walk();
976
977 for child in root.children(&mut cursor) {
978 match child.kind() {
979 "function_definition" => {
980 output.push_str(&compress_body(source, child, &["compound_statement"]));
981 output.push('\n');
982 }
983 "class_specifier" => {
984 output.push_str(&compress_cpp_class(source, child));
985 output.push('\n');
986 }
987 "namespace_definition" => {
988 output.push_str(&compress_cpp_namespace(source, child));
989 output.push('\n');
990 }
991 "template_declaration" => {
992 output.push_str(&compress_cpp_template(source, child));
993 output.push('\n');
994 }
995 "linkage_specification" => {
996 output.push_str(&compress_cpp_linkage(source, child));
997 output.push('\n');
998 }
999 "preproc_include"
1000 | "preproc_def"
1001 | "preproc_ifdef"
1002 | "preproc_if"
1003 | "preproc_ifndef"
1004 | "preproc_function_def"
1005 | "preproc_call"
1006 | "comment"
1007 | "declaration"
1008 | "type_definition"
1009 | "using_declaration"
1010 | "alias_declaration"
1011 | "struct_specifier"
1012 | "enum_specifier"
1013 | "union_specifier" => {
1014 output.push_str(node_text(source, child));
1015 output.push('\n');
1016 }
1017 _ => {}
1018 }
1019 }
1020
1021 output.trim_end().to_string()
1022}
1023
1024fn compress_cpp_class(source: &str, node: tree_sitter::Node) -> String {
1025 let mut output = String::new();
1026 let mut cursor = node.walk();
1027
1028 for child in node.children(&mut cursor) {
1029 if child.kind() == "field_declaration_list" {
1030 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
1031 output.push_str(" {\n");
1032
1033 let mut inner_cursor = child.walk();
1034 for item in child.children(&mut inner_cursor) {
1035 match item.kind() {
1036 "function_definition" => {
1037 push_indented(
1038 &mut output,
1039 " ",
1040 &compress_body(source, item, &["compound_statement"]),
1041 );
1042 }
1043 "template_declaration" => {
1044 push_indented_block(
1045 &mut output,
1046 " ",
1047 &compress_cpp_template(source, item),
1048 );
1049 }
1050 "field_declaration" | "declaration" | "using_declaration"
1051 | "alias_declaration" | "type_definition" | "access_specifier"
1052 | "friend_declaration" | "preproc_ifdef" | "preproc_if" | "preproc_ifndef"
1053 | "preproc_def" | "preproc_call" | "comment" => {
1054 push_indented(&mut output, " ", node_text(source, item));
1055 }
1056 _ => {}
1057 }
1058 }
1059 output.push('}');
1060 return output;
1061 }
1062 }
1063
1064 node_text(source, node).to_string()
1065}
1066
1067fn compress_cpp_namespace(source: &str, node: tree_sitter::Node) -> String {
1068 let mut output = String::new();
1069 let mut cursor = node.walk();
1070
1071 for child in node.children(&mut cursor) {
1072 if child.kind() == "declaration_list" {
1073 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
1074 output.push_str(" {\n");
1075
1076 let mut inner_cursor = child.walk();
1077 for item in child.children(&mut inner_cursor) {
1078 match item.kind() {
1079 "function_definition" => {
1080 push_indented(
1081 &mut output,
1082 " ",
1083 &compress_body(source, item, &["compound_statement"]),
1084 );
1085 }
1086 "class_specifier" => {
1087 push_indented_block(&mut output, " ", &compress_cpp_class(source, item));
1088 }
1089 "template_declaration" => {
1090 push_indented_block(
1091 &mut output,
1092 " ",
1093 &compress_cpp_template(source, item),
1094 );
1095 }
1096 "namespace_definition" => {
1097 push_indented_block(
1098 &mut output,
1099 " ",
1100 &compress_cpp_namespace(source, item),
1101 );
1102 }
1103 "struct_specifier" | "enum_specifier" | "union_specifier" | "declaration"
1104 | "type_definition" | "using_declaration" | "alias_declaration"
1105 | "preproc_ifdef" | "preproc_if" | "preproc_ifndef" | "preproc_def"
1106 | "preproc_call" | "comment" => {
1107 push_indented(&mut output, " ", node_text(source, item));
1108 }
1109 _ => {}
1110 }
1111 }
1112 output.push('}');
1113 return output;
1114 }
1115 }
1116
1117 node_text(source, node).to_string()
1118}
1119
1120fn compress_cpp_template(source: &str, node: tree_sitter::Node) -> String {
1121 let mut cursor = node.walk();
1122 for child in node.children(&mut cursor) {
1123 let prefix = source[node.start_byte()..child.start_byte()].trim_end();
1124 match child.kind() {
1125 "function_definition" => {
1126 return format!(
1127 "{}\n{}",
1128 prefix,
1129 compress_body(source, child, &["compound_statement"])
1130 );
1131 }
1132 "class_specifier" => {
1133 return format!("{}\n{}", prefix, compress_cpp_class(source, child));
1134 }
1135 "declaration" => {
1136 return format!("{}\n{}", prefix, node_text(source, child));
1137 }
1138 _ => {}
1139 }
1140 }
1141 node_text(source, node).to_string()
1142}
1143
1144fn compress_cpp_linkage(source: &str, node: tree_sitter::Node) -> String {
1145 let mut output = String::new();
1146 let mut cursor = node.walk();
1147
1148 for child in node.children(&mut cursor) {
1149 if child.kind() == "declaration_list" {
1150 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
1151 output.push_str(" {\n");
1152
1153 let mut inner_cursor = child.walk();
1154 for item in child.children(&mut inner_cursor) {
1155 match item.kind() {
1156 "function_definition" => {
1157 push_indented(
1158 &mut output,
1159 " ",
1160 &compress_body(source, item, &["compound_statement"]),
1161 );
1162 }
1163 "declaration" | "comment" => {
1164 push_indented(&mut output, " ", node_text(source, item));
1165 }
1166 _ => {}
1167 }
1168 }
1169 output.push('}');
1170 return output;
1171 }
1172 }
1173
1174 node_text(source, node).to_string()
1175}
1176
1177fn compress_ruby(source: &str, root: tree_sitter::Node) -> String {
1182 let mut output = String::new();
1183 let mut cursor = root.walk();
1184
1185 for child in root.children(&mut cursor) {
1186 match child.kind() {
1187 "comment" => {
1188 output.push_str(node_text(source, child));
1189 output.push('\n');
1190 }
1191 "call" => {
1192 let text = node_text(source, child);
1193 if text.starts_with("require") {
1194 output.push_str(text);
1195 output.push('\n');
1196 }
1197 }
1198 "method" | "singleton_method" => {
1199 output.push_str(&compress_ruby_method(source, child));
1200 output.push('\n');
1201 }
1202 "class" | "module" => {
1203 output.push_str(&compress_ruby_class(source, child));
1204 output.push('\n');
1205 }
1206 "assignment" => {
1207 let text = node_text(source, child);
1208 if text.len() <= 120 {
1209 output.push_str(text);
1210 output.push('\n');
1211 }
1212 }
1213 _ => {}
1214 }
1215 }
1216
1217 output.trim_end().to_string()
1218}
1219
1220fn compress_ruby_method(source: &str, node: tree_sitter::Node) -> String {
1221 let mut cursor = node.walk();
1222 for child in node.children(&mut cursor) {
1223 if child.kind() == "body_statement" {
1224 let sig = source[node.start_byte()..child.start_byte()].trim_end();
1225 return format!("{}\n ...\nend", sig);
1226 }
1227 }
1228 node_text(source, node).to_string()
1229}
1230
1231fn compress_ruby_class(source: &str, node: tree_sitter::Node) -> String {
1232 let mut output = String::new();
1233 let mut cursor = node.walk();
1234
1235 for child in node.children(&mut cursor) {
1236 if child.kind() == "body_statement" {
1237 let header = source[node.start_byte()..child.start_byte()].trim_end();
1238 output.push_str(header);
1239 output.push('\n');
1240
1241 let mut inner_cursor = child.walk();
1242 for item in child.children(&mut inner_cursor) {
1243 match item.kind() {
1244 "method" | "singleton_method" => {
1245 push_indented_block(&mut output, " ", &compress_ruby_method(source, item));
1246 }
1247 "class" | "module" => {
1248 push_indented_block(&mut output, " ", &compress_ruby_class(source, item));
1249 }
1250 "comment" => {
1251 push_indented(&mut output, " ", node_text(source, item));
1252 }
1253 "call" | "assignment" => {
1254 let text = node_text(source, item);
1255 if text.len() <= 120 {
1256 push_indented(&mut output, " ", text);
1257 }
1258 }
1259 _ => {}
1260 }
1261 }
1262
1263 output.push_str("end");
1264 return output;
1265 }
1266 }
1267
1268 node_text(source, node).to_string()
1269}
1270
1271fn compress_php(source: &str, root: tree_sitter::Node) -> String {
1276 let mut output = String::new();
1277 let mut cursor = root.walk();
1278
1279 for child in root.children(&mut cursor) {
1280 match child.kind() {
1281 "function_definition" => {
1282 output.push_str(&compress_body(source, child, &["compound_statement"]));
1283 output.push('\n');
1284 }
1285 "namespace_definition" => {
1286 output.push_str(&compress_php_namespace(source, child));
1287 output.push('\n');
1288 }
1289 "class_declaration"
1290 | "interface_declaration"
1291 | "trait_declaration"
1292 | "enum_declaration" => {
1293 output.push_str(&compress_php_class(source, child));
1294 output.push('\n');
1295 }
1296 "php_tag" | "namespace_use_declaration" | "const_declaration" | "comment" => {
1297 output.push_str(node_text(source, child));
1298 output.push('\n');
1299 }
1300 _ => {}
1301 }
1302 }
1303
1304 output.trim_end().to_string()
1305}
1306
1307fn compress_php_namespace(source: &str, node: tree_sitter::Node) -> String {
1308 let mut output = String::new();
1309 let mut cursor = node.walk();
1310
1311 for child in node.children(&mut cursor) {
1312 if child.kind() == "compound_statement" || child.kind() == "declaration_list" {
1313 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
1314 output.push_str(" {\n");
1315
1316 let mut inner_cursor = child.walk();
1317 for item in child.children(&mut inner_cursor) {
1318 match item.kind() {
1319 "class_declaration"
1320 | "interface_declaration"
1321 | "trait_declaration"
1322 | "enum_declaration" => {
1323 push_indented_block(&mut output, " ", &compress_php_class(source, item));
1324 }
1325 "function_definition" => {
1326 push_indented(
1327 &mut output,
1328 " ",
1329 &compress_body(source, item, &["compound_statement"]),
1330 );
1331 }
1332 "namespace_use_declaration" | "const_declaration" | "comment" => {
1333 push_indented(&mut output, " ", node_text(source, item));
1334 }
1335 _ => {}
1336 }
1337 }
1338 output.push('}');
1339 return output;
1340 }
1341 }
1342
1343 node_text(source, node).to_string()
1345}
1346
1347fn compress_php_class(source: &str, node: tree_sitter::Node) -> String {
1348 let mut output = String::new();
1349 let mut cursor = node.walk();
1350
1351 for child in node.children(&mut cursor) {
1352 if child.kind() == "declaration_list" || child.kind() == "enum_declaration_list" {
1353 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
1354 output.push_str(" {\n");
1355
1356 let mut inner_cursor = child.walk();
1357 for item in child.children(&mut inner_cursor) {
1358 match item.kind() {
1359 "method_declaration" => {
1360 push_indented(
1361 &mut output,
1362 " ",
1363 &compress_body(source, item, &["compound_statement"]),
1364 );
1365 }
1366 "property_declaration"
1367 | "const_declaration"
1368 | "use_declaration"
1369 | "enum_case"
1370 | "comment" => {
1371 push_indented(&mut output, " ", node_text(source, item));
1372 }
1373 _ => {}
1374 }
1375 }
1376 output.push('}');
1377 return output;
1378 }
1379 }
1380
1381 node_text(source, node).to_string()
1382}
1383
1384fn compress_solidity(source: &str, root: tree_sitter::Node) -> String {
1389 let mut output = String::new();
1390 let mut cursor = root.walk();
1391
1392 for child in root.children(&mut cursor) {
1393 match child.kind() {
1394 "contract_declaration" | "library_declaration" | "interface_declaration" => {
1395 output.push_str(&compress_solidity_contract(source, child));
1396 output.push('\n');
1397 }
1398 "pragma_directive" | "import_directive" | "comment" => {
1399 output.push_str(node_text(source, child));
1400 output.push('\n');
1401 }
1402 _ => {}
1403 }
1404 }
1405
1406 output.trim_end().to_string()
1407}
1408
1409fn compress_solidity_contract(source: &str, node: tree_sitter::Node) -> String {
1410 let mut output = String::new();
1411 let mut cursor = node.walk();
1412
1413 for child in node.children(&mut cursor) {
1414 if child.kind() == "contract_body" {
1415 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
1417 output.push_str(" {\n");
1418
1419 let mut inner_cursor = child.walk();
1421 for item in child.children(&mut inner_cursor) {
1422 match item.kind() {
1423 "function_definition" | "modifier_definition" => {
1424 push_indented(&mut output, " ",
1425 &compress_body(source, item, &["function_body"]));
1426 }
1427 "state_variable_declaration" | "event_definition"
1428 | "struct_declaration" | "enum_declaration" | "comment" => {
1429 push_indented(&mut output, " ", node_text(source, item));
1430 }
1431 _ => {}
1432 }
1433 }
1434 output.push('}');
1435 return output;
1436 }
1437 }
1438
1439 node_text(source, node).to_string()
1440}
1441
1442fn compress_elixir(source: &str, root: tree_sitter::Node) -> String {
1447 let mut output = String::new();
1448 let mut cursor = root.walk();
1449
1450 for child in root.children(&mut cursor) {
1451 match child.kind() {
1452 "call" => {
1453 if is_elixir_definition(&child, source) {
1454 output.push_str(&compress_elixir_call(source, child));
1455 output.push('\n');
1456 }
1457 }
1458 "alias" | "import" | "require" | "use" | "comment" => {
1459 output.push_str(node_text(source, child));
1460 output.push('\n');
1461 }
1462 "unary_operator" => {
1463 if node_text(source, child).starts_with('@') {
1465 output.push_str(node_text(source, child));
1466 output.push('\n');
1467 }
1468 }
1469 _ => {}
1470 }
1471 }
1472
1473 output.trim_end().to_string()
1474}
1475
1476fn is_elixir_definition(node: &tree_sitter::Node, source: &str) -> bool {
1477 let mut cursor = node.walk();
1478 for child in node.children(&mut cursor) {
1479 if child.kind() == "identifier" {
1480 let name = node_text(source, child);
1481 return matches!(name, "defmodule" | "defprotocol" | "defimpl"
1482 | "def" | "defp" | "defmacro" | "defmacrop"
1483 | "defstruct" | "defdelegate" | "defguard"
1484 | "alias" | "import" | "require" | "use");
1485 }
1486 }
1487 false
1488}
1489
1490fn compress_elixir_call(source: &str, node: tree_sitter::Node) -> String {
1491 let mut cursor = node.walk();
1492 let mut def_name = String::new();
1493
1494 for child in node.children(&mut cursor) {
1495 if child.kind() == "identifier" {
1496 def_name = node_text(source, child).to_string();
1497 break;
1498 }
1499 }
1500
1501 if matches!(def_name.as_str(), "def" | "defp" | "defmacro" | "defmacrop") {
1503 return compress_body(source, node, &["do_block"]);
1504 }
1505
1506 if matches!(def_name.as_str(), "defmodule" | "defprotocol" | "defimpl") {
1508 return compress_elixir_module(source, node);
1509 }
1510
1511 node_text(source, node).to_string()
1513}
1514
1515fn compress_elixir_module(source: &str, node: tree_sitter::Node) -> String {
1516 let mut output = String::new();
1517 let mut cursor = node.walk();
1518
1519 for child in node.children(&mut cursor) {
1520 if child.kind() == "do_block" {
1521 output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
1522 output.push_str(" do\n");
1523
1524 let mut inner_cursor = child.walk();
1525 for item in child.children(&mut inner_cursor) {
1526 match item.kind() {
1527 "call" => {
1528 if is_elixir_definition(&item, source) {
1529 push_indented(&mut output, " ", &compress_elixir_call(source, item));
1530 }
1531 }
1532 "alias" | "import" | "require" | "use" | "comment" => {
1533 push_indented(&mut output, " ", node_text(source, item));
1534 }
1535 "unary_operator" => {
1536 if node_text(source, item).starts_with('@') {
1537 push_indented(&mut output, " ", node_text(source, item));
1538 }
1539 }
1540 _ => {}
1541 }
1542 }
1543 output.push_str("end");
1544 return output;
1545 }
1546 }
1547
1548 node_text(source, node).to_string()
1549}
1550
1551#[cfg(test)]
1556mod tests {
1557 use super::*;
1558
1559 #[test]
1561 fn test_language_for_extension() {
1562 assert_eq!(language_for_extension("rs"), Some(CompressLanguage::Rust));
1563 assert_eq!(
1564 language_for_extension("ts"),
1565 Some(CompressLanguage::TypeScript)
1566 );
1567 assert_eq!(language_for_extension("tsx"), Some(CompressLanguage::Tsx));
1568 assert_eq!(
1569 language_for_extension("js"),
1570 Some(CompressLanguage::JavaScript)
1571 );
1572 assert_eq!(language_for_extension("jsx"), Some(CompressLanguage::Jsx));
1573 assert_eq!(language_for_extension("py"), Some(CompressLanguage::Python));
1574 assert_eq!(language_for_extension("go"), Some(CompressLanguage::Go));
1575 assert_eq!(language_for_extension("sol"), Some(CompressLanguage::Solidity));
1576 assert_eq!(language_for_extension("ex"), Some(CompressLanguage::Elixir));
1577 assert_eq!(language_for_extension("exs"), Some(CompressLanguage::Elixir));
1578 assert_eq!(language_for_extension("md"), None);
1579 assert_eq!(language_for_extension("toml"), None);
1580 }
1581
1582 #[test]
1583 fn test_language_for_path() {
1584 assert_eq!(
1585 language_for_path(Path::new("main.rs")),
1586 Some(CompressLanguage::Rust)
1587 );
1588 assert_eq!(
1589 language_for_path(Path::new("foo.test.ts")),
1590 Some(CompressLanguage::TypeScript)
1591 );
1592 assert_eq!(language_for_path(Path::new("Makefile")), None);
1593 assert_eq!(language_for_path(Path::new("README.md")), None);
1594 }
1595
1596 #[test]
1598 fn test_compress_rust_function() {
1599 let source = r#"fn hello(name: &str) -> String {
1600 let greeting = format!("Hello, {}!", name);
1601 println!("{}", greeting);
1602 greeting
1603}"#;
1604 match compress_source(source, CompressLanguage::Rust) {
1605 CompressResult::Compressed(output) => {
1606 assert!(output.contains("fn hello(name: &str) -> String"));
1607 assert!(output.contains("{ ... }"));
1608 assert!(!output.contains("let greeting"));
1609 }
1610 CompressResult::Fallback(_, reason) => {
1611 panic!("Expected compression, got fallback: {:?}", reason)
1612 }
1613 }
1614 }
1615
1616 #[test]
1617 fn test_compress_rust_struct() {
1618 let source = r#"pub struct Config {
1619 pub path: String,
1620 pub verbose: bool,
1621}"#;
1622 match compress_source(source, CompressLanguage::Rust) {
1623 CompressResult::Compressed(output) => {
1624 assert!(output.contains("pub struct Config"));
1625 assert!(output.contains("pub path: String"));
1626 assert!(output.contains("pub verbose: bool"));
1627 }
1628 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1629 }
1630 }
1631
1632 #[test]
1633 fn test_compress_rust_impl() {
1634 let source = r#"impl Config {
1635 pub fn new() -> Self {
1636 Self { path: String::new(), verbose: false }
1637 }
1638
1639 pub fn validate(&self) -> bool {
1640 !self.path.is_empty()
1641 }
1642}"#;
1643 match compress_source(source, CompressLanguage::Rust) {
1644 CompressResult::Compressed(output) => {
1645 assert!(output.contains("impl Config"));
1646 assert!(output.contains("pub fn new() -> Self { ... }"));
1647 assert!(output.contains("pub fn validate(&self) -> bool { ... }"));
1648 assert!(!output.contains("is_empty"));
1649 }
1650 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1651 }
1652 }
1653
1654 #[test]
1655 fn test_compress_rust_use_and_const() {
1656 let source = r#"use std::path::Path;
1657use std::collections::HashMap;
1658
1659const MAX_SIZE: usize = 1024;
1660
1661fn process() {
1662 // complex logic
1663 println!("processing");
1664}"#;
1665 match compress_source(source, CompressLanguage::Rust) {
1666 CompressResult::Compressed(output) => {
1667 assert!(output.contains("use std::path::Path;"));
1668 assert!(output.contains("use std::collections::HashMap;"));
1669 assert!(output.contains("const MAX_SIZE: usize = 1024;"));
1670 assert!(output.contains("fn process() { ... }"));
1671 }
1672 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1673 }
1674 }
1675
1676 #[test]
1677 fn test_compress_rust_trait() {
1678 let source = r#"pub trait Compressor {
1679 fn name(&self) -> &str;
1680 fn compress(&self, source: &str) -> String {
1681 source.to_string()
1682 }
1683}"#;
1684 match compress_source(source, CompressLanguage::Rust) {
1685 CompressResult::Compressed(output) => {
1686 assert!(output.contains("pub trait Compressor"));
1687 assert!(output.contains("fn name(&self) -> &str;"));
1688 assert!(output.contains("fn compress(&self, source: &str) -> String { ... }"));
1689 }
1690 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1691 }
1692 }
1693
1694 #[test]
1696 fn test_compress_typescript_function() {
1697 let source = r#"import { Config } from './config';
1698
1699function processData(data: string[]): number {
1700 const filtered = data.filter(x => x.length > 0);
1701 return filtered.length;
1702}
1703
1704export default processData;"#;
1705 match compress_source(source, CompressLanguage::TypeScript) {
1706 CompressResult::Compressed(output) => {
1707 assert!(output.contains("import { Config }"));
1708 assert!(output.contains("function processData(data: string[]): number { ... }"));
1709 assert!(output.contains("export default processData;"));
1710 assert!(!output.contains("filtered"));
1711 }
1712 CompressResult::Fallback(_, reason) => {
1713 panic!("Expected compression, got fallback: {:?}", reason)
1714 }
1715 }
1716 }
1717
1718 #[test]
1719 fn test_compress_typescript_class() {
1720 let source = r#"class UserService {
1721 private db: Database;
1722
1723 constructor(db: Database) {
1724 this.db = db;
1725 }
1726
1727 async getUser(id: string): Promise<User> {
1728 const user = await this.db.find(id);
1729 if (!user) throw new Error('Not found');
1730 return user;
1731 }
1732}"#;
1733 match compress_source(source, CompressLanguage::TypeScript) {
1734 CompressResult::Compressed(output) => {
1735 assert!(output.contains("class UserService"));
1736 assert!(output.contains("{ ... }"));
1737 assert!(!output.contains("throw new Error"));
1738 }
1739 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1740 }
1741 }
1742
1743 #[test]
1744 fn test_compress_typescript_interface() {
1745 let source = r#"interface User {
1746 id: string;
1747 name: string;
1748 email: string;
1749}"#;
1750 match compress_source(source, CompressLanguage::TypeScript) {
1751 CompressResult::Compressed(output) => {
1752 assert!(output.contains("interface User"));
1753 assert!(output.contains("id: string"));
1754 }
1755 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1756 }
1757 }
1758
1759 #[test]
1761 fn test_compress_python_function() {
1762 let source = r#"import os
1763from pathlib import Path
1764
1765def process_file(path: str) -> bool:
1766 """Process a single file."""
1767 content = Path(path).read_text()
1768 lines = content.splitlines()
1769 return len(lines) > 0"#;
1770 match compress_source(source, CompressLanguage::Python) {
1771 CompressResult::Compressed(output) => {
1772 assert!(output.contains("import os"));
1773 assert!(output.contains("from pathlib import Path"));
1774 assert!(output.contains("def process_file(path: str) -> bool:"));
1775 assert!(output.contains("\"\"\"Process a single file.\"\"\""));
1776 assert!(output.contains("..."));
1777 assert!(!output.contains("splitlines"));
1778 }
1779 CompressResult::Fallback(_, reason) => {
1780 panic!("Expected compression, got fallback: {:?}", reason)
1781 }
1782 }
1783 }
1784
1785 #[test]
1786 fn test_compress_python_class() {
1787 let source = r#"class Config:
1788 """Configuration container."""
1789 DEFAULT_SIZE = 1024
1790
1791 def __init__(self, path: str):
1792 self.path = path
1793 self.size = self.DEFAULT_SIZE
1794
1795 def validate(self) -> bool:
1796 return os.path.exists(self.path)"#;
1797 match compress_source(source, CompressLanguage::Python) {
1798 CompressResult::Compressed(output) => {
1799 assert!(output.contains("class Config:"));
1800 assert!(output.contains("\"\"\"Configuration container.\"\"\""));
1801 assert!(output.contains("DEFAULT_SIZE = 1024"));
1802 assert!(output.contains("def __init__(self, path: str):"));
1803 assert!(output.contains("def validate(self) -> bool:"));
1804 assert!(!output.contains("os.path.exists"));
1805 }
1806 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1807 }
1808 }
1809
1810 #[test]
1812 fn test_compress_go_function() {
1813 let source = r#"package main
1814
1815import "fmt"
1816
1817// ProcessData handles incoming data
1818func ProcessData(data []string) int {
1819 filtered := make([]string, 0)
1820 for _, d := range data {
1821 if len(d) > 0 {
1822 filtered = append(filtered, d)
1823 }
1824 }
1825 return len(filtered)
1826}"#;
1827 match compress_source(source, CompressLanguage::Go) {
1828 CompressResult::Compressed(output) => {
1829 assert!(output.contains("package main"));
1830 assert!(output.contains("import \"fmt\""));
1831 assert!(output.contains("// ProcessData handles incoming data"));
1832 assert!(output.contains("func ProcessData(data []string) int { ... }"));
1833 assert!(!output.contains("filtered"));
1834 }
1835 CompressResult::Fallback(_, reason) => {
1836 panic!("Expected compression, got fallback: {:?}", reason)
1837 }
1838 }
1839 }
1840
1841 #[test]
1842 fn test_compress_go_struct_and_method() {
1843 let source = r#"package main
1844
1845type Config struct {
1846 Path string
1847 Verbose bool
1848}
1849
1850func (c *Config) Validate() bool {
1851 return c.Path != ""
1852}"#;
1853 match compress_source(source, CompressLanguage::Go) {
1854 CompressResult::Compressed(output) => {
1855 assert!(output.contains("type Config struct"));
1856 assert!(output.contains("Path string"));
1857 assert!(output.contains("func (c *Config) Validate() bool { ... }"));
1858 }
1859 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1860 }
1861 }
1862
1863 #[test]
1865 fn test_compress_empty_source() {
1866 match compress_source("", CompressLanguage::Rust) {
1867 CompressResult::Compressed(output) => assert!(output.is_empty()),
1868 CompressResult::Fallback(_, _) => panic!("Empty source should return empty compressed"),
1869 }
1870 }
1871
1872 #[test]
1873 fn test_compress_bom_stripped() {
1874 let source = "\u{FEFF}fn main() {\n println!(\"hello\");\n}";
1875 match compress_source(source, CompressLanguage::Rust) {
1876 CompressResult::Compressed(output) => {
1877 assert!(!output.starts_with('\u{FEFF}'));
1878 assert!(output.contains("fn main()"));
1879 }
1880 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1881 }
1882 }
1883
1884 #[test]
1885 fn test_compress_only_comments() {
1886 let source = "// This is a comment\n// Another comment\n";
1887 match compress_source(source, CompressLanguage::Rust) {
1888 CompressResult::Compressed(output) => {
1889 assert!(output.contains("// This is a comment"));
1890 assert!(output.contains("// Another comment"));
1891 }
1892 CompressResult::Fallback(_, _) => {
1893 panic!("Comments-only should compress (keeping comments)")
1894 }
1895 }
1896 }
1897
1898 #[test]
1899 fn test_compress_typescript_export_function() {
1900 let source = r#"import { Config } from './config';
1901
1902export function processData(data: string[]): number {
1903 const filtered = data.filter(x => x.length > 0);
1904 return filtered.length;
1905}"#;
1906 match compress_source(source, CompressLanguage::TypeScript) {
1907 CompressResult::Compressed(output) => {
1908 assert!(output.contains("import { Config }"));
1909 assert!(
1910 output.contains("export function processData(data: string[]): number { ... }"),
1911 "export function should be compressed, got: {}",
1912 output
1913 );
1914 assert!(
1915 !output.contains("filtered"),
1916 "export function body should be stripped"
1917 );
1918 }
1919 CompressResult::Fallback(_, reason) => {
1920 panic!("Expected compression, got fallback: {:?}", reason)
1921 }
1922 }
1923 }
1924
1925 #[test]
1926 fn test_compress_typescript_export_class() {
1927 let source = r#"export class UserService {
1928 private db: Database;
1929
1930 constructor(db: Database) {
1931 this.db = db;
1932 }
1933
1934 async getUser(id: string): Promise<User> {
1935 const user = await this.db.find(id);
1936 return user;
1937 }
1938}"#;
1939 match compress_source(source, CompressLanguage::TypeScript) {
1940 CompressResult::Compressed(output) => {
1941 assert!(
1942 output.contains("export class UserService"),
1943 "export class should be preserved"
1944 );
1945 assert!(
1946 output.contains("{ ... }"),
1947 "method bodies should be compressed"
1948 );
1949 assert!(
1950 !output.contains("await this.db.find"),
1951 "method body should be stripped"
1952 );
1953 }
1954 CompressResult::Fallback(_, _) => panic!("Expected compression"),
1955 }
1956 }
1957
1958 #[test]
1959 fn test_compress_python_module_constant() {
1960 let source = "MAX_RETRIES = 3\nDEBUG = True\n\ndef run():\n print('running')\n";
1961 match compress_source(source, CompressLanguage::Python) {
1962 CompressResult::Compressed(output) => {
1963 assert!(
1964 output.contains("MAX_RETRIES = 3"),
1965 "Module-level constant should be preserved, got: {}",
1966 output
1967 );
1968 assert!(
1969 output.contains("DEBUG = True"),
1970 "Module-level boolean constant should be preserved"
1971 );
1972 assert!(output.contains("def run():"));
1973 assert!(
1974 !output.contains("print('running')"),
1975 "Function body should be stripped"
1976 );
1977 }
1978 CompressResult::Fallback(_, reason) => {
1979 panic!("Expected compression, got fallback: {:?}", reason)
1980 }
1981 }
1982 }
1983
1984 #[test]
1986 fn test_compress_java_class_with_methods() {
1987 let source = r#"package com.example;
1988
1989import java.util.List;
1990
1991public class UserService {
1992 private final Database db;
1993
1994 public UserService(Database db) {
1995 this.db = db;
1996 }
1997
1998 public User getUser(String id) {
1999 User user = db.find(id);
2000 if (user == null) {
2001 throw new RuntimeException("Not found");
2002 }
2003 return user;
2004 }
2005
2006 public List<User> listUsers() {
2007 return db.findAll();
2008 }
2009}"#;
2010 match compress_source(source, CompressLanguage::Java) {
2011 CompressResult::Compressed(output) => {
2012 assert!(output.contains("package com.example;"));
2013 assert!(output.contains("import java.util.List;"));
2014 assert!(output.contains("public class UserService"));
2015 assert!(output.contains("private final Database db;"));
2016 assert!(output.contains("public UserService(Database db) { ... }"));
2017 assert!(output.contains("public User getUser(String id) { ... }"));
2018 assert!(output.contains("public List<User> listUsers() { ... }"));
2019 assert!(!output.contains("throw new RuntimeException"));
2020 }
2021 CompressResult::Fallback(_, reason) => {
2022 panic!("Expected compression, got fallback: {:?}", reason)
2023 }
2024 }
2025 }
2026
2027 #[test]
2028 fn test_compress_java_interface() {
2029 let source = r#"public interface Repository<T> {
2030 T findById(String id);
2031 List<T> findAll();
2032 void save(T entity);
2033}"#;
2034 match compress_source(source, CompressLanguage::Java) {
2035 CompressResult::Compressed(output) => {
2036 assert!(output.contains("public interface Repository<T>"));
2037 assert!(output.contains("T findById(String id);"));
2038 assert!(output.contains("void save(T entity);"));
2039 }
2040 CompressResult::Fallback(_, _) => panic!("Expected compression"),
2041 }
2042 }
2043
2044 #[test]
2046 fn test_compress_csharp_class_with_methods() {
2047 let source = r#"using System;
2048using System.Collections.Generic;
2049
2050namespace MyApp.Services
2051{
2052 public class UserService
2053 {
2054 private readonly IDatabase _db;
2055
2056 public UserService(IDatabase db)
2057 {
2058 _db = db;
2059 }
2060
2061 public User GetUser(string id)
2062 {
2063 var user = _db.Find(id);
2064 if (user == null)
2065 throw new Exception("Not found");
2066 return user;
2067 }
2068 }
2069}"#;
2070 match compress_source(source, CompressLanguage::CSharp) {
2071 CompressResult::Compressed(output) => {
2072 assert!(output.contains("using System;"));
2073 assert!(output.contains("namespace MyApp.Services"));
2074 assert!(output.contains("public class UserService"));
2075 assert!(output.contains("public UserService(IDatabase db) { ... }"));
2076 assert!(output.contains("public User GetUser(string id) { ... }"));
2077 assert!(!output.contains("throw new Exception"));
2078 }
2079 CompressResult::Fallback(_, reason) => {
2080 panic!("Expected compression, got fallback: {:?}", reason)
2081 }
2082 }
2083 }
2084
2085 #[test]
2086 fn test_compress_csharp_interface() {
2087 let source = r#"public interface IRepository<T>
2088{
2089 T FindById(string id);
2090 IList<T> FindAll();
2091 void Save(T entity);
2092}"#;
2093 match compress_source(source, CompressLanguage::CSharp) {
2094 CompressResult::Compressed(output) => {
2095 assert!(output.contains("public interface IRepository<T>"));
2096 assert!(output.contains("T FindById(string id);"));
2097 }
2098 CompressResult::Fallback(_, _) => panic!("Expected compression"),
2099 }
2100 }
2101
2102 #[test]
2104 fn test_compress_c_function() {
2105 let source = r#"#include <stdio.h>
2106#include <stdlib.h>
2107
2108#define MAX_SIZE 1024
2109
2110typedef struct {
2111 int x;
2112 int y;
2113} Point;
2114
2115int process_data(const char *input, int length) {
2116 char *buffer = malloc(length);
2117 if (!buffer) return -1;
2118 memcpy(buffer, input, length);
2119 int result = compute(buffer, length);
2120 free(buffer);
2121 return result;
2122}"#;
2123 match compress_source(source, CompressLanguage::C) {
2124 CompressResult::Compressed(output) => {
2125 assert!(output.contains("#include <stdio.h>"));
2126 assert!(output.contains("#define MAX_SIZE 1024"));
2127 assert!(output.contains("typedef struct"));
2128 assert!(output.contains("int process_data(const char *input, int length) { ... }"));
2129 assert!(!output.contains("malloc"));
2130 }
2131 CompressResult::Fallback(_, reason) => {
2132 panic!("Expected compression, got fallback: {:?}", reason)
2133 }
2134 }
2135 }
2136
2137 #[test]
2138 fn test_compress_c_header() {
2139 let source = r#"#ifndef MYLIB_H
2140#define MYLIB_H
2141
2142typedef struct Node {
2143 int value;
2144 struct Node *next;
2145} Node;
2146
2147int process(const char *input);
2148void cleanup(Node *head);
2149
2150#endif"#;
2151 match compress_source(source, CompressLanguage::C) {
2152 CompressResult::Compressed(output) => {
2153 assert!(output.contains("#ifndef MYLIB_H"));
2154 assert!(output.contains("typedef struct Node"));
2155 assert!(output.contains("int process(const char *input);"));
2156 }
2157 CompressResult::Fallback(_, _) => panic!("Expected compression"),
2158 }
2159 }
2160
2161 #[test]
2163 fn test_compress_cpp_class() {
2164 let source = r#"#include <string>
2165#include <vector>
2166
2167namespace mylib {
2168
2169class UserService {
2170public:
2171 UserService(Database& db) : db_(db) {
2172 initialized_ = true;
2173 }
2174
2175 User getUser(const std::string& id) {
2176 auto user = db_.find(id);
2177 if (!user) throw std::runtime_error("not found");
2178 return *user;
2179 }
2180
2181private:
2182 Database& db_;
2183 bool initialized_;
2184};
2185
2186}"#;
2187 match compress_source(source, CompressLanguage::Cpp) {
2188 CompressResult::Compressed(output) => {
2189 assert!(output.contains("#include <string>"));
2190 assert!(output.contains("namespace mylib"));
2191 assert!(output.contains("class UserService"));
2192 assert!(output.contains("{ ... }"));
2193 assert!(!output.contains("throw std::runtime_error"));
2194 }
2195 CompressResult::Fallback(_, reason) => {
2196 panic!("Expected compression, got fallback: {:?}", reason)
2197 }
2198 }
2199 }
2200
2201 #[test]
2202 fn test_compress_cpp_template_function() {
2203 let source = r#"template<typename T>
2204T max_value(T a, T b) {
2205 return (a > b) ? a : b;
2206}"#;
2207 match compress_source(source, CompressLanguage::Cpp) {
2208 CompressResult::Compressed(output) => {
2209 assert!(output.contains("template<typename T>"));
2210 assert!(output.contains("T max_value(T a, T b) { ... }"));
2211 assert!(!output.contains("return"));
2212 }
2213 CompressResult::Fallback(_, reason) => {
2214 panic!("Expected compression, got fallback: {:?}", reason)
2215 }
2216 }
2217 }
2218
2219 #[test]
2221 fn test_compress_ruby_class() {
2222 let source = r#"require 'json'
2223
2224class UserService
2225 attr_reader :db
2226
2227 def initialize(db)
2228 @db = db
2229 @cache = {}
2230 end
2231
2232 def find_user(id)
2233 return @cache[id] if @cache.key?(id)
2234 user = @db.find(id)
2235 @cache[id] = user
2236 user
2237 end
2238end"#;
2239 match compress_source(source, CompressLanguage::Ruby) {
2240 CompressResult::Compressed(output) => {
2241 assert!(output.contains("require 'json'"));
2242 assert!(output.contains("class UserService"));
2243 assert!(output.contains("attr_reader :db"));
2244 assert!(output.contains("def initialize(db)"));
2245 assert!(output.contains("..."));
2246 assert!(output.contains("def find_user(id)"));
2247 assert!(!output.contains("@cache[id] = user"));
2248 assert!(output.contains("end"));
2249 }
2250 CompressResult::Fallback(_, reason) => {
2251 panic!("Expected compression, got fallback: {:?}", reason)
2252 }
2253 }
2254 }
2255
2256 #[test]
2257 fn test_compress_ruby_module() {
2258 let source = r#"module Validators
2259 def self.validate_email(email)
2260 email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
2261 end
2262
2263 def self.validate_name(name)
2264 name.length >= 2 && name.length <= 100
2265 end
2266end"#;
2267 match compress_source(source, CompressLanguage::Ruby) {
2268 CompressResult::Compressed(output) => {
2269 assert!(output.contains("module Validators"));
2270 assert!(output.contains("def self.validate_email(email)"));
2271 assert!(output.contains("def self.validate_name(name)"));
2272 assert!(!output.contains("match?"));
2273 }
2274 CompressResult::Fallback(_, reason) => {
2275 panic!("Expected compression, got fallback: {:?}", reason)
2276 }
2277 }
2278 }
2279
2280 #[test]
2282 fn test_compress_php_class() {
2283 let source = r#"<?php
2284
2285namespace App\Services;
2286
2287use App\Models\User;
2288
2289class UserService
2290{
2291 private $db;
2292
2293 public function __construct(Database $db)
2294 {
2295 $this->db = $db;
2296 }
2297
2298 public function getUser(string $id): User
2299 {
2300 $user = $this->db->find($id);
2301 if (!$user) {
2302 throw new \Exception('Not found');
2303 }
2304 return $user;
2305 }
2306}"#;
2307 match compress_source(source, CompressLanguage::Php) {
2308 CompressResult::Compressed(output) => {
2309 assert!(output.contains("<?php"));
2310 assert!(output.contains("namespace App\\Services;"));
2311 assert!(output.contains("use App\\Models\\User;"));
2312 assert!(output.contains("class UserService"));
2313 assert!(output.contains("public function __construct(Database $db) { ... }"));
2314 assert!(output.contains("public function getUser(string $id): User { ... }"));
2315 assert!(!output.contains("throw new"));
2316 }
2317 CompressResult::Fallback(_, reason) => {
2318 panic!("Expected compression, got fallback: {:?}", reason)
2319 }
2320 }
2321 }
2322
2323 #[test]
2324 fn test_compress_php_function() {
2325 let source = r#"<?php
2326
2327function processData(array $items): int
2328{
2329 $count = 0;
2330 foreach ($items as $item) {
2331 if ($item->isValid()) {
2332 $count++;
2333 }
2334 }
2335 return $count;
2336}"#;
2337 match compress_source(source, CompressLanguage::Php) {
2338 CompressResult::Compressed(output) => {
2339 assert!(output.contains("<?php"));
2340 assert!(output.contains("function processData(array $items): int { ... }"));
2341 assert!(!output.contains("foreach"));
2342 }
2343 CompressResult::Fallback(_, reason) => {
2344 panic!("Expected compression, got fallback: {:?}", reason)
2345 }
2346 }
2347 }
2348
2349 #[test]
2351 fn test_language_for_extension_new_languages() {
2352 assert_eq!(language_for_extension("java"), Some(CompressLanguage::Java));
2353 assert_eq!(language_for_extension("cs"), Some(CompressLanguage::CSharp));
2354 assert_eq!(language_for_extension("c"), Some(CompressLanguage::C));
2355 assert_eq!(language_for_extension("h"), Some(CompressLanguage::C));
2356 assert_eq!(language_for_extension("cpp"), Some(CompressLanguage::Cpp));
2357 assert_eq!(language_for_extension("cc"), Some(CompressLanguage::Cpp));
2358 assert_eq!(language_for_extension("cxx"), Some(CompressLanguage::Cpp));
2359 assert_eq!(language_for_extension("hpp"), Some(CompressLanguage::Cpp));
2360 assert_eq!(language_for_extension("hh"), Some(CompressLanguage::Cpp));
2361 assert_eq!(language_for_extension("hxx"), Some(CompressLanguage::Cpp));
2362 assert_eq!(language_for_extension("rb"), Some(CompressLanguage::Ruby));
2363 assert_eq!(language_for_extension("php"), Some(CompressLanguage::Php));
2364 }
2365
2366 #[test]
2368 fn test_compress_java_enum_with_constants() {
2369 let source = r#"public enum Color {
2370 RED("red"),
2371 GREEN("green"),
2372 BLUE("blue");
2373
2374 private final String code;
2375
2376 Color(String code) {
2377 this.code = code;
2378 }
2379
2380 public String getCode() {
2381 return this.code;
2382 }
2383}"#;
2384 match compress_source(source, CompressLanguage::Java) {
2385 CompressResult::Compressed(output) => {
2386 assert!(
2387 output.contains("RED(\"red\")"),
2388 "Enum constant RED should be preserved, got: {}",
2389 output
2390 );
2391 assert!(
2392 output.contains("GREEN(\"green\")"),
2393 "Enum constant GREEN should be preserved"
2394 );
2395 assert!(
2396 output.contains("BLUE(\"blue\")"),
2397 "Enum constant BLUE should be preserved"
2398 );
2399 assert!(
2400 output.contains("private final String code;"),
2401 "Enum field should be preserved"
2402 );
2403 assert!(
2404 output.contains("Color(String code) { ... }"),
2405 "Enum constructor should be compressed, got: {}",
2406 output
2407 );
2408 assert!(
2409 output.contains("public String getCode() { ... }"),
2410 "Enum method should be compressed, got: {}",
2411 output
2412 );
2413 assert!(
2414 !output.contains("return this.code"),
2415 "Method body should be stripped"
2416 );
2417 }
2418 CompressResult::Fallback(_, reason) => {
2419 panic!("Expected compression, got fallback: {:?}", reason)
2420 }
2421 }
2422 }
2423
2424 #[test]
2425 fn test_compress_php_enum_with_cases() {
2426 let source = r#"<?php
2427
2428enum Suit: string
2429{
2430 case Hearts = 'H';
2431 case Diamonds = 'D';
2432 case Clubs = 'C';
2433 case Spades = 'S';
2434
2435 public function color(): string
2436 {
2437 return match($this) {
2438 self::Hearts, self::Diamonds => 'red',
2439 self::Clubs, self::Spades => 'black',
2440 };
2441 }
2442}"#;
2443 match compress_source(source, CompressLanguage::Php) {
2444 CompressResult::Compressed(output) => {
2445 assert!(
2446 output.contains("case Hearts = 'H';"),
2447 "Enum case should be preserved, got: {}",
2448 output
2449 );
2450 assert!(
2451 output.contains("case Spades = 'S';"),
2452 "Enum case should be preserved"
2453 );
2454 assert!(
2455 output.contains("public function color(): string { ... }"),
2456 "Enum method should be compressed, got: {}",
2457 output
2458 );
2459 assert!(!output.contains("match("), "Method body should be stripped");
2460 }
2461 CompressResult::Fallback(_, reason) => {
2462 panic!("Expected compression, got fallback: {:?}", reason)
2463 }
2464 }
2465 }
2466
2467 #[test]
2468 fn test_compress_cpp_class_with_preproc() {
2469 let source = r#"class Config {
2470public:
2471 Config() {}
2472
2473 std::string getName() const {
2474 return name_;
2475 }
2476
2477#ifdef DEBUG
2478 void debugPrint() {
2479 std::cout << name_ << std::endl;
2480 }
2481#endif
2482
2483private:
2484 std::string name_;
2485};"#;
2486 match compress_source(source, CompressLanguage::Cpp) {
2487 CompressResult::Compressed(output) => {
2488 assert!(output.contains("class Config"));
2489 assert!(
2490 output.contains("#ifdef DEBUG"),
2491 "Preprocessor directive inside class should be preserved, got: {}",
2492 output
2493 );
2494 assert!(
2495 output.contains("#endif"),
2496 "Preprocessor endif should be preserved"
2497 );
2498 assert!(output.contains("std::string name_;"));
2499 }
2500 CompressResult::Fallback(_, reason) => {
2501 panic!("Expected compression, got fallback: {:?}", reason)
2502 }
2503 }
2504 }
2505
2506 #[test]
2507 fn test_compress_csharp_property() {
2508 let source = r#"public class Person
2509{
2510 public string Name { get; set; }
2511 public int Age { get; set; }
2512
2513 public string GetGreeting()
2514 {
2515 return $"Hello, {Name}!";
2516 }
2517}"#;
2518 match compress_source(source, CompressLanguage::CSharp) {
2519 CompressResult::Compressed(output) => {
2520 assert!(output.contains("public class Person"));
2521 assert!(
2522 output.contains("Name"),
2523 "Property name should be preserved, got: {}",
2524 output
2525 );
2526 assert!(output.contains("Age"), "Property name should be preserved");
2527 assert!(output.contains("public string GetGreeting() { ... }"));
2528 assert!(!output.contains("Hello, {Name}"));
2529 }
2530 CompressResult::Fallback(_, reason) => {
2531 panic!("Expected compression, got fallback: {:?}", reason)
2532 }
2533 }
2534 }
2535
2536 #[test]
2537 fn test_compress_rust_syntax_error_fallback() {
2538 let source = "fn broken( {\n this is not valid rust\n}\n";
2540 match compress_source(source, CompressLanguage::Rust) {
2541 CompressResult::Compressed(_) => {
2542 panic!("Syntax error should produce fallback, not compressed")
2543 }
2544 CompressResult::Fallback(content, reason) => {
2545 assert_eq!(content, source, "Fallback should return original content");
2546 assert!(reason.is_some(), "Fallback should include a warning reason");
2547 assert!(
2548 reason.unwrap().contains("ERROR"),
2549 "Reason should mention ERROR nodes"
2550 );
2551 }
2552 }
2553 }
2554
2555 #[test]
2557 fn test_compress_solidity_contract() {
2558 let source = r#"pragma solidity ^0.8.0;
2559
2560contract Test {
2561 uint256 public x;
2562
2563 event Transfer(address indexed from, uint256 amount);
2564
2565 function transfer(address to, uint256 amount) public {
2566 x += amount;
2567 emit Transfer(to, amount);
2568 }
2569}"#;
2570 match compress_source(source, CompressLanguage::Solidity) {
2571 CompressResult::Compressed(output) => {
2572 assert!(output.contains("pragma solidity"));
2573 assert!(output.contains("uint256 public x;"));
2574 assert!(output.contains("event Transfer"));
2575 assert!(output.contains("function transfer"));
2576 assert!(output.contains("{ ... }"));
2577 assert!(!output.contains("x += amount"));
2578 }
2579 CompressResult::Fallback(_, reason) => {
2580 panic!("Expected compression, got fallback: {:?}", reason)
2581 }
2582 }
2583 }
2584
2585 #[test]
2587 fn test_compress_elixir_module() {
2588 let source = r#"defmodule MyApp do
2589 alias MyApp.Repo
2590 use GenServer
2591
2592 @moduledoc "My application"
2593
2594 def start_link(opts) do
2595 GenServer.start_link(__MODULE__, opts, name: __MODULE__)
2596 end
2597
2598 defp handle_info(msg, state) do
2599 {:noreply, state}
2600 end
2601end"#;
2602 match compress_source(source, CompressLanguage::Elixir) {
2603 CompressResult::Compressed(output) => {
2604 assert!(output.contains("defmodule MyApp"));
2605 assert!(output.contains("alias MyApp.Repo"));
2606 assert!(output.contains("use GenServer"));
2607 assert!(output.contains("@moduledoc"));
2608 assert!(output.contains("def start_link"));
2609 assert!(output.contains("{ ... }"));
2610 assert!(!output.contains("GenServer.start_link"));
2611 }
2612 CompressResult::Fallback(_, reason) => {
2613 panic!("Expected compression, got fallback: {:?}", reason)
2614 }
2615 }
2616 }
2617
2618}