Skip to main content

arity_parser/ast/
nodes.rs

1use rowan::ast::support;
2use rowan::{SyntaxElement, SyntaxToken, TextRange, TextSize};
3use smol_str::SmolStr;
4
5use crate::ast::AstNode;
6use crate::ast::kinds::{is_binary_operator, is_trivia, is_unary_operator};
7use crate::ast::tokens::{AstToken, StringLit, token_name};
8use crate::syntax::{RLanguage, SyntaxKind, SyntaxNode};
9
10macro_rules! ast_node {
11    ($name:ident, $kind:expr) => {
12        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
13        pub struct $name(SyntaxNode);
14
15        impl AstNode for $name {
16            type Language = RLanguage;
17
18            fn can_cast(kind: SyntaxKind) -> bool {
19                kind == $kind
20            }
21
22            fn cast(syntax: SyntaxNode) -> Option<Self> {
23                Self::can_cast(syntax.kind()).then(|| Self(syntax))
24            }
25
26            fn syntax(&self) -> &SyntaxNode {
27                &self.0
28            }
29        }
30    };
31}
32
33ast_node!(Root, SyntaxKind::ROOT);
34ast_node!(AssignmentExpr, SyntaxKind::ASSIGNMENT_EXPR);
35ast_node!(BinaryExpr, SyntaxKind::BINARY_EXPR);
36ast_node!(UnaryExpr, SyntaxKind::UNARY_EXPR);
37ast_node!(ParenExpr, SyntaxKind::PAREN_EXPR);
38ast_node!(CallExpr, SyntaxKind::CALL_EXPR);
39ast_node!(SubsetExpr, SyntaxKind::SUBSET_EXPR);
40ast_node!(Subset2Expr, SyntaxKind::SUBSET2_EXPR);
41ast_node!(ArgList, SyntaxKind::ARG_LIST);
42ast_node!(Arg, SyntaxKind::ARG);
43ast_node!(IfExpr, SyntaxKind::IF_EXPR);
44ast_node!(ForExpr, SyntaxKind::FOR_EXPR);
45ast_node!(WhileExpr, SyntaxKind::WHILE_EXPR);
46ast_node!(RepeatExpr, SyntaxKind::REPEAT_EXPR);
47ast_node!(FunctionExpr, SyntaxKind::FUNCTION_EXPR);
48ast_node!(BlockExpr, SyntaxKind::BLOCK_EXPR);
49ast_node!(RoxygenBlock, SyntaxKind::ROXYGEN_BLOCK);
50ast_node!(RoxygenSection, SyntaxKind::ROXYGEN_SECTION);
51ast_node!(RoxygenParagraph, SyntaxKind::ROXYGEN_PARAGRAPH);
52ast_node!(RoxygenTag, SyntaxKind::ROXYGEN_TAG);
53
54/// The first direct child token of `node` with the given `kind`.
55fn first_child_token(node: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken<RLanguage>> {
56    node.children_with_tokens()
57        .find_map(|element| match element {
58            SyntaxElement::Token(token) if token.kind() == kind => Some(token),
59            _ => None,
60        })
61}
62
63impl RoxygenBlock {
64    /// The logical sections of this block, in source order: the intro section
65    /// (untagged leading prose) and one section per `@tag`.
66    pub fn sections(&self) -> impl Iterator<Item = RoxygenSection> + '_ {
67        self.0.children().filter_map(RoxygenSection::cast)
68    }
69
70    /// The tags of this block, in source order (`@param`, `@export`, …).
71    pub fn tags(&self) -> impl Iterator<Item = RoxygenTag> + '_ {
72        self.sections().filter_map(|section| section.tag())
73    }
74
75    /// Whether any tag of this block is named `name` (without the leading `@`).
76    pub fn has_tag(&self, name: &str) -> bool {
77        self.tags().any(|tag| tag.name().as_deref() == Some(name))
78    }
79
80    /// The intro section — the untagged leading prose whose first paragraph is
81    /// the topic title. `None` when the block starts with a tag.
82    pub fn intro(&self) -> Option<RoxygenSection> {
83        self.sections().find(|section| section.tag().is_none())
84    }
85}
86
87impl RoxygenSection {
88    /// The tag heading this section, if it is a tag section (`@details`, …); the
89    /// intro section has none.
90    pub fn tag(&self) -> Option<RoxygenTag> {
91        self.0.children().find_map(RoxygenTag::cast)
92    }
93
94    /// The prose paragraphs of this section, in source order.
95    pub fn paragraphs(&self) -> impl Iterator<Item = RoxygenParagraph> + '_ {
96        self.0.children().filter_map(RoxygenParagraph::cast)
97    }
98
99    /// Whether the section carries any prose beyond the tag head: text folded
100    /// onto the tag line (`@param x A number.`) or a continuation paragraph.
101    pub fn has_prose(&self) -> bool {
102        self.tag().is_some_and(|tag| tag.value_text().is_some())
103            || self
104                .0
105                .children()
106                .any(|child| child.kind() != SyntaxKind::ROXYGEN_TAG)
107    }
108}
109
110impl RoxygenTag {
111    /// The `@` token.
112    pub fn at(&self) -> Option<SyntaxToken<RLanguage>> {
113        first_child_token(&self.0, SyntaxKind::ROXYGEN_AT)
114    }
115
116    /// The tag name (e.g. `param`, `examples`), without the leading `@`.
117    pub fn name(&self) -> Option<SmolStr> {
118        first_child_token(&self.0, SyntaxKind::ROXYGEN_TAG_NAME).map(|t| SmolStr::new(t.text()))
119    }
120
121    /// The name argument for arg-bearing tags (e.g. `x` in `@param x ...`).
122    pub fn arg(&self) -> Option<SyntaxToken<RLanguage>> {
123        first_child_token(&self.0, SyntaxKind::ROXYGEN_TAG_ARG)
124    }
125
126    /// The remaining prose content of the tag, if any.
127    pub fn text(&self) -> Option<SyntaxToken<RLanguage>> {
128        first_child_token(&self.0, SyntaxKind::ROXYGEN_TEXT)
129    }
130
131    /// The tag's whole same-line value, with the roxygen markers dropped and
132    /// the ends trimmed: every token after the tag head (`@name`, plus the arg
133    /// token for an arg-bearing tag), joined.
134    ///
135    /// [`RoxygenTag::text`] returns the *first* `ROXYGEN_TEXT` leaf alone,
136    /// which under `@md` is only part of the value: an intraword `_` or `*` is
137    /// carved as an unresolved `ROXYGEN_MD_DELIM`, so `@rdname missing_arg` is
138    /// three leaves. Reach for this whenever the value is a *name* rather than
139    /// prose (`@name`, `@rdname`, `@describeIn`) — those tags never fold
140    /// continuation lines, so their node spans exactly one line.
141    pub fn value_text(&self) -> Option<String> {
142        let head_end = self.arg().map(|arg| arg.text_range().end()).or_else(|| {
143            first_child_token(&self.0, SyntaxKind::ROXYGEN_TAG_NAME)
144                .map(|name| name.text_range().end())
145        })?;
146        let value: String = self
147            .0
148            .descendants_with_tokens()
149            .filter_map(|element| element.into_token())
150            .filter(|token| {
151                token.text_range().start() >= head_end && token.kind() != SyntaxKind::ROXYGEN_MARKER
152            })
153            .map(|token| token.text().to_string())
154            .collect();
155        let trimmed = value.trim();
156        (!trimmed.is_empty()).then(|| trimmed.to_string())
157    }
158
159    /// Whether this is an `@examples`/`@examplesIf` tag (embedded R code).
160    pub fn is_examples(&self) -> bool {
161        matches!(self.name().as_deref(), Some("examples" | "examplesIf"))
162    }
163
164    /// The comma-separated names in the tag's arg (`@param a,b` documents both
165    /// `a` and `b`), each with the sub-range of its own text inside the arg
166    /// token. Empty for a tag with no arg.
167    pub fn arg_names(&self) -> Vec<(SmolStr, TextRange)> {
168        let Some(arg) = self.arg() else {
169            return Vec::new();
170        };
171        let arg_start = arg.text_range().start();
172        let mut names = Vec::new();
173        let mut offset = 0usize;
174        for piece in arg.text().split(',') {
175            let trimmed = piece.trim();
176            if !trimmed.is_empty() {
177                let lead = piece.len() - piece.trim_start().len();
178                let start = arg_start + TextSize::from((offset + lead) as u32);
179                names.push((
180                    SmolStr::new(trimmed),
181                    TextRange::at(start, TextSize::of(trimmed)),
182                ));
183            }
184            offset += piece.len() + 1;
185        }
186        names
187    }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct ForExprParts {
192    pub leading_comments: Vec<SyntaxToken<RLanguage>>,
193    pub variable_elements: Vec<SyntaxElement<RLanguage>>,
194    pub sequence_elements: Vec<SyntaxElement<RLanguage>>,
195    pub post_clause_comments: Vec<SyntaxToken<RLanguage>>,
196    pub body: Option<SyntaxElement<RLanguage>>,
197}
198
199#[derive(Debug, Clone, PartialEq, Eq)]
200pub struct WhileExprParts {
201    pub leading_comments: Vec<SyntaxToken<RLanguage>>,
202    pub condition_elements: Vec<SyntaxElement<RLanguage>>,
203    pub post_clause_comments: Vec<SyntaxToken<RLanguage>>,
204    pub body: Option<SyntaxElement<RLanguage>>,
205}
206
207impl Root {
208    pub fn expressions(&self) -> impl Iterator<Item = SyntaxNode> {
209        self.syntax().children()
210    }
211}
212
213impl AssignmentExpr {
214    /// The assignment operator kind. Returns one of `ASSIGN_LEFT` (`<-`),
215    /// `ASSIGN_RIGHT` (`->`), `SUPER_ASSIGN` (`<<-`), `SUPER_ASSIGN_RIGHT`
216    /// (`->>`), `ASSIGN_EQ` (`=`), or `WALRUS` (`:=`).
217    pub fn op_kind(&self) -> Option<SyntaxKind> {
218        self.syntax()
219            .children_with_tokens()
220            .find_map(|element| match element {
221                SyntaxElement::Token(token) if is_assignment_op(token.kind()) => Some(token.kind()),
222                _ => None,
223            })
224    }
225
226    /// The operator token, if present.
227    pub fn op_token(&self) -> Option<SyntaxToken<RLanguage>> {
228        self.syntax()
229            .children_with_tokens()
230            .find_map(|element| match element {
231                SyntaxElement::Token(token) if is_assignment_op(token.kind()) => Some(token),
232                _ => None,
233            })
234    }
235
236    /// The element on the *target* side of the operator (the side that becomes
237    /// the binding). For `<-`/`=`/`<<-`/`:=` this is the left element; for
238    /// `->`/`->>` it is the right element.
239    pub fn target_element(&self) -> Option<SyntaxElement<RLanguage>> {
240        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
241        let op_idx = assignment_op_index(&elements)?;
242        let kind = element_kind(&elements[op_idx])?;
243        let (start, end) = if is_right_assign(kind) {
244            (op_idx + 1, elements.len())
245        } else {
246            (0, op_idx)
247        };
248        elements[start..end]
249            .iter()
250            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
251            .cloned()
252    }
253
254    /// The element on the *value* side of the operator.
255    pub fn value_element(&self) -> Option<SyntaxElement<RLanguage>> {
256        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
257        let op_idx = assignment_op_index(&elements)?;
258        let kind = element_kind(&elements[op_idx])?;
259        let (start, end) = if is_right_assign(kind) {
260            (0, op_idx)
261        } else {
262            (op_idx + 1, elements.len())
263        };
264        elements[start..end]
265            .iter()
266            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
267            .cloned()
268    }
269
270    /// If the target is a simple `IDENT` (or backtick-quoted identifier), the
271    /// bound name as a `SmolStr`. Returns `None` for complex LHS like
272    /// `dim(x) <- ...`, `x[1] <- ...`, `x$y <- ...`.
273    pub fn target_name(&self) -> Option<SmolStr> {
274        match self.target_element()? {
275            SyntaxElement::Token(token) if token.kind() == SyntaxKind::IDENT => {
276                Some(SmolStr::new(token.text()))
277            }
278            SyntaxElement::Token(token) if token.kind() == SyntaxKind::STRING => {
279                StringLit::cast(token).and_then(|s| s.unquote().map(SmolStr::new))
280            }
281            _ => None,
282        }
283    }
284
285    /// The target name token, useful when the binding site's range is needed.
286    pub fn target_name_token(&self) -> Option<SyntaxToken<RLanguage>> {
287        match self.target_element()? {
288            SyntaxElement::Token(token)
289                if token.kind() == SyntaxKind::IDENT || token.kind() == SyntaxKind::STRING =>
290            {
291                Some(token)
292            }
293            _ => None,
294        }
295    }
296}
297
298impl FunctionExpr {
299    pub fn lparen_index(&self) -> Option<usize> {
300        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
301        elements.iter().position(|e| e.kind() == SyntaxKind::LPAREN)
302    }
303
304    pub fn rparen_index(&self) -> Option<usize> {
305        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
306        let lparen_idx = self.lparen_index()?;
307        let mut depth = 0usize;
308        elements
309            .iter()
310            .enumerate()
311            .skip(lparen_idx)
312            .find_map(|(i, el)| match el.kind() {
313                SyntaxKind::LPAREN => {
314                    depth += 1;
315                    None
316                }
317                SyntaxKind::RPAREN => {
318                    depth = depth.saturating_sub(1);
319                    if depth == 0 { Some(i) } else { None }
320                }
321                _ => None,
322            })
323    }
324
325    /// The function's formal arguments in source order, including their
326    /// optional default-value syntax and exact source ranges.
327    pub fn formals(&self) -> Vec<Formal> {
328        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
329        let Some(lparen_idx) = self.lparen_index() else {
330            return Vec::new();
331        };
332        let Some(rparen_idx) = self.rparen_index() else {
333            return Vec::new();
334        };
335
336        split_function_formals(&elements[lparen_idx + 1..rparen_idx])
337            .into_iter()
338            .filter_map(formal_from_elements)
339            .collect()
340    }
341
342    /// The function's parameters as a names-only compatibility projection.
343    /// Use [`Self::formals`] when default syntax or full formal ranges are
344    /// needed.
345    pub fn params(&self) -> Vec<Param> {
346        self.formals()
347            .into_iter()
348            .map(|formal| Param {
349                name: formal.name,
350                name_token: formal.name_token,
351            })
352            .collect()
353    }
354
355    /// The function body — the expression that follows the `)`. May be any
356    /// expression node (a `BLOCK_EXPR` for `function(x) { ... }`, or any other
357    /// expression for `function(x) x + 1`). Returns `None` if the body was
358    /// missing or replaced by an error-recovery placeholder.
359    pub fn body(&self) -> Option<SyntaxElement<RLanguage>> {
360        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
361        let rparen_idx = self.rparen_index()?;
362        elements[rparen_idx + 1..]
363            .iter()
364            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
365            .cloned()
366    }
367}
368
369/// One function formal, retaining its name, complete source range, and
370/// optional default-value syntax.
371#[derive(Debug, Clone, PartialEq, Eq, Hash)]
372pub struct Formal {
373    name: SmolStr,
374    name_token: SyntaxToken<RLanguage>,
375    range: TextRange,
376    default: Option<SyntaxElement<RLanguage>>,
377}
378
379impl Formal {
380    /// The formal's name as written, including backticks for a non-syntactic
381    /// name.
382    pub fn name(&self) -> SmolStr {
383        self.name.clone()
384    }
385
386    /// The name token and its exact source range.
387    pub fn name_token(&self) -> SyntaxToken<RLanguage> {
388        self.name_token.clone()
389    }
390
391    /// The whole formal range, excluding its comma and surrounding trivia.
392    pub fn text_range(&self) -> TextRange {
393        self.range
394    }
395
396    /// The default-value syntax, whether a token atom or compound node.
397    pub fn default(&self) -> Option<SyntaxElement<RLanguage>> {
398        self.default.clone()
399    }
400
401    /// The exact source range of [`Self::default`].
402    pub fn default_range(&self) -> Option<TextRange> {
403        self.default.as_ref().map(SyntaxElement::text_range)
404    }
405}
406
407fn split_function_formals(
408    elements: &[SyntaxElement<RLanguage>],
409) -> Vec<&[SyntaxElement<RLanguage>]> {
410    let mut formals = Vec::new();
411    let mut start = 0usize;
412    let mut depth = 0usize;
413
414    for (index, element) in elements.iter().enumerate() {
415        match element.kind() {
416            SyntaxKind::LPAREN | SyntaxKind::LBRACK | SyntaxKind::LBRACK2 | SyntaxKind::LBRACE => {
417                depth += 1
418            }
419            SyntaxKind::RPAREN | SyntaxKind::RBRACK | SyntaxKind::RBRACK2 | SyntaxKind::RBRACE => {
420                depth = depth.saturating_sub(1)
421            }
422            SyntaxKind::COMMA if depth == 0 => {
423                formals.push(&elements[start..index]);
424                start = index + 1;
425            }
426            _ => {}
427        }
428    }
429    formals.push(&elements[start..]);
430    formals
431}
432
433fn formal_from_elements(elements: &[SyntaxElement<RLanguage>]) -> Option<Formal> {
434    let name_index = elements
435        .iter()
436        .position(|element| !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT)?;
437    let SyntaxElement::Token(name_token) = &elements[name_index] else {
438        return None;
439    };
440    if name_token.kind() != SyntaxKind::IDENT {
441        return None;
442    }
443
444    let eq_index = elements[name_index + 1..]
445        .iter()
446        .position(|element| element.kind() == SyntaxKind::ASSIGN_EQ)
447        .map(|index| name_index + 1 + index);
448    let default = eq_index.and_then(|index| {
449        elements[index + 1..]
450            .iter()
451            .find(|element| !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT)
452            .filter(|element| crate::ast::Expr::cast((*element).clone()).is_some())
453            .cloned()
454    });
455    let end = default
456        .as_ref()
457        .map(SyntaxElement::text_range)
458        .map(TextRange::end)
459        .or_else(|| eq_index.map(|index| elements[index].text_range().end()))
460        .unwrap_or_else(|| name_token.text_range().end());
461
462    Some(Formal {
463        name: SmolStr::new(name_token.text()),
464        name_token: name_token.clone(),
465        range: TextRange::new(name_token.text_range().start(), end),
466        default,
467    })
468}
469
470#[derive(Debug, Clone)]
471pub struct Param {
472    pub name: SmolStr,
473    pub name_token: SyntaxToken<RLanguage>,
474}
475
476fn is_assignment_op(kind: SyntaxKind) -> bool {
477    matches!(
478        kind,
479        SyntaxKind::ASSIGN_LEFT
480            | SyntaxKind::ASSIGN_RIGHT
481            | SyntaxKind::SUPER_ASSIGN
482            | SyntaxKind::SUPER_ASSIGN_RIGHT
483            | SyntaxKind::ASSIGN_EQ
484            | SyntaxKind::WALRUS
485    )
486}
487
488fn is_right_assign(kind: SyntaxKind) -> bool {
489    matches!(
490        kind,
491        SyntaxKind::ASSIGN_RIGHT | SyntaxKind::SUPER_ASSIGN_RIGHT
492    )
493}
494
495fn assignment_op_index(elements: &[SyntaxElement<RLanguage>]) -> Option<usize> {
496    elements
497        .iter()
498        .position(|e| matches!(e, SyntaxElement::Token(t) if is_assignment_op(t.kind())))
499}
500
501fn element_kind(element: &SyntaxElement<RLanguage>) -> Option<SyntaxKind> {
502    Some(element.kind())
503}
504
505/// The RHS member `IDENT` of a `pkg::name` / `pkg:::name` `BINARY_EXPR`, if that
506/// is its shape. `None` for any other binary expression (a different operator,
507/// or a non-`IDENT` member).
508fn namespace_member_token(node: &SyntaxNode) -> Option<SyntaxToken<RLanguage>> {
509    let mut seen_ns_op = false;
510    for element in node.children_with_tokens() {
511        match element {
512            SyntaxElement::Token(t)
513                if matches!(t.kind(), SyntaxKind::COLON2 | SyntaxKind::COLON3) =>
514            {
515                seen_ns_op = true;
516            }
517            SyntaxElement::Token(t) if seen_ns_op && t.kind() == SyntaxKind::IDENT => {
518                return Some(t);
519            }
520            _ => {}
521        }
522    }
523    None
524}
525
526impl CallExpr {
527    pub fn arg_list(&self) -> Option<ArgList> {
528        support::child(self.syntax())
529    }
530
531    /// The callee name token — the `IDENT`/`STRING` immediately preceding the
532    /// argument list, when the function being called is a simple name (`foo(…)`,
533    /// `` `+`(…) ``) or a namespace-qualified name (`pkg::fn(…)`, where the token
534    /// is the `fn` member). Returns `None` for a genuinely computed callee
535    /// (`(g())(…)`, `x$f(…)`).
536    pub fn callee_token(&self) -> Option<SyntaxToken<RLanguage>> {
537        for element in self.syntax().children_with_tokens() {
538            match element {
539                SyntaxElement::Token(token)
540                    if is_trivia(token.kind()) || token.kind() == SyntaxKind::COMMENT =>
541                {
542                    continue;
543                }
544                SyntaxElement::Token(token)
545                    if matches!(token.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
546                {
547                    return Some(token);
548                }
549                // `pkg::fn(…)` / `pkg:::fn(…)` parses with the whole call wrapping
550                // a `pkg::fn` `BINARY_EXPR` callee. The callee *name* is that
551                // binary's RHS member, so the qualified form resolves like the
552                // bare `fn(…)` for every consumer (linter matchers, signature
553                // help, call hierarchy). Other binary callees (`obj$m(…)`) stay
554                // computed.
555                SyntaxElement::Node(node) if node.kind() == SyntaxKind::BINARY_EXPR => {
556                    return namespace_member_token(&node);
557                }
558                _ => return None,
559            }
560        }
561        None
562    }
563
564    /// The callee name (`foo` in `foo(…)`, `` + `` in `` `+`(…) ``), unquoted,
565    /// when the callee is a simple name. `None` for a computed callee.
566    pub fn callee_name(&self) -> Option<SmolStr> {
567        self.callee_token().as_ref().map(token_name)
568    }
569
570    /// The callee element — everything before the argument list's `(`. Generalizes
571    /// [`CallExpr::callee_token`] to the computed-callee case (`(g())(…)`,
572    /// `x$f(…)`), where it returns the callee *node* instead of a name token.
573    pub fn base(&self) -> Option<SyntaxElement<RLanguage>> {
574        last_element_before(self.syntax(), SyntaxKind::LPAREN)
575    }
576}
577
578impl SubsetExpr {
579    /// The base being indexed — everything before the `[`.
580    pub fn base(&self) -> Option<SyntaxElement<RLanguage>> {
581        last_element_before(self.syntax(), SyntaxKind::LBRACK)
582    }
583
584    pub fn open_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
585        first_child_token(self.syntax(), SyntaxKind::LBRACK)
586    }
587
588    pub fn close_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
589        first_child_token(self.syntax(), SyntaxKind::RBRACK)
590    }
591
592    pub fn arg_list(&self) -> Option<ArgList> {
593        support::child(self.syntax())
594    }
595
596    pub fn args(&self) -> impl Iterator<Item = Arg> {
597        self.arg_list()
598            .map(|list| list.args().collect::<Vec<_>>())
599            .unwrap_or_default()
600            .into_iter()
601    }
602}
603
604impl Subset2Expr {
605    /// The base being indexed — everything before the `[[`.
606    pub fn base(&self) -> Option<SyntaxElement<RLanguage>> {
607        last_element_before(self.syntax(), SyntaxKind::LBRACK2)
608    }
609
610    pub fn open_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
611        first_child_token(self.syntax(), SyntaxKind::LBRACK2)
612    }
613
614    pub fn close_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
615        first_child_token(self.syntax(), SyntaxKind::RBRACK2)
616    }
617
618    pub fn arg_list(&self) -> Option<ArgList> {
619        support::child(self.syntax())
620    }
621
622    pub fn args(&self) -> impl Iterator<Item = Arg> {
623        self.arg_list()
624            .map(|list| list.args().collect::<Vec<_>>())
625            .unwrap_or_default()
626            .into_iter()
627    }
628}
629
630/// The last non-trivia, non-comment element before the first direct-child token
631/// of `boundary` kind (e.g. the callee before `(`, the base before `[`).
632fn last_element_before(
633    node: &SyntaxNode,
634    boundary: SyntaxKind,
635) -> Option<SyntaxElement<RLanguage>> {
636    let elements: Vec<_> = node.children_with_tokens().collect();
637    let idx = elements.iter().position(|e| e.kind() == boundary)?;
638    elements[..idx]
639        .iter()
640        .rev()
641        .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
642        .cloned()
643}
644
645/// A resolved namespace access (`pkg::name` / `pkg:::name`), as extracted from a
646/// [`BinaryExpr`].
647#[derive(Debug, Clone, PartialEq, Eq)]
648pub struct NamespaceAccess {
649    pub package: SmolStr,
650    pub package_token: SyntaxToken<RLanguage>,
651    /// `true` for the internal `:::` operator, `false` for `::`.
652    pub internal: bool,
653    pub name: SmolStr,
654    pub name_token: SyntaxToken<RLanguage>,
655}
656
657impl BinaryExpr {
658    /// If this binary expression is a namespace access — `pkg::name`,
659    /// `pkg:::name`, or the call form `pkg::name(args)` — the resolved package
660    /// and accessed name with their tokens. The package and name may be written
661    /// as backtick/quoted strings, which are unquoted.
662    pub fn namespace_access(&self) -> Option<NamespaceAccess> {
663        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
664        let (op_idx, internal) = elements
665            .iter()
666            .enumerate()
667            .find_map(|(i, e)| match e.kind() {
668                SyntaxKind::COLON2 => Some((i, false)),
669                SyntaxKind::COLON3 => Some((i, true)),
670                _ => None,
671            })?;
672        // Package: the last name token before the operator.
673        let package_token = elements[..op_idx].iter().rev().find_map(|e| match e {
674            SyntaxElement::Token(t)
675                if matches!(t.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
676            {
677                Some(t.clone())
678            }
679            _ => None,
680        })?;
681        // Name: the first non-trivia element after the operator — either a bare
682        // name token or the callee of a `pkg::name(args)` call.
683        let rhs = elements[op_idx + 1..]
684            .iter()
685            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)?;
686        let name_token = match rhs {
687            SyntaxElement::Token(t)
688                if matches!(t.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
689            {
690                t.clone()
691            }
692            SyntaxElement::Node(n) if n.kind() == SyntaxKind::CALL_EXPR => {
693                CallExpr::cast(n.clone())?.callee_token()?
694            }
695            _ => return None,
696        };
697        Some(NamespaceAccess {
698            package: token_name(&package_token),
699            package_token,
700            internal,
701            name: token_name(&name_token),
702            name_token,
703        })
704    }
705}
706
707impl ArgList {
708    pub fn args(&self) -> impl Iterator<Item = Arg> {
709        support::children(self.syntax())
710    }
711}
712
713impl IfExpr {
714    pub fn elements(&self) -> Vec<SyntaxElement<RLanguage>> {
715        self.syntax().children_with_tokens().collect()
716    }
717
718    pub fn if_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
719        self.elements()
720            .into_iter()
721            .find_map(|element| match element {
722                SyntaxElement::Token(token) if token.kind() == SyntaxKind::IF_KW => Some(token),
723                _ => None,
724            })
725    }
726
727    pub fn condition_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
728        let elements = self.elements();
729        let lparen_idx = self.lparen_index()?;
730        let rparen_idx = self.rparen_index()?;
731        Some(elements[lparen_idx + 1..rparen_idx].to_vec())
732    }
733
734    pub fn then_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
735        let elements = self.elements();
736        let rparen_idx = self.rparen_index()?;
737        let else_idx = find_token_after_index(&elements, rparen_idx, SyntaxKind::ELSE_KW);
738        let then_end = else_idx.unwrap_or(elements.len());
739        Some(elements[rparen_idx + 1..then_end].to_vec())
740    }
741
742    pub fn else_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
743        self.elements()
744            .into_iter()
745            .find_map(|element| match element {
746                SyntaxElement::Token(token) if token.kind() == SyntaxKind::ELSE_KW => Some(token),
747                _ => None,
748            })
749    }
750
751    pub fn else_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
752        let elements = self.elements();
753        let else_idx = find_token_index(&elements, SyntaxKind::ELSE_KW)?;
754        Some(elements[else_idx + 1..].to_vec())
755    }
756
757    pub fn lparen_index(&self) -> Option<usize> {
758        find_token_index(&self.elements(), SyntaxKind::LPAREN)
759    }
760
761    pub fn rparen_index(&self) -> Option<usize> {
762        let elements = self.elements();
763        let lparen_idx = self.lparen_index()?;
764        find_token_after_index(&elements, lparen_idx, SyntaxKind::RPAREN)
765    }
766}
767
768impl ForExpr {
769    pub fn elements(&self) -> Vec<SyntaxElement<RLanguage>> {
770        self.syntax().children_with_tokens().collect()
771    }
772
773    pub fn for_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
774        self.elements()
775            .into_iter()
776            .find_map(|element| match element {
777                SyntaxElement::Token(token) if token.kind() == SyntaxKind::FOR_KW => Some(token),
778                _ => None,
779            })
780    }
781
782    pub fn clause_bounds(&self) -> Option<(usize, usize)> {
783        let elements = self.elements();
784        let lparen_idx = self.lparen_index()?;
785
786        let mut depth = 0usize;
787        let rparen_idx = elements.iter().enumerate().skip(lparen_idx).find_map(
788            |(idx, element)| match element {
789                SyntaxElement::Token(token) if token.kind() == SyntaxKind::LPAREN => {
790                    depth += 1;
791                    None
792                }
793                SyntaxElement::Token(token) if token.kind() == SyntaxKind::RPAREN => {
794                    depth = depth.saturating_sub(1);
795                    if depth == 0 { Some(idx) } else { None }
796                }
797                _ => None,
798            },
799        )?;
800
801        Some((lparen_idx, rparen_idx))
802    }
803
804    pub fn lparen_index(&self) -> Option<usize> {
805        let elements = self.elements();
806        let for_idx = find_token_index(&elements, SyntaxKind::FOR_KW)?;
807        find_token_after_index(&elements, for_idx, SyntaxKind::LPAREN)
808    }
809
810    pub fn leading_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
811        let elements = self.elements();
812        let for_idx = find_token_index(&elements, SyntaxKind::FOR_KW)?;
813        let (_, rparen_idx) = self.clause_bounds()?;
814        Some(
815            elements[for_idx + 1..rparen_idx]
816                .iter()
817                .filter_map(|element| match element {
818                    SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT => {
819                        Some(token.clone())
820                    }
821                    _ => None,
822                })
823                .collect(),
824        )
825    }
826
827    pub fn clause_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
828        let elements = self.elements();
829        let (lparen_idx, rparen_idx) = self.clause_bounds()?;
830        Some(
831            elements[lparen_idx + 1..rparen_idx]
832                .iter()
833                .filter(|element| {
834                    !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT
835                })
836                .cloned()
837                .collect(),
838        )
839    }
840
841    pub fn post_clause_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
842        let elements = self.elements();
843        let (_, rparen_idx) = self.clause_bounds()?;
844        let mut comments = Vec::new();
845        for element in &elements[rparen_idx + 1..] {
846            if is_trivia(element.kind()) {
847                continue;
848            }
849            if let SyntaxElement::Token(token) = element
850                && token.kind() == SyntaxKind::COMMENT
851            {
852                comments.push(token.clone());
853                continue;
854            }
855            break;
856        }
857        Some(comments)
858    }
859
860    pub fn body_element(&self) -> Option<SyntaxElement<RLanguage>> {
861        let elements = self.elements();
862        let (_, rparen_idx) = self.clause_bounds()?;
863        for element in &elements[rparen_idx + 1..] {
864            if is_trivia(element.kind()) {
865                continue;
866            }
867            if matches!(element, SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT)
868            {
869                continue;
870            }
871            return Some(element.clone());
872        }
873        None
874    }
875
876    pub fn parts(&self) -> Option<ForExprParts> {
877        self.for_keyword()?;
878        self.lparen_index()?;
879        self.clause_bounds()?;
880
881        let clause_elements = self.clause_elements()?;
882        let in_idx = clause_elements.iter().position(
883            |el| matches!(el, SyntaxElement::Token(tok) if tok.kind() == SyntaxKind::IN_KW),
884        )?;
885
886        Some(ForExprParts {
887            leading_comments: self.leading_comments()?,
888            variable_elements: clause_elements[..in_idx].to_vec(),
889            sequence_elements: clause_elements[in_idx + 1..].to_vec(),
890            post_clause_comments: self.post_clause_comments()?,
891            body: self.body_element(),
892        })
893    }
894}
895
896impl WhileExpr {
897    pub fn elements(&self) -> Vec<SyntaxElement<RLanguage>> {
898        self.syntax().children_with_tokens().collect()
899    }
900
901    pub fn while_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
902        self.elements()
903            .into_iter()
904            .find_map(|element| match element {
905                SyntaxElement::Token(token) if token.kind() == SyntaxKind::WHILE_KW => Some(token),
906                _ => None,
907            })
908    }
909
910    pub fn clause_bounds(&self) -> Option<(usize, usize)> {
911        let elements = self.elements();
912        let lparen_idx = self.lparen_index()?;
913
914        let mut depth = 0usize;
915        let rparen_idx = elements.iter().enumerate().skip(lparen_idx).find_map(
916            |(idx, element)| match element {
917                SyntaxElement::Token(token) if token.kind() == SyntaxKind::LPAREN => {
918                    depth += 1;
919                    None
920                }
921                SyntaxElement::Token(token) if token.kind() == SyntaxKind::RPAREN => {
922                    depth = depth.saturating_sub(1);
923                    if depth == 0 { Some(idx) } else { None }
924                }
925                _ => None,
926            },
927        )?;
928
929        Some((lparen_idx, rparen_idx))
930    }
931
932    pub fn lparen_index(&self) -> Option<usize> {
933        let elements = self.elements();
934        let while_idx = find_token_index(&elements, SyntaxKind::WHILE_KW)?;
935        find_token_after_index(&elements, while_idx, SyntaxKind::LPAREN)
936    }
937
938    pub fn leading_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
939        let elements = self.elements();
940        let while_idx = find_token_index(&elements, SyntaxKind::WHILE_KW)?;
941        let (_, rparen_idx) = self.clause_bounds()?;
942        Some(
943            elements[while_idx + 1..rparen_idx]
944                .iter()
945                .filter_map(|element| match element {
946                    SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT => {
947                        Some(token.clone())
948                    }
949                    _ => None,
950                })
951                .collect(),
952        )
953    }
954
955    pub fn condition_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
956        let elements = self.elements();
957        let (lparen_idx, rparen_idx) = self.clause_bounds()?;
958        Some(
959            elements[lparen_idx + 1..rparen_idx]
960                .iter()
961                .filter(|element| {
962                    !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT
963                })
964                .cloned()
965                .collect(),
966        )
967    }
968
969    pub fn post_clause_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
970        let elements = self.elements();
971        let (_, rparen_idx) = self.clause_bounds()?;
972        let mut comments = Vec::new();
973        for element in &elements[rparen_idx + 1..] {
974            if is_trivia(element.kind()) {
975                continue;
976            }
977            if let SyntaxElement::Token(token) = element
978                && token.kind() == SyntaxKind::COMMENT
979            {
980                comments.push(token.clone());
981                continue;
982            }
983            break;
984        }
985        Some(comments)
986    }
987
988    pub fn body_element(&self) -> Option<SyntaxElement<RLanguage>> {
989        let elements = self.elements();
990        let (_, rparen_idx) = self.clause_bounds()?;
991        for element in &elements[rparen_idx + 1..] {
992            if is_trivia(element.kind()) {
993                continue;
994            }
995            if matches!(element, SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT)
996            {
997                continue;
998            }
999            return Some(element.clone());
1000        }
1001        None
1002    }
1003
1004    pub fn parts(&self) -> Option<WhileExprParts> {
1005        self.while_keyword()?;
1006        self.lparen_index()?;
1007        self.clause_bounds()?;
1008
1009        Some(WhileExprParts {
1010            leading_comments: self.leading_comments()?,
1011            condition_elements: self.condition_elements()?,
1012            post_clause_comments: self.post_clause_comments()?,
1013            body: self.body_element(),
1014        })
1015    }
1016}
1017
1018impl BinaryExpr {
1019    /// The left-hand operand — an element, since a leaf operand is a bare token
1020    /// (`x`, `1`, `TRUE`) while a compound operand is a node (`a + b`).
1021    pub fn lhs(&self) -> Option<SyntaxElement<RLanguage>> {
1022        self.parts().map(|(lhs, _, _)| lhs)
1023    }
1024
1025    /// The operator token (`==`, `+`, `%in%`, `::`, …).
1026    pub fn op(&self) -> Option<SyntaxToken<RLanguage>> {
1027        self.parts().map(|(_, op, _)| op)
1028    }
1029
1030    pub fn op_kind(&self) -> Option<SyntaxKind> {
1031        self.op().map(|op| op.kind())
1032    }
1033
1034    /// The right-hand operand element.
1035    pub fn rhs(&self) -> Option<SyntaxElement<RLanguage>> {
1036        self.parts().map(|(_, _, rhs)| rhs)
1037    }
1038
1039    /// Split into `(lhs, operator, rhs)` at the top-level operator token.
1040    pub fn parts(
1041        &self,
1042    ) -> Option<(
1043        SyntaxElement<RLanguage>,
1044        SyntaxToken<RLanguage>,
1045        SyntaxElement<RLanguage>,
1046    )> {
1047        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1048        let op_idx = elements
1049            .iter()
1050            .position(|e| matches!(e, SyntaxElement::Token(t) if is_binary_operator(t.kind())))?;
1051        let op = elements[op_idx].as_token()?.clone();
1052        let lhs = elements[..op_idx]
1053            .iter()
1054            .rev()
1055            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)?
1056            .clone();
1057        let rhs = elements[op_idx + 1..]
1058            .iter()
1059            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)?
1060            .clone();
1061        Some((lhs, op, rhs))
1062    }
1063}
1064
1065impl UnaryExpr {
1066    /// The prefix operator token (`!`, `-`, `+`, `~`, `?`).
1067    pub fn op(&self) -> Option<SyntaxToken<RLanguage>> {
1068        self.syntax().children_with_tokens().find_map(|e| match e {
1069            SyntaxElement::Token(t) if is_unary_operator(t.kind()) => Some(t),
1070            _ => None,
1071        })
1072    }
1073
1074    pub fn op_kind(&self) -> Option<SyntaxKind> {
1075        self.op().map(|op| op.kind())
1076    }
1077
1078    /// The operand element following the operator.
1079    pub fn operand(&self) -> Option<SyntaxElement<RLanguage>> {
1080        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1081        let op_idx = elements
1082            .iter()
1083            .position(|e| matches!(e, SyntaxElement::Token(t) if is_unary_operator(t.kind())))?;
1084        elements[op_idx + 1..]
1085            .iter()
1086            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
1087            .cloned()
1088    }
1089}
1090
1091impl ParenExpr {
1092    pub fn lparen(&self) -> Option<SyntaxToken<RLanguage>> {
1093        first_child_token(self.syntax(), SyntaxKind::LPAREN)
1094    }
1095
1096    pub fn rparen(&self) -> Option<SyntaxToken<RLanguage>> {
1097        first_child_token(self.syntax(), SyntaxKind::RPAREN)
1098    }
1099
1100    /// The parenthesized inner expression element.
1101    pub fn inner(&self) -> Option<SyntaxElement<RLanguage>> {
1102        self.syntax().children_with_tokens().find(|e| {
1103            !is_trivia(e.kind())
1104                && !matches!(
1105                    e.kind(),
1106                    SyntaxKind::COMMENT | SyntaxKind::LPAREN | SyntaxKind::RPAREN
1107                )
1108        })
1109    }
1110}
1111
1112impl BlockExpr {
1113    pub fn lbrace(&self) -> Option<SyntaxToken<RLanguage>> {
1114        first_child_token(self.syntax(), SyntaxKind::LBRACE)
1115    }
1116
1117    pub fn rbrace(&self) -> Option<SyntaxToken<RLanguage>> {
1118        first_child_token(self.syntax(), SyntaxKind::RBRACE)
1119    }
1120
1121    /// The statement elements between the braces, in source order — the
1122    /// non-trivia children that are not the braces, comments, or `;` separators.
1123    /// Each is a bare token (`x`) or an expression node.
1124    pub fn statements(&self) -> impl Iterator<Item = SyntaxElement<RLanguage>> + '_ {
1125        self.syntax().children_with_tokens().filter(|e| {
1126            !is_trivia(e.kind())
1127                && !matches!(
1128                    e.kind(),
1129                    SyntaxKind::COMMENT
1130                        | SyntaxKind::LBRACE
1131                        | SyntaxKind::RBRACE
1132                        | SyntaxKind::SEMICOLON
1133                )
1134        })
1135    }
1136}
1137
1138impl Arg {
1139    /// The `=` token of a named argument (`name = value`), if any.
1140    pub fn eq_token(&self) -> Option<SyntaxToken<RLanguage>> {
1141        first_child_token(self.syntax(), SyntaxKind::ASSIGN_EQ)
1142    }
1143
1144    /// The name token of a named argument — the `IDENT`/`STRING` immediately
1145    /// before `=`. `None` for a positional argument.
1146    pub fn name_token(&self) -> Option<SyntaxToken<RLanguage>> {
1147        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1148        let eq = elements
1149            .iter()
1150            .position(|e| e.kind() == SyntaxKind::ASSIGN_EQ)?;
1151        elements[..eq].iter().rev().find_map(|e| match e {
1152            SyntaxElement::Token(t)
1153                if matches!(t.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
1154            {
1155                Some(t.clone())
1156            }
1157            _ => None,
1158        })
1159    }
1160
1161    /// The argument name, unquoted. `None` for a positional argument.
1162    pub fn name(&self) -> Option<SmolStr> {
1163        self.name_token().as_ref().map(token_name)
1164    }
1165
1166    pub fn is_named(&self) -> bool {
1167        self.name_token().is_some()
1168    }
1169
1170    /// The value element: for `name = value`, the first non-trivia element after
1171    /// `=`; for a positional argument, its first non-trivia element.
1172    pub fn value(&self) -> Option<SyntaxElement<RLanguage>> {
1173        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1174        let start = match elements
1175            .iter()
1176            .position(|e| e.kind() == SyntaxKind::ASSIGN_EQ)
1177        {
1178            Some(eq) => eq + 1,
1179            None => 0,
1180        };
1181        elements[start..]
1182            .iter()
1183            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
1184            .cloned()
1185    }
1186}
1187
1188impl RepeatExpr {
1189    pub fn repeat_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
1190        first_child_token(self.syntax(), SyntaxKind::REPEAT_KW)
1191    }
1192
1193    /// The loop body element following the `repeat` keyword.
1194    pub fn body(&self) -> Option<SyntaxElement<RLanguage>> {
1195        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1196        let kw_idx = elements
1197            .iter()
1198            .position(|e| e.kind() == SyntaxKind::REPEAT_KW)?;
1199        elements[kw_idx + 1..]
1200            .iter()
1201            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
1202            .cloned()
1203    }
1204}
1205
1206fn find_token_index(elements: &[SyntaxElement<RLanguage>], kind: SyntaxKind) -> Option<usize> {
1207    elements
1208        .iter()
1209        .position(|element| matches!(element, SyntaxElement::Token(token) if token.kind() == kind))
1210}
1211
1212fn find_token_after_index(
1213    elements: &[SyntaxElement<RLanguage>],
1214    start_idx: usize,
1215    kind: SyntaxKind,
1216) -> Option<usize> {
1217    elements
1218        .iter()
1219        .enumerate()
1220        .skip(start_idx + 1)
1221        .find_map(|(idx, element)| match element {
1222            SyntaxElement::Token(token) if token.kind() == kind => Some(idx),
1223            _ => None,
1224        })
1225}