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