1use 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 #[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 #[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}