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