fluidattacks_blends_domain/syntax/node.rs
1//! Typed syntax nodes: one enum variant per syntax label type.
2
3use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use strum::{EnumDiscriminants, IntoStaticStr};
8
9use crate::NodeId;
10
11/// One entry of a file's structure tree (its classes, methods and nesting).
12#[derive(Clone, PartialEq, Eq, Debug)]
13#[non_exhaustive]
14pub struct FileStructData {
15 /// Id of the AST node this entry was built from.
16 pub node: NodeId,
17 /// The entry's syntax-node label, e.g. `"MethodDeclaration"`.
18 pub kind: String,
19 /// The entry's payload: a leaf value or further nested entries.
20 pub data: FileStructValue,
21 /// Ids of every AST node this entry spans, when known.
22 pub node_range: Option<Vec<NodeId>>,
23}
24
25/// A [`FileStructData`] entry's payload.
26#[derive(Clone, PartialEq, Eq, Debug)]
27#[non_exhaustive]
28pub enum FileStructValue {
29 /// A leaf: the entry is a method, named by its signature.
30 MethodName(String),
31 /// A branch: nested entries, keyed by name.
32 Children(BTreeMap<String, FileStructData>),
33}
34
35/// One instance (object) found while extracting a file's structure.
36#[derive(Clone, PartialEq, Eq, Debug)]
37#[non_exhaustive]
38pub struct FileInstanceData {
39 /// The instance's variable or field name.
40 pub object: String,
41 /// The expression the instance was constructed from.
42 pub source: String,
43 /// The instance's constructed type name.
44 pub source_type: String,
45}
46
47/// A tree-sitter-independent syntax node: one variant per label the readers emit.
48#[derive(Clone, PartialEq, Eq, Debug, EnumDiscriminants)]
49#[non_exhaustive]
50#[strum_discriminants(
51 name(SyntaxKind),
52 derive(PartialOrd, Ord, Hash, IntoStaticStr),
53 non_exhaustive
54)]
55pub enum SyntaxNode {
56 /// A decorator/annotation applied to a declaration.
57 Annotation {
58 /// The node's name, as written.
59 name: String,
60 /// Id of the argument list node, if any.
61 arguments_id: Option<NodeId>,
62 },
63 /// A single call argument.
64 Argument,
65 /// A call's argument list.
66 ArgumentList,
67 /// An array literal initializer.
68 ArrayInitializer {
69 /// The declared variable's type, as written, if any.
70 variable_type: Option<String>,
71 },
72 /// An assignment expression.
73 Assignment {
74 /// Id of the declared variable's node.
75 variable_id: NodeId,
76 /// Id of the value node, if any.
77 value_id: Option<NodeId>,
78 /// The operator, as written, if any.
79 operator: Option<String>,
80 },
81 /// An attribute applied to a declaration (non-annotation form).
82 Attribute {
83 /// The node's name, as written.
84 name: String,
85 },
86 /// An `await` expression.
87 AwaitExpression {
88 /// Id of the expression node.
89 expression_id: NodeId,
90 },
91 /// A binary operator expression.
92 BinaryOperation {
93 /// The operator, as written.
94 operator: String,
95 /// Id of the left-hand operand node, if any.
96 left_id: Option<NodeId>,
97 /// Id of the right-hand operand node, if any.
98 right_id: Option<NodeId>,
99 },
100 /// A `break` statement.
101 Break,
102 /// A `catch` clause of a try statement.
103 CatchClause {
104 /// Id of the node's body block, if any.
105 block_id: Option<NodeId>,
106 /// Id of the exception variable declaration, if any.
107 catch_declaration: Option<NodeId>,
108 },
109 /// The exception variable declared by a `catch` clause.
110 CatchDeclaration,
111 /// A class declaration.
112 Class {
113 /// The node's name, as written.
114 name: String,
115 /// Id of the node's body block, if any.
116 block_id: Option<NodeId>,
117 /// Id of the modifiers node, if any.
118 modifiers_id: Option<NodeId>,
119 /// The name of the class this one extends, if any.
120 inherited_class: Option<String>,
121 /// The access modifier keywords, as written, if any.
122 access_modifiers: Option<String>,
123 },
124 /// A class's member list.
125 ClassBody,
126 /// A source comment.
127 Comment {
128 /// The comment text, including its delimiters.
129 comment: String,
130 },
131 /// A `continue` statement.
132 Continue,
133 /// A generic (language-agnostic) declaration.
134 Declaration {
135 /// The node's name, as written.
136 name: String,
137 /// Id of the node's body block, if any.
138 block_id: Option<NodeId>,
139 /// The access modifier keywords, as written, if any.
140 access_modifiers: Option<String>,
141 },
142 /// A block holding one or more declarations.
143 DeclarationBlock,
144 /// A `default` label or value.
145 Default,
146 /// A `do`/`while` loop.
147 DoStatement {
148 /// Id of the node's body block.
149 block_id: NodeId,
150 /// Id of the condition expression node.
151 condition_id: NodeId,
152 },
153 /// An indexed element access (e.g. `a[i]`).
154 ElementAccess {
155 /// Id of the expression node.
156 expression_id: NodeId,
157 /// Id of the argument list node, if any.
158 arguments_id: Option<NodeId>,
159 },
160 /// The `else` branch of a conditional.
161 ElseClause {
162 /// Id of the node's body block.
163 block_id: NodeId,
164 },
165 /// A block of executable statements.
166 ExecutionBlock,
167 /// An export statement.
168 Export {
169 /// The expression, as written, if any.
170 expression: Option<String>,
171 /// Id of the declaration node, if any.
172 declaration_id: Option<NodeId>,
173 /// The bound local alias, if any.
174 alias: Option<String>,
175 /// Id of the default export's declaration node, if any.
176 default_id: Option<NodeId>,
177 },
178 /// A statement that is a bare expression.
179 ExpressionStatement,
180 /// The file's root node.
181 File,
182 /// The `finally` clause of a try statement.
183 FinallyClause {
184 /// Id of the node's body block, if any.
185 block_id: Option<NodeId>,
186 },
187 /// A `for-each`/`for-in` loop.
188 ForEachStatement {
189 /// Id of the declared variable's node.
190 variable_id: NodeId,
191 /// Id of the collection being iterated.
192 iterable_item_id: NodeId,
193 /// Id of the node's body block, if any.
194 block_id: Option<NodeId>,
195 },
196 /// A classic C-style `for` loop.
197 ForStatement {
198 /// Id of the node's body block.
199 block_id: NodeId,
200 /// Id of the loop's initializer node, if any.
201 initializer_id: Option<NodeId>,
202 /// Id of the condition expression node, if any.
203 condition_id: Option<NodeId>,
204 /// Id of the loop's update node, if any.
205 update_id: Option<NodeId>,
206 },
207 /// An `if` conditional.
208 If {
209 /// Id of the condition expression node.
210 condition_id: NodeId,
211 /// Id of the branch taken when the condition is true, if any.
212 true_id: Option<NodeId>,
213 /// Id of the branch taken when the condition is false, if any.
214 false_id: Option<NodeId>,
215 /// Id of the condition's initializer node (e.g. an `if let` binding), if any.
216 initializer: Option<NodeId>,
217 },
218 /// An import statement.
219 Import {
220 /// The expression, as written, if any.
221 expression: Option<String>,
222 /// The bound local alias, if any.
223 alias: Option<String>,
224 /// The imported method's name, if any.
225 method_name: Option<String>,
226 /// The import's kind keyword (e.g. `type`), if any.
227 import_type: Option<String>,
228 /// The imported module's name, if any.
229 module_name: Option<String>,
230 },
231 /// A JSX element.
232 JsxElement,
233 /// A lambda/closure's declared type.
234 LambdaFunctionType,
235 /// A literal value.
236 Literal {
237 /// The literal value, as written.
238 value: String,
239 /// The literal's type keyword.
240 value_type: String,
241 },
242 /// A member access expression (e.g. `a.b`).
243 MemberAccess {
244 /// The accessed member's name.
245 member: String,
246 /// The expression, as written.
247 expression: String,
248 /// Id of the expression node.
249 expression_id: NodeId,
250 /// Id of the node that scopes this symbol lookup, if any.
251 symbol_scope: Option<NodeId>,
252 },
253 /// A file's top-level metadata: structure, instances and imports.
254 Metadata {
255 /// The file's path.
256 path: String,
257 /// The file's top-level members, keyed by name.
258 structure: BTreeMap<String, FileStructData>,
259 /// Per-object metadata instances, keyed by qualified name.
260 instances: BTreeMap<String, BTreeMap<String, FileInstanceData>>,
261 /// The names this file imports.
262 imports: Vec<String>,
263 /// The file's package/namespace name, if any.
264 package: Option<String>,
265 },
266 /// A method or function declaration.
267 MethodDeclaration {
268 /// The node's name, as written, if any.
269 name: Option<String>,
270 /// The access modifier keywords, as written, if any.
271 access_modifiers: Option<String>,
272 /// Id of the node's body block, if any.
273 block_id: Option<NodeId>,
274 /// Id of the modifiers node, if any.
275 modifiers_id: Option<NodeId>,
276 /// Id of the parameter list node, if any.
277 parameters_id: Option<NodeId>,
278 },
279 /// A method or function call.
280 MethodInvocation {
281 /// The expression, as written.
282 expression: String,
283 /// The receiver's textual expression, if any.
284 object: Option<String>,
285 /// Id of the node that scopes this symbol lookup, if any.
286 symbol_scope: Option<NodeId>,
287 /// Id of the expression node, if any.
288 expression_id: Option<NodeId>,
289 /// Id of the argument list node, if any.
290 arguments_id: Option<NodeId>,
291 /// Id of the receiver node, if any.
292 object_id: Option<NodeId>,
293 /// Id of the node's body block, if any.
294 block_id: Option<NodeId>,
295 /// The receiver's fully qualified type name, when resolved, if any.
296 receiver_type_fqn: Option<String>,
297 },
298 /// A node the grammar could not classify.
299 MissingNode {
300 /// The original (unmapped) tree-sitter node type.
301 node_type: String,
302 },
303 /// A declaration's modifier list (e.g. `public static`).
304 Modifiers,
305 /// A whole-module import.
306 ModuleImport {
307 /// The expression, as written.
308 expression: String,
309 /// The bound local alias, if any.
310 alias: Option<String>,
311 /// The imported module's name, if any.
312 module_name: Option<String>,
313 },
314 /// A call argument passed by name.
315 NamedArgument {
316 /// Id of the value node.
317 value_id: NodeId,
318 /// The named argument's parameter name, if any.
319 argument_name: Option<String>,
320 },
321 /// A namespace/module declaration.
322 Namespace {
323 /// The node's name, as written.
324 name: String,
325 /// Id of the node's body block, if any.
326 block_id: Option<NodeId>,
327 },
328 /// An object-construction expression (`new X(...)`).
329 NewExpression {
330 /// Id of the constructor being invoked.
331 constructor_id: NodeId,
332 /// Id of the argument list node, if any.
333 arguments_id: Option<NodeId>,
334 },
335 /// An object literal or reference (e.g. a Terraform resource block).
336 Object {
337 /// The node's name, as written, if any.
338 name: Option<String>,
339 /// The Terraform block's reference expression, if any.
340 tf_reference: Option<String>,
341 },
342 /// An object instantiation with an initializer.
343 ObjectCreation {
344 /// The node's name, as written.
345 name: String,
346 /// Id of the argument list node, if any.
347 arguments_id: Option<NodeId>,
348 /// Id of the loop's initializer node, if any.
349 initializer_id: Option<NodeId>,
350 },
351 /// A key/value pair (e.g. in a map or object literal).
352 Pair {
353 /// Id of the pair's key node.
354 key_id: NodeId,
355 /// Id of the value node.
356 value_id: NodeId,
357 },
358 /// A function/method parameter.
359 Parameter {
360 /// The declared variable's name, if any.
361 variable: Option<String>,
362 /// The declared variable's type, as written, if any.
363 variable_type: Option<String>,
364 /// Id of the value node, if any.
365 value_id: Option<NodeId>,
366 /// The parameter's passing mode keyword (e.g. `ref`), if any.
367 parameter_mode: Option<String>,
368 },
369 /// A function/method's parameter list.
370 ParameterList,
371 /// A parenthesized expression.
372 ParenthesizedExpression,
373 /// A language reserved word used as a value (e.g. `self`).
374 ReservedWord {
375 /// The literal value, as written.
376 value: String,
377 },
378 /// A rest/spread binding pattern (e.g. `...rest`).
379 RestPattern {
380 /// Id of the value node.
381 value_id: NodeId,
382 },
383 /// A `return` statement.
384 Return {
385 /// Id of the value node, if any.
386 value_id: Option<NodeId>,
387 },
388 /// A Go-style type selector.
389 Selector {
390 /// The selector's name, if any.
391 selector_name: Option<String>,
392 },
393 /// A spread element in a call or literal (e.g. `...xs`).
394 SpreadElement {
395 /// Id of the value node.
396 value_id: NodeId,
397 },
398 /// A `switch` statement's body.
399 SwitchBody,
400 /// One `case`/`when` section of a switch.
401 SwitchSection {
402 /// The case label's expression, as written.
403 case_expression: String,
404 },
405 /// A `switch` statement.
406 SwitchStatement {
407 /// Id of the node's body block.
408 block_id: NodeId,
409 /// Id of the value node.
410 value_id: NodeId,
411 },
412 /// A bare symbol reference.
413 SymbolLookup {
414 /// The looked-up symbol's name.
415 symbol: String,
416 /// Id of the node that scopes this symbol lookup, if any.
417 symbol_scope: Option<NodeId>,
418 /// The literal value, as written, if any.
419 value: Option<String>,
420 },
421 /// A ternary conditional expression (`a ? b : c`).
422 TernaryOperation {
423 /// Id of the condition expression node.
424 condition_id: NodeId,
425 /// Id of the branch taken when the condition is true.
426 true_id: NodeId,
427 /// Id of the branch taken when the condition is false.
428 false_id: NodeId,
429 },
430 /// A `this`/`self` reference.
431 This {
432 /// The literal value, as written.
433 value: String,
434 },
435 /// A `throw`/`raise` statement.
436 ThrowStatement {
437 /// Id of the expression node, if any.
438 expression_id: Option<NodeId>,
439 },
440 /// A `try` statement.
441 TryStatement {
442 /// Id of the node's body block.
443 block_id: NodeId,
444 /// Id of the try-with-resources declaration node, if any.
445 resources_id: Option<NodeId>,
446 },
447 /// A unary operator expression.
448 UnaryExpression {
449 /// The operator, as written.
450 operator: String,
451 /// Id of the operand node.
452 operand_id: NodeId,
453 },
454 /// A `using`/`with` resource-scoped statement.
455 UsingStatement {
456 /// Id of the node's body block.
457 block_id: NodeId,
458 /// Id of the declaration node, if any.
459 declaration_id: Option<NodeId>,
460 },
461 /// A variable declaration.
462 VariableDeclaration {
463 /// The declared variable's name.
464 variable: String,
465 /// The declared variable's type, as written, if any.
466 variable_type: Option<String>,
467 /// Id of the value node, if any.
468 value_id: Option<NodeId>,
469 /// Id of the declared variable's node, if any.
470 variable_id: Option<NodeId>,
471 /// The access modifier keyword, if any.
472 access_modifier: Option<String>,
473 },
474 /// A `while` loop.
475 WhileStatement {
476 /// Id of the node's body block.
477 block_id: NodeId,
478 /// Id of the condition expression node, if any.
479 condition_id: Option<NodeId>,
480 },
481}
482
483impl SyntaxNode {
484 /// Id of this node's argument list, for variants that take one.
485 #[must_use]
486 pub const fn arguments_id(&self) -> Option<NodeId> {
487 match self {
488 Self::Annotation { arguments_id, .. }
489 | Self::ElementAccess { arguments_id, .. }
490 | Self::MethodInvocation { arguments_id, .. }
491 | Self::NewExpression { arguments_id, .. }
492 | Self::ObjectCreation { arguments_id, .. } => *arguments_id,
493 _ => None,
494 }
495 }
496
497 /// Id of this node's body block, for variants that have one.
498 #[must_use]
499 pub const fn block_id(&self) -> Option<NodeId> {
500 match self {
501 Self::CatchClause { block_id, .. }
502 | Self::Class { block_id, .. }
503 | Self::Declaration { block_id, .. }
504 | Self::FinallyClause { block_id }
505 | Self::ForEachStatement { block_id, .. }
506 | Self::MethodDeclaration { block_id, .. }
507 | Self::MethodInvocation { block_id, .. }
508 | Self::Namespace { block_id, .. } => *block_id,
509 Self::DoStatement { block_id, .. }
510 | Self::ElseClause { block_id }
511 | Self::ForStatement { block_id, .. }
512 | Self::SwitchStatement { block_id, .. }
513 | Self::TryStatement { block_id, .. }
514 | Self::UsingStatement { block_id, .. }
515 | Self::WhileStatement { block_id, .. } => Some(*block_id),
516 _ => None,
517 }
518 }
519
520 /// This node's declared name, for variants that are named.
521 #[must_use]
522 pub fn name(&self) -> Option<&str> {
523 match self {
524 Self::Annotation { name, .. }
525 | Self::Attribute { name }
526 | Self::Class { name, .. }
527 | Self::Declaration { name, .. }
528 | Self::Namespace { name, .. }
529 | Self::ObjectCreation { name, .. } => Some(name.as_str()),
530 Self::MethodDeclaration { name, .. } | Self::Object { name, .. } => name.as_deref(),
531 _ => None,
532 }
533 }
534
535 /// The declared variable's name, for variants that declare one.
536 #[must_use]
537 pub fn variable(&self) -> Option<&str> {
538 match self {
539 Self::VariableDeclaration { variable, .. } => Some(variable.as_str()),
540 Self::Parameter { variable, .. } => variable.as_deref(),
541 _ => None,
542 }
543 }
544
545 /// The looked-up symbol's name, for [`Self::SymbolLookup`].
546 #[must_use]
547 pub const fn symbol(&self) -> Option<&str> {
548 match self {
549 Self::SymbolLookup { symbol, .. } => Some(symbol.as_str()),
550 _ => None,
551 }
552 }
553
554 /// This node's expression text, for variants that carry one.
555 #[must_use]
556 pub fn expression(&self) -> Option<&str> {
557 match self {
558 Self::MemberAccess { expression, .. }
559 | Self::MethodInvocation { expression, .. }
560 | Self::ModuleImport { expression, .. } => Some(expression.as_str()),
561 Self::Export { expression, .. } | Self::Import { expression, .. } => {
562 expression.as_deref()
563 }
564 _ => None,
565 }
566 }
567
568 /// The bound local alias, for import/export variants that have one.
569 #[must_use]
570 pub fn alias(&self) -> Option<&str> {
571 match self {
572 Self::Export { alias, .. }
573 | Self::Import { alias, .. }
574 | Self::ModuleImport { alias, .. } => alias.as_deref(),
575 _ => None,
576 }
577 }
578
579 /// This node's literal value, as written, for variants that have one.
580 #[must_use]
581 pub fn value(&self) -> Option<&str> {
582 match self {
583 Self::Literal { value, .. } | Self::ReservedWord { value } | Self::This { value } => {
584 Some(value.as_str())
585 }
586 Self::SymbolLookup { value, .. } => value.as_deref(),
587 _ => None,
588 }
589 }
590
591 /// Id of the call receiver node, for [`Self::MethodInvocation`].
592 #[must_use]
593 pub const fn object_id(&self) -> Option<NodeId> {
594 match self {
595 Self::MethodInvocation { object_id, .. } => *object_id,
596 _ => None,
597 }
598 }
599
600 /// Id of this node's value node, for variants that carry one.
601 #[must_use]
602 pub const fn value_id(&self) -> Option<NodeId> {
603 match self {
604 Self::Assignment { value_id, .. }
605 | Self::Parameter { value_id, .. }
606 | Self::Return { value_id }
607 | Self::VariableDeclaration { value_id, .. } => *value_id,
608 Self::NamedArgument { value_id, .. }
609 | Self::Pair { value_id, .. }
610 | Self::RestPattern { value_id }
611 | Self::SpreadElement { value_id }
612 | Self::SwitchStatement { value_id, .. } => Some(*value_id),
613 _ => None,
614 }
615 }
616
617 /// Id of the declared variable's node, for variants that declare one.
618 #[must_use]
619 pub const fn variable_id(&self) -> Option<NodeId> {
620 match self {
621 Self::VariableDeclaration { variable_id, .. } => *variable_id,
622 Self::Assignment { variable_id, .. } | Self::ForEachStatement { variable_id, .. } => {
623 Some(*variable_id)
624 }
625 _ => None,
626 }
627 }
628
629 #[must_use]
630 pub(crate) const fn symbol_scope(&self) -> Option<NodeId> {
631 match self {
632 Self::MemberAccess { symbol_scope, .. }
633 | Self::MethodInvocation { symbol_scope, .. }
634 | Self::SymbolLookup { symbol_scope, .. } => *symbol_scope,
635 _ => None,
636 }
637 }
638
639 /// This node's syntax-kind label, e.g. `"MethodDeclaration"`.
640 #[must_use]
641 pub fn label_type(&self) -> &'static str {
642 SyntaxKind::from(self).into()
643 }
644}
645
646#[cfg(test)]
647mod tests {
648 use super::SyntaxNode;
649 use crate::NodeId;
650 use alloc::borrow::ToOwned;
651 use alloc::vec;
652
653 const BLOCK: NodeId = NodeId(42);
654
655 /// Every variant that owns a body must expose it through `block_id`.
656 ///
657 /// Symbolic evaluation descends into bodies through this accessor, so a
658 /// variant silently dropped from the match arm stops the traversal without
659 /// any other signal. The list is exhaustive on purpose: it is the only way
660 /// a removed arm shows up as a failure.
661 #[test]
662 #[allow(
663 clippy::too_many_lines,
664 reason = "the variant list is the point of the test"
665 )]
666 fn every_variant_that_owns_a_body_exposes_it_as_its_block_id() {
667 let nodes_with_body = vec![
668 SyntaxNode::CatchClause {
669 block_id: Some(BLOCK),
670 catch_declaration: None,
671 },
672 SyntaxNode::Class {
673 name: "Foo".to_owned(),
674 block_id: Some(BLOCK),
675 modifiers_id: None,
676 inherited_class: None,
677 access_modifiers: None,
678 },
679 SyntaxNode::Declaration {
680 name: "Foo".to_owned(),
681 block_id: Some(BLOCK),
682 access_modifiers: None,
683 },
684 SyntaxNode::FinallyClause {
685 block_id: Some(BLOCK),
686 },
687 SyntaxNode::ForEachStatement {
688 variable_id: NodeId(1),
689 iterable_item_id: NodeId(2),
690 block_id: Some(BLOCK),
691 },
692 SyntaxNode::MethodDeclaration {
693 name: None,
694 access_modifiers: None,
695 block_id: Some(BLOCK),
696 modifiers_id: None,
697 parameters_id: None,
698 },
699 SyntaxNode::MethodInvocation {
700 expression: "foo".to_owned(),
701 object: None,
702 symbol_scope: None,
703 expression_id: None,
704 arguments_id: None,
705 object_id: None,
706 block_id: Some(BLOCK),
707 receiver_type_fqn: None,
708 },
709 SyntaxNode::Namespace {
710 name: "ns".to_owned(),
711 block_id: Some(BLOCK),
712 },
713 SyntaxNode::DoStatement {
714 block_id: BLOCK,
715 condition_id: NodeId(1),
716 },
717 SyntaxNode::ElseClause { block_id: BLOCK },
718 SyntaxNode::ForStatement {
719 block_id: BLOCK,
720 initializer_id: None,
721 condition_id: None,
722 update_id: None,
723 },
724 SyntaxNode::SwitchStatement {
725 block_id: BLOCK,
726 value_id: NodeId(1),
727 },
728 SyntaxNode::TryStatement {
729 block_id: BLOCK,
730 resources_id: None,
731 },
732 SyntaxNode::UsingStatement {
733 block_id: BLOCK,
734 declaration_id: None,
735 },
736 SyntaxNode::WhileStatement {
737 block_id: BLOCK,
738 condition_id: None,
739 },
740 ];
741
742 for node in nodes_with_body {
743 assert_eq!(
744 node.block_id(),
745 Some(BLOCK),
746 "{} must expose its body",
747 node.label_type()
748 );
749 }
750 }
751
752 /// A variant without a body must not borrow one from a neighbouring arm.
753 #[test]
754 fn a_variant_without_a_body_reports_no_block_id() {
755 assert_eq!(SyntaxNode::Break.block_id(), None);
756 assert_eq!(
757 SyntaxNode::FinallyClause { block_id: None }.block_id(),
758 None
759 );
760 }
761}