1use std::path::PathBuf;
2
3use tree_sitter::Node;
4
5use nomograph_core::types::{Diagnostic, Severity, Span};
6
7use crate::element::SysmlElement;
8use crate::relationship::SysmlRelationship;
9
10pub fn is_element_node(kind: &str) -> bool {
11 kind.ends_with("_definition") || kind.ends_with("_usage") || kind == "library_package"
12}
13
14fn is_body_node(kind: &str) -> bool {
15 matches!(
16 kind,
17 "package_body"
18 | "structural_body"
19 | "usage_body"
20 | "action_body"
21 | "constraint_body"
22 | "enumeration_body"
23 | "requirement_body"
24 | "state_body"
25 )
26}
27
28fn strip_quotes(s: &str) -> &str {
29 s.strip_prefix('\'')
30 .and_then(|s| s.strip_suffix('\''))
31 .unwrap_or(s)
32}
33
34fn extract_element_name(node: &Node, source: &str) -> Option<String> {
35 for i in 0..node.child_count() {
36 if let Some(child) = node.child(i) {
37 if child.kind() == "identification" {
38 return find_name_in_identification(&child, source);
39 }
40 }
41 }
42
43 for i in 0..node.child_count() {
44 if let Some(child) = node.child(i) {
45 if child.kind() == "usage_declaration" {
46 for j in 0..child.child_count() {
47 if let Some(gc) = child.child(j) {
48 if gc.kind() == "identification" {
49 return find_name_in_identification(&gc, source);
50 }
51 }
52 }
53 if let Some(rel_part) = find_child_by_kind(&child, "relationship_part") {
54 if let Some(redef) = find_child_by_kind(&rel_part, "redefinition_part") {
55 if let Some(fc) = find_child_by_kind(&redef, "feature_chain") {
56 return extract_feature_chain_text(&fc, source);
57 }
58 }
59 }
60 }
61 }
62 }
63
64 if let Some(fc) = find_child_by_kind(node, "feature_chain") {
65 return extract_feature_chain_text(&fc, source);
66 }
67
68 None
69}
70
71fn find_name_in_identification(node: &Node, source: &str) -> Option<String> {
72 for i in 0..node.child_count() {
73 if let Some(child) = node.child(i) {
74 if child.kind() == "name" {
75 return child
76 .utf8_text(source.as_bytes())
77 .ok()
78 .map(|s| strip_quotes(s).to_string());
79 }
80 }
81 }
82 None
83}
84
85fn extract_typed_by(node: &Node, source: &str) -> Option<String> {
86 let decl = find_child_by_kind(node, "usage_declaration")?;
87 let typing = find_child_by_kind(&decl, "typing_part")?;
88 let qname = find_child_by_kind(&typing, "qualified_name")?;
89 extract_qualified_name_text(&qname, source)
90}
91
92fn extract_qualified_name_text(node: &Node, source: &str) -> Option<String> {
93 let mut parts = Vec::new();
94 for i in 0..node.child_count() {
95 if let Some(child) = node.child(i) {
96 if child.kind() == "name" {
97 if let Ok(text) = child.utf8_text(source.as_bytes()) {
98 parts.push(strip_quotes(text).to_string());
99 }
100 }
101 }
102 }
103 if parts.is_empty() {
104 None
105 } else {
106 Some(parts.join("::"))
107 }
108}
109
110fn find_child_by_kind<'a>(node: &Node<'a>, kind: &str) -> Option<Node<'a>> {
111 for i in 0..node.child_count() {
112 if let Some(child) = node.child(i) {
113 if child.kind() == kind {
114 return Some(child);
115 }
116 }
117 }
118 None
119}
120
121fn extract_doc_comment(body_node: &Node, source: &str) -> Option<String> {
122 for i in 0..body_node.child_count() {
123 if let Some(child) = body_node.child(i) {
124 if child.kind() == "documentation" {
125 if let Some(comment_body) = find_child_by_kind(&child, "block_comment_body") {
126 if let Ok(text) = comment_body.utf8_text(source.as_bytes()) {
127 return Some(clean_doc_comment(text));
128 }
129 }
130 }
131 }
132 }
133 None
134}
135
136fn clean_doc_comment(text: &str) -> String {
137 let trimmed = text
138 .strip_prefix("/*")
139 .unwrap_or(text)
140 .strip_suffix("*/")
141 .unwrap_or(text);
142 trimmed
143 .lines()
144 .map(|line| {
145 let stripped = line.trim();
146 stripped
147 .strip_prefix("* ")
148 .unwrap_or(stripped.strip_prefix('*').unwrap_or(stripped))
149 })
150 .collect::<Vec<_>>()
151 .join(" ")
152 .trim()
153 .to_string()
154}
155
156fn find_body_child<'a>(node: &Node<'a>) -> Option<Node<'a>> {
157 for i in 0..node.child_count() {
158 if let Some(child) = node.child(i) {
159 if is_body_node(child.kind()) {
160 return Some(child);
161 }
162 }
163 }
164 None
165}
166
167fn extract_feature_chain_text(node: &Node, source: &str) -> Option<String> {
168 let mut parts = Vec::new();
169 for i in 0..node.child_count() {
170 if let Some(child) = node.child(i) {
171 if child.kind() == "name" {
172 if let Ok(text) = child.utf8_text(source.as_bytes()) {
173 parts.push(strip_quotes(text).to_string());
174 }
175 }
176 }
177 }
178 if parts.is_empty() {
179 None
180 } else {
181 Some(parts.join("."))
182 }
183}
184
185fn find_children_by_kind<'a>(node: &Node<'a>, kind: &str) -> Vec<Node<'a>> {
186 let mut result = Vec::new();
187 for i in 0..node.child_count() {
188 if let Some(child) = node.child(i) {
189 if child.kind() == kind {
190 result.push(child);
191 }
192 }
193 }
194 result
195}
196
197fn extract_binding_value_text(node: &Node, source: &str) -> Option<String> {
198 for i in 0..node.child_count() {
199 if let Some(child) = node.child(i) {
200 if child.is_named() {
201 if let Some(fce) = find_child_by_kind(&child, "qualified_name") {
202 return extract_qualified_name_text(&fce, source);
203 }
204 if let Some(fc) = find_child_by_kind(&child, "feature_chain") {
205 return extract_feature_chain_text(&fc, source);
206 }
207 return child
208 .utf8_text(source.as_bytes())
209 .ok()
210 .map(|s| s.trim().to_string());
211 }
212 }
213 }
214 None
215}
216
217fn extract_reference_text(node: &Node, source: &str) -> Option<String> {
218 if let Some(qname) = find_child_by_kind(node, "qualified_name") {
219 return extract_qualified_name_text(&qname, source);
220 }
221 if let Some(fc) = find_child_by_kind(node, "feature_chain") {
222 return extract_feature_chain_text(&fc, source);
223 }
224 if let Some(name) = find_child_by_kind(node, "name") {
225 return name
226 .utf8_text(source.as_bytes())
227 .ok()
228 .map(|s| strip_quotes(s).to_string());
229 }
230 None
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
234pub(crate) enum RelationshipKind {
235 Satisfy,
236 Verify,
237 Import,
238 Specialize,
239 Allocate,
240 Connect,
241 Bind,
242 Flow,
243 Stream,
244 Dependency,
245 Redefine,
246 Expose,
247 Perform,
248 Exhibit,
249 Include,
250 Succession,
251 Transition,
252 Send,
253 Accept,
254 Require,
255 Assume,
256 Assert,
257 Assign,
258 Subject,
259 Render,
260 Frame,
261 Message,
262 TypedBy,
263 Member,
264}
265
266impl RelationshipKind {
267 #[cfg(test)]
268 pub(crate) const ALL: &[RelationshipKind] = &[
269 Self::Satisfy,
270 Self::Verify,
271 Self::Import,
272 Self::Specialize,
273 Self::Allocate,
274 Self::Connect,
275 Self::Bind,
276 Self::Flow,
277 Self::Stream,
278 Self::Dependency,
279 Self::Redefine,
280 Self::Expose,
281 Self::Perform,
282 Self::Exhibit,
283 Self::Include,
284 Self::Succession,
285 Self::Transition,
286 Self::Send,
287 Self::Accept,
288 Self::Require,
289 Self::Assume,
290 Self::Assert,
291 Self::Assign,
292 Self::Subject,
293 Self::Render,
294 Self::Frame,
295 Self::Message,
296 Self::TypedBy,
297 Self::Member,
298 ];
299}
300
301impl std::fmt::Display for RelationshipKind {
302 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303 match self {
304 Self::Satisfy => write!(f, "Satisfy"),
305 Self::Verify => write!(f, "Verify"),
306 Self::Import => write!(f, "Import"),
307 Self::Specialize => write!(f, "Specialize"),
308 Self::Allocate => write!(f, "Allocate"),
309 Self::Connect => write!(f, "Connect"),
310 Self::Bind => write!(f, "Bind"),
311 Self::Flow => write!(f, "Flow"),
312 Self::Stream => write!(f, "Stream"),
313 Self::Dependency => write!(f, "Dependency"),
314 Self::Redefine => write!(f, "Redefine"),
315 Self::Expose => write!(f, "Expose"),
316 Self::Perform => write!(f, "Perform"),
317 Self::Exhibit => write!(f, "Exhibit"),
318 Self::Include => write!(f, "Include"),
319 Self::Succession => write!(f, "Succession"),
320 Self::Transition => write!(f, "Transition"),
321 Self::Send => write!(f, "Send"),
322 Self::Accept => write!(f, "Accept"),
323 Self::Require => write!(f, "Require"),
324 Self::Assume => write!(f, "Assume"),
325 Self::Assert => write!(f, "Assert"),
326 Self::Assign => write!(f, "Assign"),
327 Self::Subject => write!(f, "Subject"),
328 Self::Render => write!(f, "Render"),
329 Self::Frame => write!(f, "Frame"),
330 Self::Message => write!(f, "Message"),
331 Self::TypedBy => write!(f, "TypedBy"),
332 Self::Member => write!(f, "Member"),
333 }
334 }
335}
336
337pub(crate) const RELATIONSHIP_DISPATCH: &[(&str, RelationshipKind)] = &[
338 ("satisfy_statement", RelationshipKind::Satisfy),
339 ("verify_statement", RelationshipKind::Verify),
340 ("import_statement", RelationshipKind::Import),
341 ("definition_specialization", RelationshipKind::Specialize),
342 ("allocate_statement", RelationshipKind::Allocate),
343 ("connect_statement", RelationshipKind::Connect),
344 ("bind_statement", RelationshipKind::Bind),
345 ("flow_statement", RelationshipKind::Flow),
346 ("flow_usage", RelationshipKind::Flow),
347 ("stream_statement", RelationshipKind::Stream),
348 ("dependency", RelationshipKind::Dependency),
349 ("perform_statement", RelationshipKind::Perform),
350 ("exhibit_usage", RelationshipKind::Exhibit),
351 ("include_statement", RelationshipKind::Include),
352 ("expose_statement", RelationshipKind::Expose),
353 ("then_succession", RelationshipKind::Succession),
354 ("succession_statement", RelationshipKind::Succession),
355 ("first_statement", RelationshipKind::Succession),
356 ("message_statement", RelationshipKind::Message),
357 ("redefines_statement", RelationshipKind::Redefine),
358 ("specialization_statement", RelationshipKind::Specialize),
359 ("transition_statement", RelationshipKind::Transition),
360 ("send_statement", RelationshipKind::Send),
361 ("accept_then_statement", RelationshipKind::Accept),
362 ("require_statement", RelationshipKind::Require),
363 ("assume_statement", RelationshipKind::Assume),
364 ("assert_statement", RelationshipKind::Assert),
365 ("assign_statement", RelationshipKind::Assign),
366 ("subject_statement", RelationshipKind::Subject),
367 ("render_statement", RelationshipKind::Render),
368 ("frame_statement", RelationshipKind::Frame),
369];
370
371fn dispatch_relationship_kind(node_kind: &str) -> Option<RelationshipKind> {
372 RELATIONSHIP_DISPATCH
373 .iter()
374 .find(|(k, _)| *k == node_kind)
375 .map(|(_, v)| *v)
376}
377
378fn node_span(node: &Node) -> Span {
379 let start = node.start_position();
380 let end = node.end_position();
381 Span {
382 start_line: start.row as u32,
383 start_col: start.column as u32,
384 end_line: end.row as u32,
385 end_col: end.column as u32,
386 }
387}
388
389pub struct Walker<'a> {
390 source: &'a str,
391 file_path: PathBuf,
392 name_stack: Vec<String>,
393 pub elements: Vec<SysmlElement>,
394 pub relationships: Vec<SysmlRelationship>,
395 anon_counter: usize,
396}
397
398impl<'a> Walker<'a> {
399 pub fn new(source: &'a str, file_path: PathBuf) -> Self {
400 Self {
401 source,
402 file_path,
403 name_stack: Vec::new(),
404 elements: Vec::new(),
405 relationships: Vec::new(),
406 anon_counter: 0,
407 }
408 }
409
410 fn qualified_name(&self) -> String {
411 self.name_stack.join("::")
412 }
413
414 pub fn walk_root(&mut self, root: Node<'a>) {
415 for i in 0..root.child_count() {
416 if let Some(child) = root.child(i) {
417 if child.is_named() {
418 self.walk_node(child);
419 }
420 }
421 }
422 }
423
424 fn walk_node(&mut self, node: Node<'a>) {
425 let kind = node.kind();
426 if !is_element_node(kind) {
427 return;
428 }
429
430 let name = extract_element_name(&node, self.source);
431 if name.is_none() {
432 return;
433 }
434
435 let display_name = name.clone().unwrap_or_else(|| {
436 self.anon_counter += 1;
437 format!("<anonymous_{}>", self.anon_counter)
438 });
439
440 self.name_stack.push(display_name);
441 let qname = self.qualified_name();
442 let span = node_span(&node);
443
444 let body = find_body_child(&node);
445 let doc = body
446 .as_ref()
447 .and_then(|b| extract_doc_comment(b, self.source));
448 let typed_by = extract_typed_by(&node, self.source);
449 let members = body
450 .as_ref()
451 .map(|b| self.collect_member_names(b, &qname))
452 .unwrap_or_default();
453
454 if let Some(ref ty) = typed_by {
455 self.relationships.push(SysmlRelationship {
456 source: qname.clone(),
457 target: ty.clone(),
458 kind: RelationshipKind::TypedBy.to_string(),
459 file_path: self.file_path.clone(),
460 span: span.clone(),
461 });
462 }
463
464 let parent_qname = if self.name_stack.len() > 1 {
465 Some(self.name_stack[..self.name_stack.len() - 1].join("::"))
466 } else {
467 None
468 };
469
470 if let Some(ref pq) = parent_qname {
471 self.relationships.push(SysmlRelationship {
472 source: pq.clone(),
473 target: qname.clone(),
474 kind: RelationshipKind::Member.to_string(),
475 file_path: self.file_path.clone(),
476 span: span.clone(),
477 });
478 }
479
480 self.extract_definition_relationships(&node, &qname);
481 self.extract_binding_value(&node, &qname);
482
483 let elem = SysmlElement {
484 qualified_name: qname.clone(),
485 kind: kind.to_string(),
486 file_path: self.file_path.clone(),
487 span,
488 doc,
489 attributes: Vec::new(),
490 members,
491 layer: crate::vocabulary::classify_layer(kind),
492 };
493 self.elements.push(elem);
494
495 if let Some(body) = find_body_child(&node) {
496 self.extract_body_relationships(&body, &qname);
497 for i in 0..body.child_count() {
498 if let Some(child) = body.child(i) {
499 if child.is_named() {
500 self.walk_node(child);
501 }
502 }
503 }
504 }
505
506 self.name_stack.pop();
507 }
508
509 fn collect_member_names(&self, body: &Node, parent_qname: &str) -> Vec<String> {
510 let mut names = Vec::new();
511 for i in 0..body.child_count() {
512 if let Some(child) = body.child(i) {
513 if is_element_node(child.kind()) {
514 if let Some(name) = extract_element_name(&child, self.source) {
515 names.push(format!("{}::{}", parent_qname, name));
516 }
517 }
518 }
519 }
520 names
521 }
522
523 fn extract_definition_relationships(&mut self, node: &Node, qname: &str) {
524 if let Some(spec) = find_child_by_kind(node, "definition_specialization") {
525 let span = node_span(&spec);
526 for qn in find_children_by_kind(&spec, "qualified_name") {
527 if let Some(target) = extract_qualified_name_text(&qn, self.source) {
528 self.relationships.push(SysmlRelationship {
529 source: qname.to_string(),
530 target,
531 kind: RelationshipKind::Specialize.to_string(),
532 file_path: self.file_path.clone(),
533 span: span.clone(),
534 });
535 }
536 }
537 }
538
539 if let Some(decl) = find_child_by_kind(node, "usage_declaration") {
540 if let Some(rel_part) = find_child_by_kind(&decl, "relationship_part") {
541 if let Some(redef) = find_child_by_kind(&rel_part, "redefinition_part") {
542 let span = node_span(&redef);
543 if let Some(fc) = find_child_by_kind(&redef, "feature_chain") {
544 if let Some(target) = extract_feature_chain_text(&fc, self.source) {
545 self.relationships.push(SysmlRelationship {
546 source: qname.to_string(),
547 target,
548 kind: RelationshipKind::Redefine.to_string(),
549 file_path: self.file_path.clone(),
550 span,
551 });
552 }
553 }
554 }
555 if let Some(spec) = find_child_by_kind(&rel_part, "specialization_part") {
556 let span = node_span(&spec);
557 for fc in find_children_by_kind(&spec, "feature_chain") {
558 if let Some(target) = extract_feature_chain_text(&fc, self.source) {
559 self.relationships.push(SysmlRelationship {
560 source: qname.to_string(),
561 target,
562 kind: RelationshipKind::Specialize.to_string(),
563 file_path: self.file_path.clone(),
564 span: span.clone(),
565 });
566 }
567 }
568 for qn in find_children_by_kind(&spec, "qualified_name") {
569 if let Some(target) = extract_qualified_name_text(&qn, self.source) {
570 self.relationships.push(SysmlRelationship {
571 source: qname.to_string(),
572 target,
573 kind: RelationshipKind::Specialize.to_string(),
574 file_path: self.file_path.clone(),
575 span: span.clone(),
576 });
577 }
578 }
579 }
580 }
581 }
582 }
583
584 fn extract_binding_value(&mut self, node: &Node, qname: &str) {
585 let value = find_child_by_kind(node, "value_part").or_else(|| {
586 find_child_by_kind(node, "usage_declaration").and_then(|decl| {
587 find_child_by_kind(&decl, "relationship_part").and_then(|rp| {
588 find_child_by_kind(&rp, "redefinition_part")
589 .and_then(|rd| find_child_by_kind(&rd, "value_part"))
590 })
591 })
592 });
593 if let Some(vp) = value {
594 let target = extract_reference_text(&vp, self.source)
595 .or_else(|| extract_binding_value_text(&vp, self.source));
596 if let Some(target) = target {
597 self.relationships.push(SysmlRelationship {
598 source: qname.to_string(),
599 target,
600 kind: RelationshipKind::Bind.to_string(),
601 file_path: self.file_path.clone(),
602 span: node_span(&vp),
603 });
604 }
605 }
606 }
607
608 fn extract_body_relationships(&mut self, body: &Node, context_qname: &str) {
609 for i in 0..body.child_count() {
610 if let Some(child) = body.child(i) {
611 if !child.is_named() {
612 continue;
613 }
614 let kind = child.kind();
615 if let Some(rel_kind) = dispatch_relationship_kind(kind) {
616 self.extract_relationship(&child, rel_kind, context_qname);
617 }
618 if kind == "allocate_statement" {
619 self.extract_nested_allocates(&child, context_qname);
620 }
621 }
622 }
623 }
624
625 fn extract_relationship(
626 &mut self,
627 node: &Node,
628 rel_kind: RelationshipKind,
629 context_qname: &str,
630 ) {
631 let span = node_span(node);
632 let file_path = self.file_path.clone();
633
634 match rel_kind {
635 RelationshipKind::Satisfy => {
636 let qnames = find_children_by_kind(node, "qualified_name");
637 let fchains = find_children_by_kind(node, "feature_chain");
638 let target = qnames
639 .first()
640 .and_then(|n| extract_qualified_name_text(n, self.source))
641 .unwrap_or_default();
642 let source = fchains
643 .first()
644 .and_then(|n| extract_feature_chain_text(n, self.source))
645 .unwrap_or_else(|| context_qname.to_string());
646 if !target.is_empty() {
647 self.relationships.push(SysmlRelationship {
648 source,
649 target,
650 kind: rel_kind.to_string(),
651 file_path,
652 span,
653 });
654 }
655 }
656 RelationshipKind::Verify => {
657 if let Some(target) = extract_reference_text(node, self.source) {
658 self.relationships.push(SysmlRelationship {
659 source: context_qname.to_string(),
660 target,
661 kind: rel_kind.to_string(),
662 file_path,
663 span,
664 });
665 }
666 }
667 RelationshipKind::Import => {
668 if let Some(import_ref) = find_child_by_kind(node, "import_reference") {
669 let target = if let Some(wildcard) =
670 find_child_by_kind(&import_ref, "wildcard_import")
671 {
672 find_child_by_kind(&wildcard, "name").and_then(|n| {
673 n.utf8_text(self.source.as_bytes())
674 .ok()
675 .map(|s| format!("{}::*", strip_quotes(s)))
676 })
677 } else if let Some(qn) = find_child_by_kind(&import_ref, "qualified_name") {
678 extract_qualified_name_text(&qn, self.source)
679 } else {
680 None
681 };
682 if let Some(target) = target {
683 self.relationships.push(SysmlRelationship {
684 source: context_qname.to_string(),
685 target,
686 kind: rel_kind.to_string(),
687 file_path,
688 span,
689 });
690 }
691 }
692 }
693 RelationshipKind::Connect => {
694 let endpoints = find_children_by_kind(node, "connect_endpoint");
695 let texts: Vec<String> = endpoints
696 .iter()
697 .filter_map(|ep| {
698 find_child_by_kind(ep, "feature_chain")
699 .and_then(|fc| extract_feature_chain_text(&fc, self.source))
700 })
701 .collect();
702 if texts.len() >= 2 {
703 self.relationships.push(SysmlRelationship {
704 source: texts[0].clone(),
705 target: texts[1].clone(),
706 kind: rel_kind.to_string(),
707 file_path,
708 span,
709 });
710 }
711 }
712 RelationshipKind::Allocate => {
713 let fchains = find_children_by_kind(node, "feature_chain");
714 if fchains.len() >= 2 {
715 let source =
716 extract_feature_chain_text(&fchains[0], self.source).unwrap_or_default();
717 let target =
718 extract_feature_chain_text(&fchains[1], self.source).unwrap_or_default();
719 if !source.is_empty() && !target.is_empty() {
720 self.relationships.push(SysmlRelationship {
721 source,
722 target,
723 kind: rel_kind.to_string(),
724 file_path,
725 span,
726 });
727 }
728 }
729 }
730 RelationshipKind::Flow => {
731 let fchains = if node.kind() == "flow_usage" {
732 find_child_by_kind(node, "flow_part")
733 .map(|fp| find_children_by_kind(&fp, "feature_chain"))
734 .unwrap_or_default()
735 } else {
736 find_children_by_kind(node, "feature_chain")
737 };
738 if fchains.len() >= 2 {
739 let source =
740 extract_feature_chain_text(&fchains[0], self.source).unwrap_or_default();
741 let target =
742 extract_feature_chain_text(&fchains[1], self.source).unwrap_or_default();
743 if !source.is_empty() && !target.is_empty() {
744 self.relationships.push(SysmlRelationship {
745 source,
746 target,
747 kind: rel_kind.to_string(),
748 file_path,
749 span,
750 });
751 }
752 }
753 }
754 RelationshipKind::Dependency => {
755 let qnames = find_children_by_kind(node, "qualified_name");
756 if qnames.len() >= 2 {
757 let source =
758 extract_qualified_name_text(&qnames[0], self.source).unwrap_or_default();
759 for qn in &qnames[1..] {
760 if let Some(target) = extract_qualified_name_text(qn, self.source) {
761 self.relationships.push(SysmlRelationship {
762 source: source.clone(),
763 target,
764 kind: rel_kind.to_string(),
765 file_path: file_path.clone(),
766 span: span.clone(),
767 });
768 }
769 }
770 }
771 }
772 RelationshipKind::Perform => {
773 if let Some(target) = find_child_by_kind(node, "feature_chain")
774 .and_then(|fc| extract_feature_chain_text(&fc, self.source))
775 {
776 self.relationships.push(SysmlRelationship {
777 source: context_qname.to_string(),
778 target,
779 kind: rel_kind.to_string(),
780 file_path,
781 span,
782 });
783 }
784 }
785 RelationshipKind::Exhibit => {
786 let target = find_child_by_kind(node, "feature_chain")
787 .and_then(|fc| extract_feature_chain_text(&fc, self.source))
788 .or_else(|| {
789 find_child_by_kind(node, "qualified_name")
790 .and_then(|qn| extract_qualified_name_text(&qn, self.source))
791 });
792 if let Some(target) = target {
793 self.relationships.push(SysmlRelationship {
794 source: context_qname.to_string(),
795 target,
796 kind: rel_kind.to_string(),
797 file_path,
798 span,
799 });
800 }
801 }
802 RelationshipKind::Succession => {
803 let node_kind = node.kind();
804 if node_kind == "succession_statement" {
805 let fchains = find_children_by_kind(node, "feature_chain");
806 if fchains.len() >= 2 {
807 let source = extract_feature_chain_text(&fchains[0], self.source)
808 .unwrap_or_default();
809 let target = extract_feature_chain_text(&fchains[1], self.source)
810 .unwrap_or_default();
811 if !source.is_empty() && !target.is_empty() {
812 self.relationships.push(SysmlRelationship {
813 source,
814 target,
815 kind: rel_kind.to_string(),
816 file_path,
817 span,
818 });
819 }
820 }
821 } else if node_kind == "first_statement" {
822 let fchains = find_children_by_kind(node, "feature_chain");
823 if fchains.len() >= 2 {
824 let source = extract_feature_chain_text(&fchains[0], self.source)
825 .unwrap_or_default();
826 let target = extract_feature_chain_text(&fchains[1], self.source)
827 .unwrap_or_default();
828 if !source.is_empty() && !target.is_empty() {
829 self.relationships.push(SysmlRelationship {
830 source,
831 target,
832 kind: rel_kind.to_string(),
833 file_path,
834 span,
835 });
836 }
837 } else if let Some(target) = find_child_by_kind(node, "name").and_then(|n| {
838 n.utf8_text(self.source.as_bytes())
839 .ok()
840 .map(|s| strip_quotes(s).to_string())
841 }) {
842 self.relationships.push(SysmlRelationship {
843 source: context_qname.to_string(),
844 target,
845 kind: rel_kind.to_string(),
846 file_path,
847 span,
848 });
849 }
850 } else if let Some(target) = find_child_by_kind(node, "name").and_then(|n| {
851 n.utf8_text(self.source.as_bytes())
852 .ok()
853 .map(|s| strip_quotes(s).to_string())
854 }) {
855 self.relationships.push(SysmlRelationship {
856 source: context_qname.to_string(),
857 target,
858 kind: rel_kind.to_string(),
859 file_path,
860 span,
861 });
862 }
863 }
864 RelationshipKind::Message => {
865 let fchains = find_children_by_kind(node, "feature_chain");
866 if fchains.len() >= 2 {
867 let source =
868 extract_feature_chain_text(&fchains[0], self.source).unwrap_or_default();
869 let target =
870 extract_feature_chain_text(&fchains[1], self.source).unwrap_or_default();
871 if !source.is_empty() && !target.is_empty() {
872 self.relationships.push(SysmlRelationship {
873 source,
874 target,
875 kind: rel_kind.to_string(),
876 file_path,
877 span,
878 });
879 }
880 } else if let Some(target) = extract_reference_text(node, self.source) {
881 self.relationships.push(SysmlRelationship {
882 source: context_qname.to_string(),
883 target,
884 kind: rel_kind.to_string(),
885 file_path,
886 span,
887 });
888 }
889 }
890 RelationshipKind::Stream => {
891 let fchains = find_children_by_kind(node, "feature_chain");
892 if fchains.len() >= 2 {
893 let source =
894 extract_feature_chain_text(&fchains[0], self.source).unwrap_or_default();
895 let target =
896 extract_feature_chain_text(&fchains[1], self.source).unwrap_or_default();
897 if !source.is_empty() && !target.is_empty() {
898 self.relationships.push(SysmlRelationship {
899 source,
900 target,
901 kind: rel_kind.to_string(),
902 file_path,
903 span,
904 });
905 }
906 }
907 }
908 RelationshipKind::Accept => {
909 if let Some(trigger) = find_child_by_kind(node, "trigger_kind") {
910 if let Some(target) = extract_reference_text(&trigger, self.source) {
911 self.relationships.push(SysmlRelationship {
912 source: context_qname.to_string(),
913 target,
914 kind: rel_kind.to_string(),
915 file_path,
916 span,
917 });
918 }
919 }
920 }
921 RelationshipKind::Assert => {
922 let mut found_nested = false;
923 for i in 0..node.child_count() {
924 if let Some(child) = node.child(i) {
925 let ck = child.kind();
926 if ck == "satisfy_statement" {
927 self.extract_relationship(
928 &child,
929 RelationshipKind::Satisfy,
930 context_qname,
931 );
932 found_nested = true;
933 } else if ck == "verify_statement" {
934 self.extract_relationship(
935 &child,
936 RelationshipKind::Verify,
937 context_qname,
938 );
939 found_nested = true;
940 }
941 }
942 }
943 if !found_nested {
944 if let Some(target) = extract_reference_text(node, self.source) {
945 self.relationships.push(SysmlRelationship {
946 source: context_qname.to_string(),
947 target,
948 kind: rel_kind.to_string(),
949 file_path,
950 span,
951 });
952 }
953 }
954 }
955 RelationshipKind::Redefine if node.kind() == "redefines_statement" => {
956 let qnames = find_children_by_kind(node, "qualified_name");
957 let fchains = find_children_by_kind(node, "feature_chain");
958 for qn in &qnames {
959 if let Some(target) = extract_qualified_name_text(qn, self.source) {
960 self.relationships.push(SysmlRelationship {
961 source: context_qname.to_string(),
962 target,
963 kind: rel_kind.to_string(),
964 file_path: file_path.clone(),
965 span: span.clone(),
966 });
967 }
968 }
969 for fc in &fchains {
970 if let Some(target) = extract_feature_chain_text(fc, self.source) {
971 self.relationships.push(SysmlRelationship {
972 source: context_qname.to_string(),
973 target,
974 kind: rel_kind.to_string(),
975 file_path: file_path.clone(),
976 span: span.clone(),
977 });
978 }
979 }
980 }
981 RelationshipKind::Specialize if node.kind() == "specialization_statement" => {
982 if let Some(target) = find_child_by_kind(node, "qualified_name")
983 .and_then(|qn| extract_qualified_name_text(&qn, self.source))
984 {
985 self.relationships.push(SysmlRelationship {
986 source: context_qname.to_string(),
987 target,
988 kind: rel_kind.to_string(),
989 file_path,
990 span,
991 });
992 }
993 }
994 _ => {
995 if let Some(target) = extract_reference_text(node, self.source) {
996 self.relationships.push(SysmlRelationship {
997 source: context_qname.to_string(),
998 target,
999 kind: rel_kind.to_string(),
1000 file_path,
1001 span,
1002 });
1003 }
1004 }
1005 }
1006 }
1007
1008 fn extract_nested_allocates(&mut self, node: &Node, context_qname: &str) {
1009 for i in 0..node.child_count() {
1010 if let Some(child) = node.child(i) {
1011 if child.kind() == "allocate_statement" {
1012 self.extract_relationship(&child, RelationshipKind::Allocate, context_qname);
1013 self.extract_nested_allocates(&child, context_qname);
1014 }
1015 }
1016 }
1017 }
1018}
1019
1020pub fn collect_parse_errors(node: Node, source: &str, diagnostics: &mut Vec<Diagnostic>) {
1021 if node.is_error() {
1022 let start = node.start_position();
1023 let end = node.end_position();
1024 let text = node.utf8_text(source.as_bytes()).unwrap_or("<invalid>");
1025 let context = if text.len() > 30 {
1026 format!("{}...", &text[..30])
1027 } else {
1028 text.to_string()
1029 };
1030 diagnostics.push(Diagnostic {
1031 severity: Severity::Error,
1032 message: format!("Syntax error near '{}'", context),
1033 span: Span {
1034 start_line: start.row as u32,
1035 start_col: start.column as u32,
1036 end_line: end.row as u32,
1037 end_col: end.column as u32,
1038 },
1039 });
1040 } else if node.is_missing() {
1041 let start = node.start_position();
1042 let end = node.end_position();
1043 diagnostics.push(Diagnostic {
1044 severity: Severity::Error,
1045 message: format!("Missing {}", node.kind()),
1046 span: Span {
1047 start_line: start.row as u32,
1048 start_col: start.column as u32,
1049 end_line: end.row as u32,
1050 end_col: end.column as u32,
1051 },
1052 });
1053 }
1054
1055 for i in 0..node.child_count() {
1056 if let Some(child) = node.child(i) {
1057 collect_parse_errors(child, source, diagnostics);
1058 }
1059 }
1060}