tree_sitter_svelte_next/
lib.rs1use tree_sitter_language::LanguageFn;
4
5unsafe extern "C" {
6 fn tree_sitter_svelte() -> *const ();
7}
8
9pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_svelte) };
11
12pub const NODE_TYPES: &str = include_str!("node-types.json");
14
15pub const HIGHLIGHTS_QUERY: &str = include_str!("../queries/highlights.scm");
17
18pub const INJECTIONS_QUERY: &str = include_str!("../queries/injections.scm");
20
21pub const LOCALS_QUERY: &str = include_str!("../queries/locals.scm");
23
24#[cfg(test)]
25mod tests {
26 #[test]
27 fn test_can_load_grammar() {
28 let mut parser = tree_sitter::Parser::new();
29 parser
30 .set_language(&super::LANGUAGE.into())
31 .expect("Error loading Svelte parser");
32 }
33
34 #[test]
35 fn test_can_parse_svelte() {
36 let mut parser = tree_sitter::Parser::new();
37 parser
38 .set_language(&super::LANGUAGE.into())
39 .expect("Error loading Svelte parser");
40
41 let code = r#"
42<script>
43 let count = $state(0);
44 let doubled = $derived(count * 2);
45
46 function increment() {
47 count++;
48 }
49</script>
50
51{#snippet greeting(name)}
52 <p>Hello {name}!</p>
53{/snippet}
54
55{#each items as item, i (item.id)}
56 <div class="item">{item.name}</div>
57{:else}
58 <p>No items</p>
59{/each}
60
61{#if count > 0}
62 <button onclick={increment}>
63 Clicked {count} {count === 1 ? 'time' : 'times'}
64 </button>
65 {@render greeting('World')}
66{:else}
67 <p>Count is zero</p>
68{/if}
69
70{#await promise}
71 <p>Loading...</p>
72{:then data}
73 <p>Success: {data}</p>
74{:catch error}
75 <p>Error: {error.message}</p>
76{/await}
77
78<style>
79 .item {
80 color: red;
81 font-weight: bold;
82 }
83</style>
84"#;
85
86 let tree = parser.parse(code, None).unwrap();
87 let root = tree.root_node();
88 assert!(
89 !root.has_error(),
90 "Parse tree has errors: {}",
91 root.to_sexp()
92 );
93 }
94
95 #[test]
96 fn test_highlights_query_is_valid() {
97 let language: tree_sitter::Language = super::LANGUAGE.into();
98 tree_sitter::Query::new(&language, super::HIGHLIGHTS_QUERY)
99 .expect("HIGHLIGHTS_QUERY should be a valid query for the Svelte grammar");
100 }
101
102 #[test]
103 fn test_highlights_query_matches_html_nodes() {
104 use tree_sitter::{Parser, Query, QueryCursor, StreamingIterator as _};
105
106 let language: tree_sitter::Language = super::LANGUAGE.into();
107 let mut parser = Parser::new();
108 parser.set_language(&language).unwrap();
109
110 let code = r#"<h1 class="title">Hello</h1>"#;
111 let tree = parser.parse(code, None).unwrap();
112
113 let query = Query::new(&language, super::HIGHLIGHTS_QUERY).unwrap();
114 let mut cursor = QueryCursor::new();
115 let mut matches = cursor.matches(&query, tree.root_node(), code.as_bytes());
116
117 let mut capture_names: Vec<String> = vec![];
118 while let Some(m) = matches.next() {
119 for cap in m.captures {
120 let name = query.capture_names()[cap.index as usize].to_string();
121 if !capture_names.contains(&name) {
122 capture_names.push(name);
123 }
124 }
125 }
126
127 assert!(
128 capture_names.contains(&"tag".to_string()),
129 "should match tag_name as @tag"
130 );
131 assert!(
132 capture_names.contains(&"attribute".to_string()),
133 "should match attribute_name as @attribute"
134 );
135 assert!(
136 capture_names.contains(&"string".to_string()),
137 "should match attribute_value as @string"
138 );
139 }
140
141 #[test]
142 fn test_injections_query_is_valid() {
143 let language: tree_sitter::Language = super::LANGUAGE.into();
144 tree_sitter::Query::new(&language, super::INJECTIONS_QUERY)
145 .expect("INJECTIONS_QUERY should be a valid query for the Svelte grammar");
146 }
147
148 #[test]
149 fn test_injections_query_matches_script_and_style() {
150 use tree_sitter::{Parser, Query, QueryCursor, StreamingIterator as _};
151
152 let language: tree_sitter::Language = super::LANGUAGE.into();
153 let mut parser = Parser::new();
154 parser.set_language(&language).unwrap();
155
156 let code = r#"
157<script lang="ts">
158 let x: number = 1;
159</script>
160<style>
161 h1 { color: red; }
162</style>
163"#;
164 let tree = parser.parse(code, None).unwrap();
165 let query = Query::new(&language, super::INJECTIONS_QUERY).unwrap();
166 let mut cursor = QueryCursor::new();
167 let mut matches = cursor.matches(&query, tree.root_node(), code.as_bytes());
168
169 let mut matched_languages: Vec<String> = vec![];
170 while let Some(m) = matches.next() {
171 for prop in query.property_settings(m.pattern_index) {
172 if prop.key.as_ref() == "injection.language" {
173 if let Some(val) = &prop.value {
174 matched_languages.push(val.to_string());
175 }
176 }
177 }
178 }
179
180 assert!(
181 matched_languages.contains(&"typescript".to_string()),
182 "should inject TypeScript for script content"
183 );
184 assert!(
185 matched_languages.contains(&"css".to_string()),
186 "should inject CSS for style content"
187 );
188 }
189}