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