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 },
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 #[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 #[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}