use crate::Language;
use super::Grammar;
pub struct TypeScriptGrammar {
#[cfg(feature = "tree-sitter")]
parser: std::sync::Mutex<tree_sitter::Parser>,
}
impl std::fmt::Debug for TypeScriptGrammar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TypeScriptGrammar")
.field("language", &"typescript")
.finish()
}
}
impl Default for TypeScriptGrammar {
fn default() -> Self {
Self::new()
}
}
impl TypeScriptGrammar {
#[must_use]
#[allow(clippy::expect_used)]
pub fn new() -> Self {
#[cfg(feature = "tree-sitter")]
{
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into())
.expect("Failed to load TypeScript grammar");
Self {
parser: std::sync::Mutex::new(parser),
}
}
#[cfg(not(feature = "tree-sitter"))]
{
Self {}
}
}
#[cfg(feature = "tree-sitter")]
pub fn parse(&self, code: &str) -> Option<tree_sitter::Tree> {
let mut parser = self.parser.lock().ok()?;
parser.parse(code, None)
}
#[cfg(feature = "tree-sitter")]
pub fn root_node(&self, code: &str) -> Option<String> {
self.parse(code)
.map(|tree| tree.root_node().kind().to_string())
}
#[cfg(feature = "tree-sitter")]
pub fn has_errors(&self, code: &str) -> bool {
self.parse(code)
.map_or(true, |tree| tree.root_node().has_error())
}
#[cfg(feature = "tree-sitter")]
pub fn ast_depth(&self, code: &str) -> usize {
fn max_depth(node: tree_sitter::Node<'_>) -> usize {
let child_depths = node
.children(&mut node.walk())
.map(max_depth)
.max()
.unwrap_or(0);
1 + child_depths
}
self.parse(code)
.map_or(0, |tree| max_depth(tree.root_node()))
}
#[cfg(feature = "tree-sitter")]
pub fn node_count(&self, code: &str) -> usize {
fn count_nodes(node: tree_sitter::Node<'_>) -> usize {
1 + node
.children(&mut node.walk())
.map(count_nodes)
.sum::<usize>()
}
self.parse(code)
.map_or(0, |tree| count_nodes(tree.root_node()))
}
}
impl Grammar for TypeScriptGrammar {
fn language(&self) -> Language {
Language::TypeScript
}
fn validate(&self, code: &str) -> bool {
if code.is_empty() {
return false;
}
#[cfg(feature = "tree-sitter")]
{
!self.has_errors(code)
}
#[cfg(not(feature = "tree-sitter"))]
{
let balanced_parens = code.chars().filter(|&c| c == '(').count()
== code.chars().filter(|&c| c == ')').count();
let balanced_brackets = code.chars().filter(|&c| c == '[').count()
== code.chars().filter(|&c| c == ']').count();
let balanced_braces = code.chars().filter(|&c| c == '{').count()
== code.chars().filter(|&c| c == '}').count();
balanced_parens && balanced_brackets && balanced_braces
}
}
fn max_enumeration_depth(&self) -> usize {
5 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_typescript_grammar_language() {
let grammar = TypeScriptGrammar::new();
assert_eq!(grammar.language(), Language::TypeScript);
}
#[test]
fn test_typescript_grammar_validate_basic() {
let grammar = TypeScriptGrammar::new();
assert!(grammar.validate("let x = 1;"));
assert!(grammar.validate("const y: number = 42;"));
assert!(!grammar.validate(""));
}
#[test]
fn test_typescript_grammar_validate_function() {
let grammar = TypeScriptGrammar::new();
assert!(grammar.validate("function foo(): void {}"));
assert!(grammar.validate("function add(a: number, b: number): number { return a + b; }"));
assert!(grammar.validate("const arrow = (x: number) => x * 2;"));
}
#[test]
fn test_typescript_grammar_validate_interface() {
let grammar = TypeScriptGrammar::new();
assert!(grammar.validate("interface Foo { x: number; }"));
assert!(grammar.validate("interface Bar { name: string; age: number; }"));
}
#[test]
fn test_typescript_grammar_validate_class() {
let grammar = TypeScriptGrammar::new();
assert!(grammar.validate("class Foo {}"));
assert!(grammar.validate("class Bar { constructor(public x: number) {} }"));
assert!(grammar.validate("class Baz extends Foo { private y: string = ''; }"));
}
#[test]
fn test_typescript_grammar_validate_type_annotations() {
let grammar = TypeScriptGrammar::new();
assert!(grammar.validate("let x: number = 1;"));
assert!(grammar.validate("let arr: number[] = [1, 2, 3];"));
assert!(grammar.validate("let tuple: [string, number] = ['a', 1];"));
assert!(grammar.validate("type MyType = string | number;"));
}
#[test]
fn test_typescript_grammar_validate_generics() {
let grammar = TypeScriptGrammar::new();
assert!(grammar.validate("function identity<T>(x: T): T { return x; }"));
assert!(grammar.validate("class Box<T> { value: T; }"));
assert!(grammar.validate("let map: Map<string, number> = new Map();"));
}
#[test]
fn test_typescript_grammar_validate_control_flow() {
let grammar = TypeScriptGrammar::new();
assert!(grammar.validate("if (x) { y = 1; }"));
assert!(grammar.validate("for (let i = 0; i < 10; i++) { console.log(i); }"));
assert!(grammar.validate("while (true) { break; }"));
assert!(grammar.validate("switch (x) { case 1: break; default: break; }"));
}
#[test]
fn test_typescript_grammar_validate_async() {
let grammar = TypeScriptGrammar::new();
assert!(grammar.validate("async function fetch(): Promise<void> {}"));
assert!(grammar.validate("const result = await fetch();"));
}
#[test]
fn test_typescript_grammar_validate_unbalanced() {
let grammar = TypeScriptGrammar::new();
assert!(!grammar.validate("let x = (1 + 2"));
assert!(!grammar.validate("let x = [1, 2"));
assert!(!grammar.validate("let x = {a: 1"));
}
#[test]
fn test_typescript_grammar_max_depth() {
let grammar = TypeScriptGrammar::new();
assert_eq!(grammar.max_enumeration_depth(), 5);
}
#[test]
fn test_typescript_grammar_debug() {
let grammar = TypeScriptGrammar::new();
let debug = format!("{:?}", grammar);
assert!(debug.contains("TypeScriptGrammar"));
assert!(debug.contains("typescript"));
}
#[test]
fn test_typescript_grammar_default() {
let grammar = TypeScriptGrammar::default();
assert_eq!(grammar.language(), Language::TypeScript);
}
#[cfg(feature = "tree-sitter")]
mod tree_sitter_tests {
use super::*;
#[test]
fn test_parse_simple() {
let grammar = TypeScriptGrammar::new();
let tree = grammar.parse("let x = 1;");
assert!(tree.is_some());
}
#[test]
fn test_root_node() {
let grammar = TypeScriptGrammar::new();
let root = grammar.root_node("let x = 1;");
assert_eq!(root, Some("program".to_string()));
}
#[test]
fn test_has_errors_valid() {
let grammar = TypeScriptGrammar::new();
assert!(!grammar.has_errors("let x = 1;"));
assert!(!grammar.has_errors("function foo(): void {}"));
}
#[test]
fn test_has_errors_invalid() {
let grammar = TypeScriptGrammar::new();
assert!(grammar.has_errors("function foo("));
assert!(grammar.has_errors("class {"));
}
#[test]
fn test_ast_depth() {
let grammar = TypeScriptGrammar::new();
let simple_depth = grammar.ast_depth("let x = 1;");
let complex_depth = grammar.ast_depth("function foo() { if (x) { return y + z; } }");
assert!(simple_depth > 0);
assert!(complex_depth > simple_depth);
}
#[test]
fn test_node_count() {
let grammar = TypeScriptGrammar::new();
let simple_count = grammar.node_count("let x = 1;");
let complex_count = grammar.node_count("let x = 1; let y = 2; let z = 3;");
assert!(simple_count > 0);
assert!(complex_count > simple_count);
}
#[test]
fn test_typescript_specific_syntax() {
let grammar = TypeScriptGrammar::new();
assert!(!grammar.has_errors("let x: number = 1;"));
assert!(
!grammar.has_errors("function add(a: number, b: number): number { return a + b; }")
);
assert!(!grammar.has_errors("interface Foo { bar: string; }"));
assert!(!grammar.has_errors("type MyType = string | number;"));
assert!(!grammar.has_errors("enum Color { Red, Green, Blue }"));
}
}
}