Extract docstrings from `# comment` lines preceding a node. In Nix, comments may be siblings of the node at the same level, or they may be at the parent level (e.g., a comment before `binding_set` in a `let_expression` or `attrset_expression`).
(state: &ExtractionState, node: TsNode<'_>)
| 809 | /// may be at the parent level (e.g., a comment before `binding_set` in a |
| 810 | /// `let_expression` or `attrset_expression`). |
| 811 | fn extract_docstring(state: &ExtractionState, node: TsNode<'_>) -> Option<String> { |
| 812 | let mut comments: Vec<String> = Vec::new(); |
| 813 | let mut prev = node.prev_named_sibling(); |
| 814 | |
| 815 | // If no previous sibling at this level, check the parent's previous sibling. |
| 816 | // This handles cases where the comment is a child of `let_expression` or |
| 817 | // `attrset_expression` but the binding is inside `binding_set`. |
| 818 | if prev.is_none() { |
| 819 | if let Some(parent) = node.parent() { |
| 820 | if parent.kind() == "binding_set" { |
| 821 | prev = parent.prev_named_sibling(); |
| 822 | } |
| 823 | } |
| 824 | } |
| 825 | |
| 826 | while let Some(prev_node) = prev { |
| 827 | if prev_node.kind() == "comment" { |
| 828 | let text = state.node_text(prev_node); |
| 829 | let stripped = text.trim_start_matches('#').trim().to_string(); |
| 830 | comments.push(stripped); |
| 831 | prev = prev_node.prev_named_sibling(); |
| 832 | } else { |
| 833 | break; |
| 834 | } |
| 835 | } |
| 836 | if comments.is_empty() { |
| 837 | return None; |
| 838 | } |
| 839 | comments.reverse(); |
| 840 | Some(comments.join("\n")) |
| 841 | } |
| 842 | |
| 843 | /// Recursively find call nodes (`apply_expression`) and create unresolved Calls references. |
| 844 | /// Also handles Enhancement 2: import path resolution. |