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)]
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 #[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 #[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}