Skip to main content

tree_sitter_htmlx/
lib.rs

1//! Tree-sitter grammar for HTMLX (expression-enhanced HTML)
2//!
3//! HTMLX extends HTML with expression syntax commonly used in modern
4//! frontend frameworks like Svelte, Vue, and others.
5//!
6//! ## Features
7//!
8//! - Expression interpolation: `{expression}`
9//! - Shorthand attributes: `{name}` (equivalent to `name={name}`)
10//! - Spread attributes: `{...props}`
11//! - Directive attributes: `bind:value`, `on:click`, `class:active`, etc.
12//!
13//! ## Example
14//!
15//! ```rust
16//! use tree_sitter_htmlx::LANGUAGE;
17//!
18//! let mut parser = tree_sitter::Parser::new();
19//! parser.set_language(&LANGUAGE.into()).expect("Failed to load HTMLX grammar");
20//!
21//! let source = r#"<div class="container" {hidden}>
22//!   <p>{greeting}, {name}!</p>
23//!   <button onclick={handleClick}>Click me</button>
24//! </div>"#;
25//!
26//! let tree = parser.parse(source, None).unwrap();
27//! assert!(!tree.root_node().has_error());
28//! ```
29
30use tree_sitter_language::LanguageFn;
31
32extern "C" {
33    fn tree_sitter_htmlx() -> *const ();
34}
35
36/// The tree-sitter [`LanguageFn`] for HTMLX.
37pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_htmlx) };
38
39/// The tree-sitter language for HTMLX.
40pub fn language() -> tree_sitter::Language {
41    LANGUAGE.into()
42}
43
44/// The syntax highlighting query for HTMLX.
45pub const HIGHLIGHTS_QUERY: &str = include_str!("../queries/highlights.scm");
46
47/// The content of the [`node-types.json`] file for HTMLX.
48pub const NODE_TYPES: &str = include_str!("../src/node-types.json");
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_can_load_grammar() {
56        let mut parser = tree_sitter::Parser::new();
57        parser
58            .set_language(&LANGUAGE.into())
59            .expect("Failed to load HTMLX grammar");
60    }
61
62    #[test]
63    fn test_parse_simple_html() {
64        let mut parser = tree_sitter::Parser::new();
65        parser.set_language(&LANGUAGE.into()).unwrap();
66
67        let source = "<div>Hello</div>";
68        let tree = parser.parse(source, None).unwrap();
69
70        assert!(!tree.root_node().has_error());
71        assert_eq!(tree.root_node().kind(), "document");
72    }
73
74    #[test]
75    fn test_parse_expression() {
76        let mut parser = tree_sitter::Parser::new();
77        parser.set_language(&LANGUAGE.into()).unwrap();
78
79        let source = "<div>{name}</div>";
80        let tree = parser.parse(source, None).unwrap();
81
82        assert!(!tree.root_node().has_error());
83    }
84
85    #[test]
86    fn test_parse_expression_attribute() {
87        let mut parser = tree_sitter::Parser::new();
88        parser.set_language(&LANGUAGE.into()).unwrap();
89
90        let source = r#"<input value={text} />"#;
91        let tree = parser.parse(source, None).unwrap();
92
93        assert!(!tree.root_node().has_error());
94    }
95
96    #[test]
97    fn test_parse_shorthand_attribute() {
98        let mut parser = tree_sitter::Parser::new();
99        parser.set_language(&LANGUAGE.into()).unwrap();
100
101        let source = r#"<div {hidden} {id}></div>"#;
102        let tree = parser.parse(source, None).unwrap();
103
104        assert!(!tree.root_node().has_error());
105    }
106
107    #[test]
108    fn test_parse_spread_attribute() {
109        let mut parser = tree_sitter::Parser::new();
110        parser.set_language(&LANGUAGE.into()).unwrap();
111
112        let source = r#"<Component {...props} />"#;
113        let tree = parser.parse(source, None).unwrap();
114
115        assert!(!tree.root_node().has_error());
116    }
117
118    #[test]
119    fn test_parse_directive_attribute() {
120        let mut parser = tree_sitter::Parser::new();
121        parser.set_language(&LANGUAGE.into()).unwrap();
122
123        let source = r#"<input bind:value={name} on:input={handleInput} />"#;
124        let tree = parser.parse(source, None).unwrap();
125
126        assert!(!tree.root_node().has_error());
127    }
128
129    #[test]
130    fn test_parse_nested_expressions() {
131        let mut parser = tree_sitter::Parser::new();
132        parser.set_language(&LANGUAGE.into()).unwrap();
133
134        let source = r#"<div>{items.map(item => item.name)}</div>"#;
135        let tree = parser.parse(source, None).unwrap();
136
137        assert!(!tree.root_node().has_error());
138    }
139}