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