Skip to main content

flat/
compress.rs

1use std::path::Path;
2use tree_sitter::{Language, Parser};
3use tree_sitter_solidity;
4use tree_sitter_elixir;
5
6/// Languages supported for compression
7#[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
26/// Map a file extension to a compressible language
27pub 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
48/// Detect language from a file path's extension
49pub 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
55/// Get the tree-sitter Language for a CompressLanguage
56fn 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/// Result of compressing a source file
77#[derive(Debug)]
78pub enum CompressResult {
79    /// Successfully compressed
80    Compressed(String),
81    /// Fell back to full content (with optional reason for stderr warning)
82    Fallback(String, Option<String>),
83}
84
85/// Strip UTF-8 BOM if present
86fn strip_bom(source: &str) -> &str {
87    source.strip_prefix('\u{FEFF}').unwrap_or(source)
88}
89
90/// Compress a source file by extracting declarations and signatures.
91///
92/// Returns compressed output or falls back to full content per the fallback rules:
93/// - Unsupported extension → full content
94/// - Parse error (NULL tree) → full content + warn
95/// - ERROR nodes in parse tree → full content + warn
96/// - Empty compressed output → full content + warn
97/// - Compressed ≥ original → full content (no warning)
98/// - tree-sitter panic → full content + warn (catch_unwind)
99pub 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    // Wrap tree-sitter calls in catch_unwind to prevent panics from crashing the process
107    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
121/// Inner compression logic, separated so catch_unwind can wrap it
122fn 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    // Check for ERROR nodes
146    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
185/// Recursively check if the parse tree contains any ERROR nodes
186fn 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
199/// Extract the text of a node from source
200fn node_text<'a>(source: &'a str, node: tree_sitter::Node) -> &'a str {
201    &source[node.byte_range()]
202}
203
204/// Replace a function/method body with `{ ... }`, keeping the signature.
205///
206/// Searches for the first child matching any of `body_kinds` and replaces it.
207/// Falls back to the full node text if no matching body child is found.
208fn 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
221/// Append a single line with indentation to an output string.
222fn 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
228/// Append a multi-line block with indentation to an output string.
229fn 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
237// ============================================================================
238// Rust Compressor
239// ============================================================================
240
241fn 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
351// ============================================================================
352// TypeScript/JavaScript Compressor
353// ============================================================================
354
355fn 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    // For arrow functions and complex initializers, try to compress
434    let text = node_text(source, node);
435    if text.contains("=>") && text.len() > 80 {
436        // Try to find arrow function body and compress it
437        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    // Walk to find arrow_function children with statement_block bodies
451    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                // Find the statement_block in the function
484                let mut fcursor = inner.walk();
485                for fchild in inner.children(&mut fcursor) {
486                    if fchild.kind() == "statement_block" {
487                        // Everything from export start to the body start is the signature
488                        let sig = source[node.start_byte()..fchild.start_byte()].trim_end();
489                        return format!("{} {{ ... }}", sig);
490                    }
491                }
492                // No body found, keep as-is
493                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    // No compressible child found, keep verbatim
503    node_text(source, node).to_string()
504}
505
506// ============================================================================
507// Python Compressor
508// ============================================================================
509
510fn 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            // Imports
517            "import_statement" | "import_from_statement" | "future_import_statement" => {
518                output.push_str(node_text(source, child));
519                output.push('\n');
520            }
521            // Comments
522            "comment" => {
523                output.push_str(node_text(source, child));
524                output.push('\n');
525            }
526            // Expression statements (docstrings and assignments at module level)
527            "expression_statement" => {
528                let text = node_text(source, child);
529                // Keep module-level docstrings
530                if text.starts_with("\"\"\"") || text.starts_with("'''") {
531                    output.push_str(text);
532                    output.push('\n');
533                } else {
534                    // Keep simple assignments (e.g., MAX_RETRIES = 3)
535                    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 definitions
546            "function_definition" | "decorated_definition" => {
547                output.push_str(&compress_python_function(source, child));
548                output.push('\n');
549            }
550            // Class definitions
551            "class_definition" => {
552                output.push_str(&compress_python_class(source, child));
553                output.push('\n');
554            }
555            // Global variable assignments at module level
556            "assignment" => {
557                let text = node_text(source, child);
558                // Keep type-annotated assignments and simple constants
559                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    // Handle decorated functions
575    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            // Check for docstring (first statement only)
607            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                        // Indent the compressed function
638                        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                        // Keep docstrings and assignments (class-level vars)
648                        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
673// ============================================================================
674// Go Compressor
675// ============================================================================
676
677fn 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
699// ============================================================================
700// Java Compressor
701// ============================================================================
702
703fn 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                        // In Java enums, fields/methods are wrapped in this node
763                        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
808// ============================================================================
809// C# Compressor
810// ============================================================================
811
812fn 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
932// ============================================================================
933// C Compressor
934// ============================================================================
935
936fn 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
969// ============================================================================
970// C++ Compressor
971// ============================================================================
972
973fn 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
1177// ============================================================================
1178// Ruby Compressor
1179// ============================================================================
1180
1181fn 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
1271// ============================================================================
1272// PHP Compressor
1273// ============================================================================
1274
1275fn 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    // Statement form: namespace Foo;
1344    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
1384// ============================================================================
1385// Solidity Compressor
1386// ============================================================================
1387
1388fn 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            // Write contract header
1416            output.push_str(source[node.start_byte()..child.start_byte()].trim_end());
1417            output.push_str(" {\n");
1418
1419            // Process contract members
1420            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
1442// ============================================================================
1443// Elixir Compressor
1444// ============================================================================
1445
1446fn 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                // Module attributes like @moduledoc
1464                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    // Compress function bodies
1502    if matches!(def_name.as_str(), "def" | "defp" | "defmacro" | "defmacrop") {
1503        return compress_body(source, node, &["do_block"]);
1504    }
1505
1506    // Expand modules and process children
1507    if matches!(def_name.as_str(), "defmodule" | "defprotocol" | "defimpl") {
1508        return compress_elixir_module(source, node);
1509    }
1510
1511    // Keep other definitions as-is
1512    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// ============================================================================
1552// Tests
1553// ============================================================================
1554
1555#[cfg(test)]
1556mod tests {
1557    use super::*;
1558
1559    // Language detection tests
1560    #[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    // Rust compression tests
1597    #[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    // TypeScript compression tests
1695    #[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    // Python compression tests
1760    #[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    // Go compression tests
1811    #[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    // Fallback tests
1864    #[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    // Java compression tests
1985    #[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    // C# compression tests
2045    #[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    // C compression tests
2103    #[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    // C++ compression tests
2162    #[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    // Ruby compression tests
2220    #[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    // PHP compression tests
2281    #[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    // Extension mapping tests for new languages
2350    #[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    // Edge case tests found during QA review
2367    #[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        // Source with syntax errors should fall back to full content
2539        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    // Solidity compression tests
2556    #[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    // Elixir compression tests
2586    #[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}