1use tree_sitter_language::LanguageFn;
31
32extern "C" {
33 fn tree_sitter_htmlx() -> *const ();
34}
35
36pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_htmlx) };
38
39pub fn language() -> tree_sitter::Language {
41 LANGUAGE.into()
42}
43
44pub const HIGHLIGHTS_QUERY: &str = include_str!("../queries/highlights.scm");
46
47pub const INJECTIONS_QUERY: &str = include_str!("../queries/injections.scm");
49
50pub const NODE_TYPES: &str = include_str!("../src/node-types.json");
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn test_can_load_grammar() {
59 let mut parser = tree_sitter::Parser::new();
60 parser
61 .set_language(&LANGUAGE.into())
62 .expect("Failed to load HTMLX grammar");
63 }
64
65 #[test]
66 fn test_parse_simple_html() {
67 let mut parser = tree_sitter::Parser::new();
68 parser.set_language(&LANGUAGE.into()).unwrap();
69
70 let source = "<div>Hello</div>";
71 let tree = parser.parse(source, None).unwrap();
72
73 assert!(!tree.root_node().has_error());
74 assert_eq!(tree.root_node().kind(), "document");
75 }
76
77 #[test]
78 fn test_parse_expression() {
79 let mut parser = tree_sitter::Parser::new();
80 parser.set_language(&LANGUAGE.into()).unwrap();
81
82 let source = "<div>{name}</div>";
83 let tree = parser.parse(source, None).unwrap();
84
85 assert!(!tree.root_node().has_error());
86 }
87
88 #[test]
89 fn test_parse_expression_attribute() {
90 let mut parser = tree_sitter::Parser::new();
91 parser.set_language(&LANGUAGE.into()).unwrap();
92
93 let source = r#"<input value={text} />"#;
94 let tree = parser.parse(source, None).unwrap();
95
96 assert!(!tree.root_node().has_error());
97 }
98
99 #[test]
100 fn test_parse_shorthand_attribute() {
101 let mut parser = tree_sitter::Parser::new();
102 parser.set_language(&LANGUAGE.into()).unwrap();
103
104 let source = r#"<div {hidden} {id}></div>"#;
105 let tree = parser.parse(source, None).unwrap();
106
107 assert!(!tree.root_node().has_error());
108 }
109
110 #[test]
111 fn test_parse_spread_attribute() {
112 let mut parser = tree_sitter::Parser::new();
113 parser.set_language(&LANGUAGE.into()).unwrap();
114
115 let source = r#"<Component {...props} />"#;
116 let tree = parser.parse(source, None).unwrap();
117
118 assert!(!tree.root_node().has_error());
119 }
120
121 #[test]
122 fn test_parse_directive_attribute() {
123 let mut parser = tree_sitter::Parser::new();
124 parser.set_language(&LANGUAGE.into()).unwrap();
125
126 let source = r#"<input bind:value={name} on:input={handleInput} />"#;
127 let tree = parser.parse(source, None).unwrap();
128
129 assert!(!tree.root_node().has_error());
130 }
131
132 #[test]
133 fn test_parse_nested_expressions() {
134 let mut parser = tree_sitter::Parser::new();
135 parser.set_language(&LANGUAGE.into()).unwrap();
136
137 let source = r#"<div>{items.map(item => item.name)}</div>"#;
138 let tree = parser.parse(source, None).unwrap();
139
140 assert!(!tree.root_node().has_error());
141 }
142
143 #[test]
144 fn test_highlights_query_includes_html_captures() {
145 let language = language();
146 let query = tree_sitter::Query::new(&language, HIGHLIGHTS_QUERY)
147 .expect("HTMLX highlights query should compile");
148 let captures = query.capture_names();
149
150 for capture in ["tag", "attribute", "string", "punctuation.bracket"] {
151 assert!(
152 captures.iter().any(|name| *name == capture),
153 "missing @{capture} from HTMLX highlights query"
154 );
155 }
156 }
157}