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