Skip to main content

verificar/grammar/
typescript.rs

1//! TypeScript grammar definition
2//!
3//! Grammar rules for TypeScript code generation, targeting decy transpilation.
4//! Uses tree-sitter for proper AST validation when the `tree-sitter` feature is enabled.
5
6use crate::Language;
7
8use super::Grammar;
9
10/// TypeScript grammar for code generation
11///
12/// When the `tree-sitter` feature is enabled, uses tree-sitter-typescript for
13/// proper syntax validation. Otherwise, falls back to basic heuristics.
14pub struct TypeScriptGrammar {
15    #[cfg(feature = "tree-sitter")]
16    parser: std::sync::Mutex<tree_sitter::Parser>,
17}
18
19impl std::fmt::Debug for TypeScriptGrammar {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_struct("TypeScriptGrammar")
22            .field("language", &"typescript")
23            .finish()
24    }
25}
26
27impl Default for TypeScriptGrammar {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl TypeScriptGrammar {
34    /// Create a new TypeScript grammar
35    ///
36    /// # Panics
37    ///
38    /// Panics if the tree-sitter TypeScript grammar fails to load (should never happen
39    /// with a correctly compiled tree-sitter-typescript dependency).
40    #[must_use]
41    #[allow(clippy::expect_used)]
42    pub fn new() -> Self {
43        #[cfg(feature = "tree-sitter")]
44        {
45            let mut parser = tree_sitter::Parser::new();
46            parser
47                .set_language(&tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into())
48                .expect("Failed to load TypeScript grammar");
49            Self {
50                parser: std::sync::Mutex::new(parser),
51            }
52        }
53        #[cfg(not(feature = "tree-sitter"))]
54        {
55            Self {}
56        }
57    }
58
59    /// Parse TypeScript code and return the AST tree
60    ///
61    /// Returns `None` if parsing fails or tree-sitter feature is disabled.
62    #[cfg(feature = "tree-sitter")]
63    pub fn parse(&self, code: &str) -> Option<tree_sitter::Tree> {
64        let mut parser = self.parser.lock().ok()?;
65        parser.parse(code, None)
66    }
67
68    /// Get the root node of parsed code
69    #[cfg(feature = "tree-sitter")]
70    pub fn root_node(&self, code: &str) -> Option<String> {
71        self.parse(code)
72            .map(|tree| tree.root_node().kind().to_string())
73    }
74
75    /// Check if the parsed code has any syntax errors
76    #[cfg(feature = "tree-sitter")]
77    pub fn has_errors(&self, code: &str) -> bool {
78        self.parse(code)
79            .map_or(true, |tree| tree.root_node().has_error())
80    }
81
82    /// Get the AST depth of parsed code
83    #[cfg(feature = "tree-sitter")]
84    pub fn ast_depth(&self, code: &str) -> usize {
85        fn max_depth(node: tree_sitter::Node<'_>) -> usize {
86            let child_depths = node
87                .children(&mut node.walk())
88                .map(max_depth)
89                .max()
90                .unwrap_or(0);
91            1 + child_depths
92        }
93
94        self.parse(code)
95            .map_or(0, |tree| max_depth(tree.root_node()))
96    }
97
98    /// Count the number of nodes in the AST
99    #[cfg(feature = "tree-sitter")]
100    pub fn node_count(&self, code: &str) -> usize {
101        fn count_nodes(node: tree_sitter::Node<'_>) -> usize {
102            1 + node
103                .children(&mut node.walk())
104                .map(count_nodes)
105                .sum::<usize>()
106        }
107
108        self.parse(code)
109            .map_or(0, |tree| count_nodes(tree.root_node()))
110    }
111}
112
113impl Grammar for TypeScriptGrammar {
114    fn language(&self) -> Language {
115        Language::TypeScript
116    }
117
118    fn validate(&self, code: &str) -> bool {
119        if code.is_empty() {
120            return false;
121        }
122
123        #[cfg(feature = "tree-sitter")]
124        {
125            !self.has_errors(code)
126        }
127
128        #[cfg(not(feature = "tree-sitter"))]
129        {
130            // Basic fallback validation without tree-sitter
131            // Check for obvious syntax issues
132            let balanced_parens = code.chars().filter(|&c| c == '(').count()
133                == code.chars().filter(|&c| c == ')').count();
134            let balanced_brackets = code.chars().filter(|&c| c == '[').count()
135                == code.chars().filter(|&c| c == ']').count();
136            let balanced_braces = code.chars().filter(|&c| c == '{').count()
137                == code.chars().filter(|&c| c == '}').count();
138
139            balanced_parens && balanced_brackets && balanced_braces
140        }
141    }
142
143    fn max_enumeration_depth(&self) -> usize {
144        5 // TypeScript ASTs get complex quickly like Python
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    // ========================================
153    // RED PHASE: These tests should FAIL initially
154    // ========================================
155
156    #[test]
157    fn test_typescript_grammar_language() {
158        let grammar = TypeScriptGrammar::new();
159        assert_eq!(grammar.language(), Language::TypeScript);
160    }
161
162    #[test]
163    fn test_typescript_grammar_validate_basic() {
164        let grammar = TypeScriptGrammar::new();
165        assert!(grammar.validate("let x = 1;"));
166        assert!(grammar.validate("const y: number = 42;"));
167        assert!(!grammar.validate(""));
168    }
169
170    #[test]
171    fn test_typescript_grammar_validate_function() {
172        let grammar = TypeScriptGrammar::new();
173        assert!(grammar.validate("function foo(): void {}"));
174        assert!(grammar.validate("function add(a: number, b: number): number { return a + b; }"));
175        assert!(grammar.validate("const arrow = (x: number) => x * 2;"));
176    }
177
178    #[test]
179    fn test_typescript_grammar_validate_interface() {
180        let grammar = TypeScriptGrammar::new();
181        assert!(grammar.validate("interface Foo { x: number; }"));
182        assert!(grammar.validate("interface Bar { name: string; age: number; }"));
183    }
184
185    #[test]
186    fn test_typescript_grammar_validate_class() {
187        let grammar = TypeScriptGrammar::new();
188        assert!(grammar.validate("class Foo {}"));
189        assert!(grammar.validate("class Bar { constructor(public x: number) {} }"));
190        assert!(grammar.validate("class Baz extends Foo { private y: string = ''; }"));
191    }
192
193    #[test]
194    fn test_typescript_grammar_validate_type_annotations() {
195        let grammar = TypeScriptGrammar::new();
196        assert!(grammar.validate("let x: number = 1;"));
197        assert!(grammar.validate("let arr: number[] = [1, 2, 3];"));
198        assert!(grammar.validate("let tuple: [string, number] = ['a', 1];"));
199        assert!(grammar.validate("type MyType = string | number;"));
200    }
201
202    #[test]
203    fn test_typescript_grammar_validate_generics() {
204        let grammar = TypeScriptGrammar::new();
205        assert!(grammar.validate("function identity<T>(x: T): T { return x; }"));
206        assert!(grammar.validate("class Box<T> { value: T; }"));
207        assert!(grammar.validate("let map: Map<string, number> = new Map();"));
208    }
209
210    #[test]
211    fn test_typescript_grammar_validate_control_flow() {
212        let grammar = TypeScriptGrammar::new();
213        assert!(grammar.validate("if (x) { y = 1; }"));
214        assert!(grammar.validate("for (let i = 0; i < 10; i++) { console.log(i); }"));
215        assert!(grammar.validate("while (true) { break; }"));
216        assert!(grammar.validate("switch (x) { case 1: break; default: break; }"));
217    }
218
219    #[test]
220    fn test_typescript_grammar_validate_async() {
221        let grammar = TypeScriptGrammar::new();
222        assert!(grammar.validate("async function fetch(): Promise<void> {}"));
223        assert!(grammar.validate("const result = await fetch();"));
224    }
225
226    #[test]
227    fn test_typescript_grammar_validate_unbalanced() {
228        let grammar = TypeScriptGrammar::new();
229        // Unbalanced should fail
230        assert!(!grammar.validate("let x = (1 + 2"));
231        assert!(!grammar.validate("let x = [1, 2"));
232        assert!(!grammar.validate("let x = {a: 1"));
233    }
234
235    #[test]
236    fn test_typescript_grammar_max_depth() {
237        let grammar = TypeScriptGrammar::new();
238        assert_eq!(grammar.max_enumeration_depth(), 5);
239    }
240
241    #[test]
242    fn test_typescript_grammar_debug() {
243        let grammar = TypeScriptGrammar::new();
244        let debug = format!("{:?}", grammar);
245        assert!(debug.contains("TypeScriptGrammar"));
246        assert!(debug.contains("typescript"));
247    }
248
249    #[test]
250    fn test_typescript_grammar_default() {
251        let grammar = TypeScriptGrammar::default();
252        assert_eq!(grammar.language(), Language::TypeScript);
253    }
254
255    #[cfg(feature = "tree-sitter")]
256    mod tree_sitter_tests {
257        use super::*;
258
259        #[test]
260        fn test_parse_simple() {
261            let grammar = TypeScriptGrammar::new();
262            let tree = grammar.parse("let x = 1;");
263            assert!(tree.is_some());
264        }
265
266        #[test]
267        fn test_root_node() {
268            let grammar = TypeScriptGrammar::new();
269            let root = grammar.root_node("let x = 1;");
270            assert_eq!(root, Some("program".to_string()));
271        }
272
273        #[test]
274        fn test_has_errors_valid() {
275            let grammar = TypeScriptGrammar::new();
276            assert!(!grammar.has_errors("let x = 1;"));
277            assert!(!grammar.has_errors("function foo(): void {}"));
278        }
279
280        #[test]
281        fn test_has_errors_invalid() {
282            let grammar = TypeScriptGrammar::new();
283            assert!(grammar.has_errors("function foo("));
284            assert!(grammar.has_errors("class {"));
285        }
286
287        #[test]
288        fn test_ast_depth() {
289            let grammar = TypeScriptGrammar::new();
290            let simple_depth = grammar.ast_depth("let x = 1;");
291            let complex_depth = grammar.ast_depth("function foo() { if (x) { return y + z; } }");
292            assert!(simple_depth > 0);
293            assert!(complex_depth > simple_depth);
294        }
295
296        #[test]
297        fn test_node_count() {
298            let grammar = TypeScriptGrammar::new();
299            let simple_count = grammar.node_count("let x = 1;");
300            let complex_count = grammar.node_count("let x = 1; let y = 2; let z = 3;");
301            assert!(simple_count > 0);
302            assert!(complex_count > simple_count);
303        }
304
305        #[test]
306        fn test_typescript_specific_syntax() {
307            let grammar = TypeScriptGrammar::new();
308            // Type annotations should parse correctly
309            assert!(!grammar.has_errors("let x: number = 1;"));
310            assert!(
311                !grammar.has_errors("function add(a: number, b: number): number { return a + b; }")
312            );
313            assert!(!grammar.has_errors("interface Foo { bar: string; }"));
314            assert!(!grammar.has_errors("type MyType = string | number;"));
315            assert!(!grammar.has_errors("enum Color { Red, Green, Blue }"));
316        }
317    }
318}