Skip to main content

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}