1use crate::cst::{ControlKind, Span};
7
8#[tracing::instrument(skip_all, fields(bytes = source.len()))]
12#[must_use]
13pub fn parse_go_template(source: &str) -> Option<tree_sitter::Tree> {
14 let language =
15 tree_sitter::Language::new(helm_schema_template_grammar::go_template::language());
16 let mut parser = tree_sitter::Parser::new();
17 parser.set_language(&language).ok()?;
18 parser.parse(source, None)
19}
20
21#[derive(Clone, Copy, Debug)]
22pub(crate) struct ActionToken {
23 pub(crate) span: Span,
24 pub(crate) kind: TokenKind,
25}
26
27#[derive(Clone, Copy, Debug)]
28pub(crate) enum TokenKind {
29 Output { expr_span: Span },
31 Assign,
33 TemplateComment,
35 Break,
37 Continue,
39 RegionOpen {
42 region: usize,
43 kind: ControlKind,
44 region_end: usize,
45 },
46 RegionBranch { region: usize },
48 RegionEnd { region: usize },
50 Error,
52}
53
54pub(crate) fn collect_action_tokens(root: tree_sitter::Node<'_>) -> Vec<ActionToken> {
55 let mut out = Vec::new();
56 let mut next_region = 0usize;
57 walk_body(root, &mut next_region, &mut out);
58 out.sort_by_key(|token| (token.span.start, token.span.end));
59 out
60}
61
62fn is_left_delimiter(kind: &str) -> bool {
63 matches!(kind, "{{" | "{{-")
64}
65
66fn is_right_delimiter(kind: &str) -> bool {
67 matches!(kind, "}}" | "-}}")
68}
69
70fn control_kind(node_kind: &str) -> Option<ControlKind> {
71 match node_kind {
72 "if_action" => Some(ControlKind::If),
73 "with_action" => Some(ControlKind::With),
74 "range_action" => Some(ControlKind::Range),
75 "define_action" => Some(ControlKind::Define),
76 "block_action" => Some(ControlKind::Block),
77 _ => None,
78 }
79}
80
81fn walk_body(node: tree_sitter::Node<'_>, next_region: &mut usize, out: &mut Vec<ActionToken>) {
85 let mut group = GroupState::default();
86 let mut cursor = node.walk();
87 if !cursor.goto_first_child() {
88 return;
89 }
90 loop {
91 let child = cursor.node();
92 dispatch_body_child(child, next_region, &mut group, out);
93 if !cursor.goto_next_sibling() {
94 break;
95 }
96 }
97 group.finish(node.end_byte(), out);
98}
99
100fn dispatch_body_child<'tree>(
101 child: tree_sitter::Node<'tree>,
102 next_region: &mut usize,
103 group: &mut GroupState<'tree>,
104 out: &mut Vec<ActionToken>,
105) {
106 let kind = child.kind();
107 if !child.is_named() {
108 if is_left_delimiter(kind) {
109 group.open(child, out);
110 } else if is_right_delimiter(kind) {
111 group.close(child.end_byte(), out);
112 }
113 return;
114 }
115 if group.collect_named(child) {
116 return;
117 }
118 match kind {
119 "text" | "yaml_no_injection_text" | "comment" => {
120 }
123 "template_action" => out.push(ActionToken {
124 span: node_span(child),
125 kind: TokenKind::Output {
126 expr_span: node_span(child),
127 },
128 }),
129 "break_action" => out.push(ActionToken {
130 span: node_span(child),
131 kind: TokenKind::Break,
132 }),
133 "continue_action" => out.push(ActionToken {
134 span: node_span(child),
135 kind: TokenKind::Continue,
136 }),
137 "ERROR" => walk_body(child, next_region, out),
138 _ => {
139 if let Some(control) = control_kind(kind) {
140 walk_control(child, control, next_region, out);
141 } else {
142 out.push(ActionToken {
143 span: node_span(child),
144 kind: TokenKind::Error,
145 });
146 }
147 }
148 }
149}
150
151fn walk_control(
155 node: tree_sitter::Node<'_>,
156 kind: ControlKind,
157 next_region: &mut usize,
158 out: &mut Vec<ActionToken>,
159) {
160 let region = *next_region;
161 *next_region += 1;
162 let region_end = node.end_byte();
163
164 let mut group = GroupState::default();
165 let mut bracket: Option<Bracket> = None;
166 let mut opened = false;
167
168 let mut cursor = node.walk();
169 if !cursor.goto_first_child() {
170 return;
171 }
172 loop {
173 let child = cursor.node();
174 let field = cursor.field_name();
175 if let Some(state) = bracket.as_mut() {
176 if !child.is_named() && field.is_none() && is_right_delimiter(child.kind()) {
177 let closed = Bracket {
178 end: child.end_byte(),
179 ..*state
180 };
181 bracket = None;
182 emit_bracket(closed, region, kind, region_end, &mut opened, out);
183 } else if !child.is_named() {
184 match child.kind() {
185 "else" => state.has_else = true,
186 "end" => state.has_end = true,
187 _ => {}
188 }
189 }
190 } else if field.is_none() && !child.is_named() && is_left_delimiter(child.kind()) {
191 bracket = Some(Bracket {
192 start: child.start_byte(),
193 end: child.end_byte(),
194 has_else: false,
195 has_end: false,
196 });
197 } else {
198 dispatch_body_child(child, next_region, &mut group, out);
199 }
200 if !cursor.goto_next_sibling() {
201 break;
202 }
203 }
204 group.finish(node.end_byte(), out);
205}
206
207#[derive(Clone, Copy)]
208struct Bracket {
209 start: usize,
210 end: usize,
211 has_else: bool,
212 has_end: bool,
213}
214
215fn emit_bracket(
216 bracket: Bracket,
217 region: usize,
218 kind: ControlKind,
219 region_end: usize,
220 opened: &mut bool,
221 out: &mut Vec<ActionToken>,
222) {
223 let span = Span::new(bracket.start, bracket.end);
224 let token_kind = if !*opened {
225 *opened = true;
226 TokenKind::RegionOpen {
227 region,
228 kind,
229 region_end,
230 }
231 } else if bracket.has_end {
232 TokenKind::RegionEnd { region }
233 } else if bracket.has_else {
234 TokenKind::RegionBranch { region }
235 } else {
236 TokenKind::Error
237 };
238 out.push(ActionToken {
239 span,
240 kind: token_kind,
241 });
242}
243
244#[derive(Default)]
248struct GroupState<'tree> {
249 start: Option<usize>,
250 named: Vec<tree_sitter::Node<'tree>>,
251}
252
253impl<'tree> GroupState<'tree> {
254 fn open(&mut self, child: tree_sitter::Node<'tree>, out: &mut Vec<ActionToken>) {
255 if let Some(start) = self.start.take() {
256 out.push(ActionToken {
257 span: Span::new(start, child.start_byte()),
258 kind: TokenKind::Error,
259 });
260 self.named.clear();
261 }
262 self.start = Some(child.start_byte());
263 }
264
265 fn close(&mut self, end: usize, out: &mut Vec<ActionToken>) {
266 let Some(start) = self.start.take() else {
267 return;
268 };
269 let span = Span::new(start, end);
270 let kind = classify_group(&self.named);
271 self.named.clear();
272 out.push(ActionToken { span, kind });
273 }
274
275 fn collect_named(&mut self, child: tree_sitter::Node<'tree>) -> bool {
277 if self.start.is_some() {
278 self.named.push(child);
279 return true;
280 }
281 false
282 }
283
284 fn finish(&mut self, node_end: usize, out: &mut Vec<ActionToken>) {
285 if let Some(start) = self.start.take() {
286 out.push(ActionToken {
287 span: Span::new(start, node_end),
288 kind: TokenKind::Error,
289 });
290 self.named.clear();
291 }
292 }
293}
294
295fn classify_group(named: &[tree_sitter::Node<'_>]) -> TokenKind {
296 let Some(first) = named.first() else {
297 return TokenKind::Error;
298 };
299 match first.kind() {
300 "comment" => TokenKind::TemplateComment,
301 "variable_definition" | "assignment" => TokenKind::Assign,
302 "ERROR" => TokenKind::Error,
303 _ => {
304 let last = named.last().unwrap_or(first);
305 TokenKind::Output {
306 expr_span: Span::new(first.start_byte(), last.end_byte()),
307 }
308 }
309 }
310}
311
312fn node_span(node: tree_sitter::Node<'_>) -> Span {
313 Span::new(node.start_byte(), node.end_byte())
314}