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