llmcc_ts/
token.rs

1use llmcc_core::LanguageTraitImpl;
2use llmcc_core::graph_builder::BlockKind;
3use llmcc_core::ir::{HirKind, HirNode};
4use llmcc_core::lang_def::{LanguageTrait, ParseNode, ParseTree, TreeSitterParseTree};
5use llmcc_core::scope::{Scope, ScopeStack};
6use llmcc_core::symbol::{SymKind, Symbol};
7use llmcc_core::{CompileCtxt, CompileUnit};
8use llmcc_resolver::ResolverOption;
9
10#[allow(clippy::single_component_path_imports)]
11use tree_sitter_typescript;
12
13// Include the auto-generated language definition from build script
14// The generated file contains a define_lang! call that expands to LangTypeScript
15include!(concat!(env!("OUT_DIR"), "/typescript_tokens.rs"));
16
17impl LanguageTraitImpl for LangTypeScript {
18    /// Block kind with parent context - handles special TypeScript cases
19    fn block_kind_with_parent_impl(kind_id: u16, field_id: u16, _parent_kind_id: u16) -> BlockKind {
20        // Default behavior: check field kind first, then node kind
21        let field_kind = <Self as LanguageTrait>::block_kind(field_id);
22        if field_kind != BlockKind::Undefined {
23            field_kind
24        } else {
25            <Self as LanguageTrait>::block_kind(kind_id)
26        }
27    }
28
29    #[rustfmt::skip]
30    fn collect_init_impl<'tcx>(cc: &'tcx CompileCtxt<'tcx>) -> ScopeStack<'tcx> {
31        let stack = ScopeStack::new(cc.arena(), &cc.interner);
32        let globals = cc.create_globals();
33        stack.push(globals);
34        debug_assert!(stack.depth() == 1);
35
36        for prim in crate::TYPESCRIPT_PRIMITIVES {
37            let name = cc.interner.intern(prim);
38            let symbol_val = Symbol::new(CompileCtxt::GLOBAL_SCOPE_OWNER, name);
39            let sym_id = symbol_val.id().0;
40            let symbol = cc.arena().alloc_with_id(sym_id, symbol_val);
41            symbol.set_kind(SymKind::Primitive);
42            symbol.set_is_global(true);
43            globals.insert(symbol);
44        }
45
46        stack
47    }
48
49    fn parse_impl(text: impl AsRef<[u8]>) -> Option<Box<dyn ParseTree>> {
50        use std::cell::RefCell;
51
52        // Thread-local parser reuse to avoid contention from Parser::new()
53        thread_local! {
54            static PARSER: RefCell<tree_sitter::Parser> = {
55                let mut parser = tree_sitter::Parser::new();
56                parser.set_language(&tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()).unwrap();
57                RefCell::new(parser)
58            };
59        }
60
61        PARSER.with(|parser| {
62            let mut parser = parser.borrow_mut();
63            let bytes = text.as_ref();
64            let tree = parser.parse(bytes, None)?;
65            Some(Box::new(TreeSitterParseTree { tree }) as Box<dyn ParseTree>)
66        })
67    }
68
69    fn supported_extensions_impl() -> &'static [&'static str] {
70        &["ts", "mts", "cts"]
71    }
72
73    fn manifest_name_impl() -> &'static str {
74        "package.json"
75    }
76
77    fn container_dirs_impl() -> &'static [&'static str] {
78        &["src", "lib", "dist", "build", "out", "source"]
79    }
80
81    /// Check if the given parse node is a TypeScript test attribute.
82    /// Detects: @test, describe(), it(), test(), etc.
83    fn is_test_attribute_impl(node: &dyn ParseNode, source: &[u8]) -> bool {
84        let kind_id = node.kind_id();
85
86        // Check for decorator nodes (e.g., @Test)
87        if kind_id == LangTypeScript::decorator {
88            let start = node.start_byte();
89            let end = node.end_byte();
90            if end <= start || end > source.len() {
91                return false;
92            }
93
94            let attr_text = match std::str::from_utf8(&source[start..end]) {
95                Ok(text) => text,
96                Err(_) => return false,
97            };
98
99            // Check for test-related decorators
100            return attr_text.contains("@Test")
101                || attr_text.contains("@test")
102                || attr_text.contains("@it")
103                || attr_text.contains("@describe");
104        }
105
106        false
107    }
108
109    fn collect_symbols_impl<'tcx, C>(
110        unit: CompileUnit<'tcx>,
111        node: HirNode<'tcx>,
112        scope_stack: ScopeStack<'tcx>,
113        config: &C,
114    ) -> &'tcx Scope<'tcx> {
115        unsafe {
116            let config_ref = config as *const C as *const ResolverOption;
117            crate::collect::collect_symbols(unit, &node, scope_stack, &*config_ref)
118        }
119    }
120
121    fn bind_symbols_impl<'tcx, C>(
122        unit: CompileUnit<'tcx>,
123        node: HirNode<'tcx>,
124        globals: &'tcx Scope<'tcx>,
125        config: &C,
126    ) {
127        unsafe {
128            let config = config as *const C as *const ResolverOption;
129            crate::bind::bind_symbols(unit, &node, globals, &*config);
130        }
131    }
132}