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#[derive(Clone, PartialEq, Eq, Debug)]
12pub struct FileStructData {
13    pub node: NodeId,
14    pub kind: String,
15    pub data: FileStructValue,
16    pub node_range: Option<Vec<NodeId>>,
17}
18
19#[derive(Clone, PartialEq, Eq, Debug)]
20pub enum FileStructValue {
21    MethodName(String),
22    Children(BTreeMap<String, FileStructData>),
23}
24
25#[derive(Clone, PartialEq, Eq, Debug)]
26pub struct FileInstanceData {
27    pub object: String,
28    pub source: String,
29    pub source_type: String,
30}
31
32#[derive(Clone, PartialEq, Eq, Debug, EnumDiscriminants)]
33#[strum_discriminants(name(SyntaxKind), derive(PartialOrd, Ord, Hash, IntoStaticStr))]
34pub enum SyntaxNode {
35    Annotation {
36        name: String,
37        arguments_id: Option<NodeId>,
38    },
39    Argument,
40    ArgumentList,
41    ArrayInitializer {
42        variable_type: Option<String>,
43    },
44    Assignment {
45        variable_id: NodeId,
46        value_id: Option<NodeId>,
47        operator: Option<String>,
48    },
49    Attribute {
50        name: String,
51    },
52    AwaitExpression {
53        expression_id: NodeId,
54    },
55    BinaryOperation {
56        operator: String,
57        left_id: Option<NodeId>,
58        right_id: Option<NodeId>,
59    },
60    Break,
61    CatchClause {
62        block_id: Option<NodeId>,
63        catch_declaration: Option<NodeId>,
64    },
65    CatchDeclaration,
66    Class {
67        name: String,
68        block_id: Option<NodeId>,
69        modifiers_id: Option<NodeId>,
70        inherited_class: Option<String>,
71        access_modifiers: Option<String>,
72    },
73    ClassBody,
74    Comment {
75        comment: String,
76    },
77    Continue,
78    Declaration {
79        name: String,
80        block_id: Option<NodeId>,
81        access_modifiers: Option<String>,
82    },
83    DeclarationBlock,
84    Default,
85    DoStatement {
86        block_id: NodeId,
87        condition_id: NodeId,
88    },
89    ElementAccess {
90        expression_id: NodeId,
91        arguments_id: Option<NodeId>,
92    },
93    ElseClause {
94        block_id: NodeId,
95    },
96    ExecutionBlock,
97    Export {
98        expression: Option<String>,
99        declaration_id: Option<NodeId>,
100        alias: Option<String>,
101        default_id: Option<NodeId>,
102    },
103    ExpressionStatement,
104    File,
105    FinallyClause {
106        block_id: Option<NodeId>,
107    },
108    ForEachStatement {
109        variable_id: NodeId,
110        iterable_item_id: NodeId,
111        block_id: Option<NodeId>,
112    },
113    ForStatement {
114        block_id: NodeId,
115        initializer_id: Option<NodeId>,
116        condition_id: Option<NodeId>,
117        update_id: Option<NodeId>,
118    },
119    If {
120        condition_id: NodeId,
121        true_id: Option<NodeId>,
122        false_id: Option<NodeId>,
123        initializer: Option<NodeId>,
124    },
125    Import {
126        expression: Option<String>,
127        alias: Option<String>,
128        method_name: Option<String>,
129        import_type: Option<String>,
130        module_name: Option<String>,
131    },
132    JsxElement,
133    LambdaFunctionType,
134    Literal {
135        value: String,
136        value_type: String,
137    },
138    MemberAccess {
139        member: String,
140        expression: String,
141        expression_id: NodeId,
142        symbol_scope: Option<NodeId>,
143    },
144    Metadata {
145        path: String,
146        structure: BTreeMap<String, FileStructData>,
147        instances: BTreeMap<String, BTreeMap<String, FileInstanceData>>,
148        imports: Vec<String>,
149        package: Option<String>,
150    },
151    MethodDeclaration {
152        name: Option<String>,
153        access_modifiers: Option<String>,
154        block_id: Option<NodeId>,
155        modifiers_id: Option<NodeId>,
156        parameters_id: Option<NodeId>,
157    },
158    MethodInvocation {
159        expression: String,
160        object: Option<String>,
161        symbol_scope: Option<NodeId>,
162        expression_id: Option<NodeId>,
163        arguments_id: Option<NodeId>,
164        object_id: Option<NodeId>,
165        block_id: Option<NodeId>,
166        receiver_type_fqn: Option<String>,
167    },
168    MissingNode {
169        node_type: String,
170    },
171    Modifiers,
172    ModuleImport {
173        expression: String,
174        alias: Option<String>,
175        module_name: Option<String>,
176    },
177    NamedArgument {
178        value_id: NodeId,
179        argument_name: Option<String>,
180    },
181    Namespace {
182        name: String,
183        block_id: Option<NodeId>,
184    },
185    NewExpression {
186        constructor_id: NodeId,
187        arguments_id: Option<NodeId>,
188    },
189    Object {
190        name: Option<String>,
191        tf_reference: Option<String>,
192    },
193    ObjectCreation {
194        name: String,
195        arguments_id: Option<NodeId>,
196        initializer_id: Option<NodeId>,
197    },
198    Pair {
199        key_id: NodeId,
200        value_id: NodeId,
201    },
202    Parameter {
203        variable: Option<String>,
204        variable_type: Option<String>,
205        value_id: Option<NodeId>,
206        parameter_mode: Option<String>,
207    },
208    ParameterList,
209    ParenthesizedExpression,
210    ReservedWord {
211        value: String,
212    },
213    RestPattern {
214        value_id: NodeId,
215    },
216    Return {
217        value_id: Option<NodeId>,
218    },
219    Selector {
220        selector_name: Option<String>,
221    },
222    SpreadElement {
223        value_id: NodeId,
224    },
225    SwitchBody,
226    SwitchSection {
227        case_expression: String,
228    },
229    SwitchStatement {
230        block_id: NodeId,
231        value_id: NodeId,
232    },
233    SymbolLookup {
234        symbol: String,
235        symbol_scope: Option<NodeId>,
236        value: Option<String>,
237    },
238    TernaryOperation {
239        condition_id: NodeId,
240        true_id: NodeId,
241        false_id: NodeId,
242    },
243    This {
244        value: String,
245    },
246    ThrowStatement {
247        expression_id: Option<NodeId>,
248    },
249    TryStatement {
250        block_id: NodeId,
251        resources_id: Option<NodeId>,
252    },
253    UnaryExpression {
254        operator: String,
255        operand_id: NodeId,
256    },
257    UsingStatement {
258        block_id: NodeId,
259        declaration_id: Option<NodeId>,
260    },
261    VariableDeclaration {
262        variable: String,
263        variable_type: Option<String>,
264        value_id: Option<NodeId>,
265        variable_id: Option<NodeId>,
266        access_modifier: Option<String>,
267    },
268    WhileStatement {
269        block_id: NodeId,
270        condition_id: Option<NodeId>,
271    },
272}
273
274impl SyntaxNode {
275    #[must_use]
276    pub const fn arguments_id(&self) -> Option<NodeId> {
277        match self {
278            Self::Annotation { arguments_id, .. }
279            | Self::ElementAccess { arguments_id, .. }
280            | Self::MethodInvocation { arguments_id, .. }
281            | Self::NewExpression { arguments_id, .. }
282            | Self::ObjectCreation { arguments_id, .. } => *arguments_id,
283            _ => None,
284        }
285    }
286
287    #[must_use]
288    pub const fn block_id(&self) -> Option<NodeId> {
289        match self {
290            Self::CatchClause { block_id, .. }
291            | Self::Class { block_id, .. }
292            | Self::Declaration { block_id, .. }
293            | Self::FinallyClause { block_id }
294            | Self::ForEachStatement { block_id, .. }
295            | Self::MethodDeclaration { block_id, .. }
296            | Self::MethodInvocation { block_id, .. }
297            | Self::Namespace { block_id, .. } => *block_id,
298            Self::DoStatement { block_id, .. }
299            | Self::ElseClause { block_id }
300            | Self::ForStatement { block_id, .. }
301            | Self::SwitchStatement { block_id, .. }
302            | Self::TryStatement { block_id, .. }
303            | Self::UsingStatement { block_id, .. }
304            | Self::WhileStatement { block_id, .. } => Some(*block_id),
305            _ => None,
306        }
307    }
308
309    #[must_use]
310    pub fn name(&self) -> Option<&str> {
311        match self {
312            Self::Annotation { name, .. }
313            | Self::Attribute { name }
314            | Self::Class { name, .. }
315            | Self::Declaration { name, .. }
316            | Self::Namespace { name, .. }
317            | Self::ObjectCreation { name, .. } => Some(name.as_str()),
318            Self::MethodDeclaration { name, .. } | Self::Object { name, .. } => name.as_deref(),
319            _ => None,
320        }
321    }
322
323    #[must_use]
324    pub fn variable(&self) -> Option<&str> {
325        match self {
326            Self::VariableDeclaration { variable, .. } => Some(variable.as_str()),
327            Self::Parameter { variable, .. } => variable.as_deref(),
328            _ => None,
329        }
330    }
331
332    #[must_use]
333    pub const fn symbol(&self) -> Option<&str> {
334        match self {
335            Self::SymbolLookup { symbol, .. } => Some(symbol.as_str()),
336            _ => None,
337        }
338    }
339
340    #[must_use]
341    pub fn expression(&self) -> Option<&str> {
342        match self {
343            Self::MemberAccess { expression, .. }
344            | Self::MethodInvocation { expression, .. }
345            | Self::ModuleImport { expression, .. } => Some(expression.as_str()),
346            Self::Export { expression, .. } | Self::Import { expression, .. } => {
347                expression.as_deref()
348            }
349            _ => None,
350        }
351    }
352
353    #[must_use]
354    pub fn alias(&self) -> Option<&str> {
355        match self {
356            Self::Export { alias, .. }
357            | Self::Import { alias, .. }
358            | Self::ModuleImport { alias, .. } => alias.as_deref(),
359            _ => None,
360        }
361    }
362
363    #[must_use]
364    pub fn value(&self) -> Option<&str> {
365        match self {
366            Self::Literal { value, .. } | Self::ReservedWord { value } | Self::This { value } => {
367                Some(value.as_str())
368            }
369            Self::SymbolLookup { value, .. } => value.as_deref(),
370            _ => None,
371        }
372    }
373
374    #[must_use]
375    pub const fn object_id(&self) -> Option<NodeId> {
376        match self {
377            Self::MethodInvocation { object_id, .. } => *object_id,
378            _ => None,
379        }
380    }
381
382    #[must_use]
383    pub const fn value_id(&self) -> Option<NodeId> {
384        match self {
385            Self::Assignment { value_id, .. }
386            | Self::Parameter { value_id, .. }
387            | Self::Return { value_id }
388            | Self::VariableDeclaration { value_id, .. } => *value_id,
389            Self::NamedArgument { value_id, .. }
390            | Self::Pair { value_id, .. }
391            | Self::RestPattern { value_id }
392            | Self::SpreadElement { value_id }
393            | Self::SwitchStatement { value_id, .. } => Some(*value_id),
394            _ => None,
395        }
396    }
397
398    #[must_use]
399    pub const fn variable_id(&self) -> Option<NodeId> {
400        match self {
401            Self::VariableDeclaration { variable_id, .. } => *variable_id,
402            Self::Assignment { variable_id, .. } | Self::ForEachStatement { variable_id, .. } => {
403                Some(*variable_id)
404            }
405            _ => None,
406        }
407    }
408
409    #[must_use]
410    pub(crate) const fn symbol_scope(&self) -> Option<NodeId> {
411        match self {
412            Self::MemberAccess { symbol_scope, .. }
413            | Self::MethodInvocation { symbol_scope, .. }
414            | Self::SymbolLookup { symbol_scope, .. } => *symbol_scope,
415            _ => None,
416        }
417    }
418
419    #[must_use]
420    pub fn label_type(&self) -> &'static str {
421        SyntaxKind::from(self).into()
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::SyntaxNode;
428    use crate::NodeId;
429    use alloc::borrow::ToOwned;
430    use alloc::vec;
431
432    const BLOCK: NodeId = NodeId(42);
433
434    /// Every variant that owns a body must expose it through `block_id`.
435    ///
436    /// Symbolic evaluation descends into bodies through this accessor, so a
437    /// variant silently dropped from the match arm stops the traversal without
438    /// any other signal. The list is exhaustive on purpose: it is the only way
439    /// a removed arm shows up as a failure.
440    #[test]
441    #[allow(
442        clippy::too_many_lines,
443        reason = "the variant list is the point of the test"
444    )]
445    fn every_variant_that_owns_a_body_exposes_it_as_its_block_id() {
446        let nodes_with_body = vec![
447            SyntaxNode::CatchClause {
448                block_id: Some(BLOCK),
449                catch_declaration: None,
450            },
451            SyntaxNode::Class {
452                name: "Foo".to_owned(),
453                block_id: Some(BLOCK),
454                modifiers_id: None,
455                inherited_class: None,
456                access_modifiers: None,
457            },
458            SyntaxNode::Declaration {
459                name: "Foo".to_owned(),
460                block_id: Some(BLOCK),
461                access_modifiers: None,
462            },
463            SyntaxNode::FinallyClause {
464                block_id: Some(BLOCK),
465            },
466            SyntaxNode::ForEachStatement {
467                variable_id: NodeId(1),
468                iterable_item_id: NodeId(2),
469                block_id: Some(BLOCK),
470            },
471            SyntaxNode::MethodDeclaration {
472                name: None,
473                access_modifiers: None,
474                block_id: Some(BLOCK),
475                modifiers_id: None,
476                parameters_id: None,
477            },
478            SyntaxNode::MethodInvocation {
479                expression: "foo".to_owned(),
480                object: None,
481                symbol_scope: None,
482                expression_id: None,
483                arguments_id: None,
484                object_id: None,
485                block_id: Some(BLOCK),
486                receiver_type_fqn: None,
487            },
488            SyntaxNode::Namespace {
489                name: "ns".to_owned(),
490                block_id: Some(BLOCK),
491            },
492            SyntaxNode::DoStatement {
493                block_id: BLOCK,
494                condition_id: NodeId(1),
495            },
496            SyntaxNode::ElseClause { block_id: BLOCK },
497            SyntaxNode::ForStatement {
498                block_id: BLOCK,
499                initializer_id: None,
500                condition_id: None,
501                update_id: None,
502            },
503            SyntaxNode::SwitchStatement {
504                block_id: BLOCK,
505                value_id: NodeId(1),
506            },
507            SyntaxNode::TryStatement {
508                block_id: BLOCK,
509                resources_id: None,
510            },
511            SyntaxNode::UsingStatement {
512                block_id: BLOCK,
513                declaration_id: None,
514            },
515            SyntaxNode::WhileStatement {
516                block_id: BLOCK,
517                condition_id: None,
518            },
519        ];
520
521        for node in nodes_with_body {
522            assert_eq!(
523                node.block_id(),
524                Some(BLOCK),
525                "{} must expose its body",
526                node.label_type()
527            );
528        }
529    }
530
531    /// A variant without a body must not borrow one from a neighbouring arm.
532    #[test]
533    fn a_variant_without_a_body_reports_no_block_id() {
534        assert_eq!(SyntaxNode::Break.block_id(), None);
535        assert_eq!(
536            SyntaxNode::FinallyClause { block_id: None }.block_id(),
537            None
538        );
539    }
540}