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        self.operand(false)
241    }
242
243    /// The element on the *value* side of the operator.
244    pub fn value_element(&self) -> Option<SyntaxElement<RLanguage>> {
245        self.operand(true)
246    }
247
248    fn operand(&self, value_side: bool) -> Option<SyntaxElement<RLanguage>> {
249        let is_operand =
250            |e: &SyntaxElement<RLanguage>| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT;
251        let mut elements = self.syntax().children_with_tokens();
252        let mut before = None;
253        while let Some(element) = elements.next() {
254            if matches!(&element, SyntaxElement::Token(t) if is_assignment_op(t.kind())) {
255                return if is_right_assign(element.kind()) == value_side {
256                    before
257                } else {
258                    elements.find(is_operand)
259                };
260            }
261            if before.is_none() && is_operand(&element) {
262                before = Some(element);
263            }
264        }
265        None
266    }
267
268    /// If the target is a simple `IDENT` (or backtick-quoted identifier), the
269    /// bound name as a `SmolStr`. Returns `None` for complex LHS like
270    /// `dim(x) <- ...`, `x[1] <- ...`, `x$y <- ...`.
271    pub fn target_name(&self) -> Option<SmolStr> {
272        match self.target_element()? {
273            SyntaxElement::Token(token) if token.kind() == SyntaxKind::IDENT => {
274                Some(SmolStr::new(token.text()))
275            }
276            SyntaxElement::Token(token) if token.kind() == SyntaxKind::STRING => {
277                StringLit::cast(token).and_then(|s| s.unquote().map(SmolStr::new))
278            }
279            _ => None,
280        }
281    }
282
283    /// The target name token, useful when the binding site's range is needed.
284    pub fn target_name_token(&self) -> Option<SyntaxToken<RLanguage>> {
285        match self.target_element()? {
286            SyntaxElement::Token(token)
287                if token.kind() == SyntaxKind::IDENT || token.kind() == SyntaxKind::STRING =>
288            {
289                Some(token)
290            }
291            _ => None,
292        }
293    }
294}
295
296impl FunctionExpr {
297    pub fn lparen_index(&self) -> Option<usize> {
298        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
299        elements.iter().position(|e| e.kind() == SyntaxKind::LPAREN)
300    }
301
302    pub fn rparen_index(&self) -> Option<usize> {
303        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
304        let lparen_idx = self.lparen_index()?;
305        let mut depth = 0usize;
306        elements
307            .iter()
308            .enumerate()
309            .skip(lparen_idx)
310            .find_map(|(i, el)| match el.kind() {
311                SyntaxKind::LPAREN => {
312                    depth += 1;
313                    None
314                }
315                SyntaxKind::RPAREN => {
316                    depth = depth.saturating_sub(1);
317                    if depth == 0 { Some(i) } else { None }
318                }
319                _ => None,
320            })
321    }
322
323    /// The function's formal arguments in source order, including their
324    /// optional default-value syntax and exact source ranges.
325    pub fn formals(&self) -> Vec<Formal> {
326        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
327        let Some(lparen_idx) = self.lparen_index() else {
328            return Vec::new();
329        };
330        let Some(rparen_idx) = self.rparen_index() else {
331            return Vec::new();
332        };
333
334        split_function_formals(&elements[lparen_idx + 1..rparen_idx])
335            .into_iter()
336            .filter_map(formal_from_elements)
337            .collect()
338    }
339
340    /// The function's parameters as a names-only compatibility projection.
341    /// Use [`Self::formals`] when default syntax or full formal ranges are
342    /// needed.
343    pub fn params(&self) -> Vec<Param> {
344        self.formals()
345            .into_iter()
346            .map(|formal| Param {
347                name: formal.name,
348                name_token: formal.name_token,
349            })
350            .collect()
351    }
352
353    /// The function body — the expression that follows the `)`. May be any
354    /// expression node (a `BLOCK_EXPR` for `function(x) { ... }`, or any other
355    /// expression for `function(x) x + 1`). Returns `None` if the body was
356    /// missing or replaced by an error-recovery placeholder.
357    pub fn body(&self) -> Option<SyntaxElement<RLanguage>> {
358        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
359        let rparen_idx = self.rparen_index()?;
360        elements[rparen_idx + 1..]
361            .iter()
362            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
363            .cloned()
364    }
365}
366
367/// One function formal, retaining its name, complete source range, and
368/// optional default-value syntax.
369#[derive(Debug, Clone, PartialEq, Eq, Hash)]
370pub struct Formal {
371    name: SmolStr,
372    name_token: SyntaxToken<RLanguage>,
373    range: TextRange,
374    default: Option<SyntaxElement<RLanguage>>,
375}
376
377impl Formal {
378    /// The formal's name as written, including backticks for a non-syntactic
379    /// name.
380    pub fn name(&self) -> SmolStr {
381        self.name.clone()
382    }
383
384    /// The name token and its exact source range.
385    pub fn name_token(&self) -> SyntaxToken<RLanguage> {
386        self.name_token.clone()
387    }
388
389    /// The whole formal range, excluding its comma and surrounding trivia.
390    pub fn text_range(&self) -> TextRange {
391        self.range
392    }
393
394    /// The default-value syntax, whether a token atom or compound node.
395    pub fn default(&self) -> Option<SyntaxElement<RLanguage>> {
396        self.default.clone()
397    }
398
399    /// The exact source range of [`Self::default`].
400    pub fn default_range(&self) -> Option<TextRange> {
401        self.default.as_ref().map(SyntaxElement::text_range)
402    }
403}
404
405fn split_function_formals(
406    elements: &[SyntaxElement<RLanguage>],
407) -> Vec<&[SyntaxElement<RLanguage>]> {
408    let mut formals = Vec::new();
409    let mut start = 0usize;
410    let mut depth = 0usize;
411
412    for (index, element) in elements.iter().enumerate() {
413        match element.kind() {
414            SyntaxKind::LPAREN | SyntaxKind::LBRACK | SyntaxKind::LBRACK2 | SyntaxKind::LBRACE => {
415                depth += 1
416            }
417            SyntaxKind::RPAREN | SyntaxKind::RBRACK | SyntaxKind::RBRACK2 | SyntaxKind::RBRACE => {
418                depth = depth.saturating_sub(1)
419            }
420            SyntaxKind::COMMA if depth == 0 => {
421                formals.push(&elements[start..index]);
422                start = index + 1;
423            }
424            _ => {}
425        }
426    }
427    formals.push(&elements[start..]);
428    formals
429}
430
431fn formal_from_elements(elements: &[SyntaxElement<RLanguage>]) -> Option<Formal> {
432    let name_index = elements
433        .iter()
434        .position(|element| !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT)?;
435    let SyntaxElement::Token(name_token) = &elements[name_index] else {
436        return None;
437    };
438    if name_token.kind() != SyntaxKind::IDENT {
439        return None;
440    }
441
442    let eq_index = elements[name_index + 1..]
443        .iter()
444        .position(|element| element.kind() == SyntaxKind::ASSIGN_EQ)
445        .map(|index| name_index + 1 + index);
446    let default = eq_index.and_then(|index| {
447        elements[index + 1..]
448            .iter()
449            .find(|element| !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT)
450            .filter(|element| crate::ast::Expr::cast((*element).clone()).is_some())
451            .cloned()
452    });
453    let end = default
454        .as_ref()
455        .map(SyntaxElement::text_range)
456        .map(TextRange::end)
457        .or_else(|| eq_index.map(|index| elements[index].text_range().end()))
458        .unwrap_or_else(|| name_token.text_range().end());
459
460    Some(Formal {
461        name: SmolStr::new(name_token.text()),
462        name_token: name_token.clone(),
463        range: TextRange::new(name_token.text_range().start(), end),
464        default,
465    })
466}
467
468#[derive(Debug, Clone)]
469pub struct Param {
470    pub name: SmolStr,
471    pub name_token: SyntaxToken<RLanguage>,
472}
473
474fn is_assignment_op(kind: SyntaxKind) -> bool {
475    matches!(
476        kind,
477        SyntaxKind::ASSIGN_LEFT
478            | SyntaxKind::ASSIGN_RIGHT
479            | SyntaxKind::SUPER_ASSIGN
480            | SyntaxKind::SUPER_ASSIGN_RIGHT
481            | SyntaxKind::ASSIGN_EQ
482            | SyntaxKind::WALRUS
483    )
484}
485
486fn is_right_assign(kind: SyntaxKind) -> bool {
487    matches!(
488        kind,
489        SyntaxKind::ASSIGN_RIGHT | SyntaxKind::SUPER_ASSIGN_RIGHT
490    )
491}
492
493/// The RHS member `IDENT` of a `pkg::name` / `pkg:::name` `BINARY_EXPR`, if that
494/// is its shape. `None` for any other binary expression (a different operator,
495/// or a non-`IDENT` member).
496fn namespace_member_token(node: &SyntaxNode) -> Option<SyntaxToken<RLanguage>> {
497    let mut seen_ns_op = false;
498    for element in node.children_with_tokens() {
499        match element {
500            SyntaxElement::Token(t)
501                if matches!(t.kind(), SyntaxKind::COLON2 | SyntaxKind::COLON3) =>
502            {
503                seen_ns_op = true;
504            }
505            SyntaxElement::Token(t) if seen_ns_op && t.kind() == SyntaxKind::IDENT => {
506                return Some(t);
507            }
508            _ => {}
509        }
510    }
511    None
512}
513
514impl CallExpr {
515    pub fn arg_list(&self) -> Option<ArgList> {
516        support::child(self.syntax())
517    }
518
519    /// The callee name token — the `IDENT`/`STRING` immediately preceding the
520    /// argument list, when the function being called is a simple name (`foo(…)`,
521    /// `` `+`(…) ``) or a namespace-qualified name (`pkg::fn(…)`, where the token
522    /// is the `fn` member). Returns `None` for a genuinely computed callee
523    /// (`(g())(…)`, `x$f(…)`).
524    pub fn callee_token(&self) -> Option<SyntaxToken<RLanguage>> {
525        for element in self.syntax().children_with_tokens() {
526            match element {
527                SyntaxElement::Token(token)
528                    if is_trivia(token.kind()) || token.kind() == SyntaxKind::COMMENT =>
529                {
530                    continue;
531                }
532                SyntaxElement::Token(token)
533                    if matches!(token.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
534                {
535                    return Some(token);
536                }
537                // `pkg::fn(…)` / `pkg:::fn(…)` parses with the whole call wrapping
538                // a `pkg::fn` `BINARY_EXPR` callee. The callee *name* is that
539                // binary's RHS member, so the qualified form resolves like the
540                // bare `fn(…)` for every consumer (linter matchers, signature
541                // help, call hierarchy). Other binary callees (`obj$m(…)`) stay
542                // computed.
543                SyntaxElement::Node(node) if node.kind() == SyntaxKind::BINARY_EXPR => {
544                    return namespace_member_token(&node);
545                }
546                _ => return None,
547            }
548        }
549        None
550    }
551
552    /// The callee name (`foo` in `foo(…)`, `` + `` in `` `+`(…) ``), unquoted,
553    /// when the callee is a simple name. `None` for a computed callee.
554    pub fn callee_name(&self) -> Option<SmolStr> {
555        self.callee_token().as_ref().map(token_name)
556    }
557
558    /// The callee element — everything before the argument list's `(`. Generalizes
559    /// [`CallExpr::callee_token`] to the computed-callee case (`(g())(…)`,
560    /// `x$f(…)`), where it returns the callee *node* instead of a name token.
561    pub fn base(&self) -> Option<SyntaxElement<RLanguage>> {
562        last_element_before(self.syntax(), SyntaxKind::LPAREN)
563    }
564}
565
566impl SubsetExpr {
567    /// The base being indexed — everything before the `[`.
568    pub fn base(&self) -> Option<SyntaxElement<RLanguage>> {
569        last_element_before(self.syntax(), SyntaxKind::LBRACK)
570    }
571
572    pub fn open_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
573        first_child_token(self.syntax(), SyntaxKind::LBRACK)
574    }
575
576    pub fn close_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
577        first_child_token(self.syntax(), SyntaxKind::RBRACK)
578    }
579
580    pub fn arg_list(&self) -> Option<ArgList> {
581        support::child(self.syntax())
582    }
583
584    pub fn args(&self) -> impl Iterator<Item = Arg> {
585        self.arg_list()
586            .map(|list| list.args().collect::<Vec<_>>())
587            .unwrap_or_default()
588            .into_iter()
589    }
590}
591
592impl Subset2Expr {
593    /// The base being indexed — everything before the `[[`.
594    pub fn base(&self) -> Option<SyntaxElement<RLanguage>> {
595        last_element_before(self.syntax(), SyntaxKind::LBRACK2)
596    }
597
598    pub fn open_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
599        first_child_token(self.syntax(), SyntaxKind::LBRACK2)
600    }
601
602    pub fn close_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
603        first_child_token(self.syntax(), SyntaxKind::RBRACK2)
604    }
605
606    pub fn arg_list(&self) -> Option<ArgList> {
607        support::child(self.syntax())
608    }
609
610    pub fn args(&self) -> impl Iterator<Item = Arg> {
611        self.arg_list()
612            .map(|list| list.args().collect::<Vec<_>>())
613            .unwrap_or_default()
614            .into_iter()
615    }
616}
617
618/// The last non-trivia, non-comment element before the first direct-child token
619/// of `boundary` kind (e.g. the callee before `(`, the base before `[`).
620fn last_element_before(
621    node: &SyntaxNode,
622    boundary: SyntaxKind,
623) -> Option<SyntaxElement<RLanguage>> {
624    let elements: Vec<_> = node.children_with_tokens().collect();
625    let idx = elements.iter().position(|e| e.kind() == boundary)?;
626    elements[..idx]
627        .iter()
628        .rev()
629        .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
630        .cloned()
631}
632
633/// A resolved namespace access (`pkg::name` / `pkg:::name`), as extracted from a
634/// [`BinaryExpr`].
635#[derive(Debug, Clone, PartialEq, Eq)]
636pub struct NamespaceAccess {
637    pub package: SmolStr,
638    pub package_token: SyntaxToken<RLanguage>,
639    /// `true` for the internal `:::` operator, `false` for `::`.
640    pub internal: bool,
641    pub name: SmolStr,
642    pub name_token: SyntaxToken<RLanguage>,
643}
644
645impl BinaryExpr {
646    /// If this binary expression is a namespace access — `pkg::name`,
647    /// `pkg:::name`, or the call form `pkg::name(args)` — the resolved package
648    /// and accessed name with their tokens. The package and name may be written
649    /// as backtick/quoted strings, which are unquoted.
650    pub fn namespace_access(&self) -> Option<NamespaceAccess> {
651        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
652        let (op_idx, internal) = elements
653            .iter()
654            .enumerate()
655            .find_map(|(i, e)| match e.kind() {
656                SyntaxKind::COLON2 => Some((i, false)),
657                SyntaxKind::COLON3 => Some((i, true)),
658                _ => None,
659            })?;
660        // Package: the last name token before the operator.
661        let package_token = elements[..op_idx].iter().rev().find_map(|e| match e {
662            SyntaxElement::Token(t)
663                if matches!(t.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
664            {
665                Some(t.clone())
666            }
667            _ => None,
668        })?;
669        // Name: the first non-trivia element after the operator — either a bare
670        // name token or the callee of a `pkg::name(args)` call.
671        let rhs = elements[op_idx + 1..]
672            .iter()
673            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)?;
674        let name_token = match rhs {
675            SyntaxElement::Token(t)
676                if matches!(t.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
677            {
678                t.clone()
679            }
680            SyntaxElement::Node(n) if n.kind() == SyntaxKind::CALL_EXPR => {
681                CallExpr::cast(n.clone())?.callee_token()?
682            }
683            _ => return None,
684        };
685        Some(NamespaceAccess {
686            package: token_name(&package_token),
687            package_token,
688            internal,
689            name: token_name(&name_token),
690            name_token,
691        })
692    }
693}
694
695impl ArgList {
696    pub fn args(&self) -> impl Iterator<Item = Arg> {
697        support::children(self.syntax())
698    }
699}
700
701impl IfExpr {
702    pub fn elements(&self) -> Vec<SyntaxElement<RLanguage>> {
703        self.syntax().children_with_tokens().collect()
704    }
705
706    pub fn if_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
707        self.elements()
708            .into_iter()
709            .find_map(|element| match element {
710                SyntaxElement::Token(token) if token.kind() == SyntaxKind::IF_KW => Some(token),
711                _ => None,
712            })
713    }
714
715    pub fn condition_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
716        let elements = self.elements();
717        let lparen_idx = self.lparen_index()?;
718        let rparen_idx = self.rparen_index()?;
719        Some(elements[lparen_idx + 1..rparen_idx].to_vec())
720    }
721
722    pub fn then_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
723        let elements = self.elements();
724        let rparen_idx = self.rparen_index()?;
725        let else_idx = find_token_after_index(&elements, rparen_idx, SyntaxKind::ELSE_KW);
726        let then_end = else_idx.unwrap_or(elements.len());
727        Some(elements[rparen_idx + 1..then_end].to_vec())
728    }
729
730    pub fn else_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
731        self.elements()
732            .into_iter()
733            .find_map(|element| match element {
734                SyntaxElement::Token(token) if token.kind() == SyntaxKind::ELSE_KW => Some(token),
735                _ => None,
736            })
737    }
738
739    pub fn else_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
740        let elements = self.elements();
741        let else_idx = find_token_index(&elements, SyntaxKind::ELSE_KW)?;
742        Some(elements[else_idx + 1..].to_vec())
743    }
744
745    pub fn lparen_index(&self) -> Option<usize> {
746        find_token_index(&self.elements(), SyntaxKind::LPAREN)
747    }
748
749    pub fn rparen_index(&self) -> Option<usize> {
750        let elements = self.elements();
751        let lparen_idx = self.lparen_index()?;
752        find_token_after_index(&elements, lparen_idx, SyntaxKind::RPAREN)
753    }
754}
755
756impl ForExpr {
757    pub fn elements(&self) -> Vec<SyntaxElement<RLanguage>> {
758        self.syntax().children_with_tokens().collect()
759    }
760
761    pub fn for_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
762        self.elements()
763            .into_iter()
764            .find_map(|element| match element {
765                SyntaxElement::Token(token) if token.kind() == SyntaxKind::FOR_KW => Some(token),
766                _ => None,
767            })
768    }
769
770    pub fn clause_bounds(&self) -> Option<(usize, usize)> {
771        let elements = self.elements();
772        let lparen_idx = self.lparen_index()?;
773
774        let mut depth = 0usize;
775        let rparen_idx = elements.iter().enumerate().skip(lparen_idx).find_map(
776            |(idx, element)| match element {
777                SyntaxElement::Token(token) if token.kind() == SyntaxKind::LPAREN => {
778                    depth += 1;
779                    None
780                }
781                SyntaxElement::Token(token) if token.kind() == SyntaxKind::RPAREN => {
782                    depth = depth.saturating_sub(1);
783                    if depth == 0 { Some(idx) } else { None }
784                }
785                _ => None,
786            },
787        )?;
788
789        Some((lparen_idx, rparen_idx))
790    }
791
792    pub fn lparen_index(&self) -> Option<usize> {
793        let elements = self.elements();
794        let for_idx = find_token_index(&elements, SyntaxKind::FOR_KW)?;
795        find_token_after_index(&elements, for_idx, SyntaxKind::LPAREN)
796    }
797
798    pub fn leading_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
799        let elements = self.elements();
800        let for_idx = find_token_index(&elements, SyntaxKind::FOR_KW)?;
801        let (_, rparen_idx) = self.clause_bounds()?;
802        Some(
803            elements[for_idx + 1..rparen_idx]
804                .iter()
805                .filter_map(|element| match element {
806                    SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT => {
807                        Some(token.clone())
808                    }
809                    _ => None,
810                })
811                .collect(),
812        )
813    }
814
815    pub fn clause_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
816        let elements = self.elements();
817        let (lparen_idx, rparen_idx) = self.clause_bounds()?;
818        Some(
819            elements[lparen_idx + 1..rparen_idx]
820                .iter()
821                .filter(|element| {
822                    !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT
823                })
824                .cloned()
825                .collect(),
826        )
827    }
828
829    pub fn post_clause_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
830        let elements = self.elements();
831        let (_, rparen_idx) = self.clause_bounds()?;
832        let mut comments = Vec::new();
833        for element in &elements[rparen_idx + 1..] {
834            if is_trivia(element.kind()) {
835                continue;
836            }
837            if let SyntaxElement::Token(token) = element
838                && token.kind() == SyntaxKind::COMMENT
839            {
840                comments.push(token.clone());
841                continue;
842            }
843            break;
844        }
845        Some(comments)
846    }
847
848    pub fn body_element(&self) -> Option<SyntaxElement<RLanguage>> {
849        let elements = self.elements();
850        let (_, rparen_idx) = self.clause_bounds()?;
851        for element in &elements[rparen_idx + 1..] {
852            if is_trivia(element.kind()) {
853                continue;
854            }
855            if matches!(element, SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT)
856            {
857                continue;
858            }
859            return Some(element.clone());
860        }
861        None
862    }
863
864    pub fn parts(&self) -> Option<ForExprParts> {
865        self.for_keyword()?;
866        self.lparen_index()?;
867        self.clause_bounds()?;
868
869        let clause_elements = self.clause_elements()?;
870        let in_idx = clause_elements.iter().position(
871            |el| matches!(el, SyntaxElement::Token(tok) if tok.kind() == SyntaxKind::IN_KW),
872        )?;
873
874        Some(ForExprParts {
875            leading_comments: self.leading_comments()?,
876            variable_elements: clause_elements[..in_idx].to_vec(),
877            sequence_elements: clause_elements[in_idx + 1..].to_vec(),
878            post_clause_comments: self.post_clause_comments()?,
879            body: self.body_element(),
880        })
881    }
882}
883
884impl WhileExpr {
885    pub fn elements(&self) -> Vec<SyntaxElement<RLanguage>> {
886        self.syntax().children_with_tokens().collect()
887    }
888
889    pub fn while_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
890        self.elements()
891            .into_iter()
892            .find_map(|element| match element {
893                SyntaxElement::Token(token) if token.kind() == SyntaxKind::WHILE_KW => Some(token),
894                _ => None,
895            })
896    }
897
898    pub fn clause_bounds(&self) -> Option<(usize, usize)> {
899        let elements = self.elements();
900        let lparen_idx = self.lparen_index()?;
901
902        let mut depth = 0usize;
903        let rparen_idx = elements.iter().enumerate().skip(lparen_idx).find_map(
904            |(idx, element)| match element {
905                SyntaxElement::Token(token) if token.kind() == SyntaxKind::LPAREN => {
906                    depth += 1;
907                    None
908                }
909                SyntaxElement::Token(token) if token.kind() == SyntaxKind::RPAREN => {
910                    depth = depth.saturating_sub(1);
911                    if depth == 0 { Some(idx) } else { None }
912                }
913                _ => None,
914            },
915        )?;
916
917        Some((lparen_idx, rparen_idx))
918    }
919
920    pub fn lparen_index(&self) -> Option<usize> {
921        let elements = self.elements();
922        let while_idx = find_token_index(&elements, SyntaxKind::WHILE_KW)?;
923        find_token_after_index(&elements, while_idx, SyntaxKind::LPAREN)
924    }
925
926    pub fn leading_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
927        let elements = self.elements();
928        let while_idx = find_token_index(&elements, SyntaxKind::WHILE_KW)?;
929        let (_, rparen_idx) = self.clause_bounds()?;
930        Some(
931            elements[while_idx + 1..rparen_idx]
932                .iter()
933                .filter_map(|element| match element {
934                    SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT => {
935                        Some(token.clone())
936                    }
937                    _ => None,
938                })
939                .collect(),
940        )
941    }
942
943    pub fn condition_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
944        let elements = self.elements();
945        let (lparen_idx, rparen_idx) = self.clause_bounds()?;
946        Some(
947            elements[lparen_idx + 1..rparen_idx]
948                .iter()
949                .filter(|element| {
950                    !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT
951                })
952                .cloned()
953                .collect(),
954        )
955    }
956
957    pub fn post_clause_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
958        let elements = self.elements();
959        let (_, rparen_idx) = self.clause_bounds()?;
960        let mut comments = Vec::new();
961        for element in &elements[rparen_idx + 1..] {
962            if is_trivia(element.kind()) {
963                continue;
964            }
965            if let SyntaxElement::Token(token) = element
966                && token.kind() == SyntaxKind::COMMENT
967            {
968                comments.push(token.clone());
969                continue;
970            }
971            break;
972        }
973        Some(comments)
974    }
975
976    pub fn body_element(&self) -> Option<SyntaxElement<RLanguage>> {
977        let elements = self.elements();
978        let (_, rparen_idx) = self.clause_bounds()?;
979        for element in &elements[rparen_idx + 1..] {
980            if is_trivia(element.kind()) {
981                continue;
982            }
983            if matches!(element, SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT)
984            {
985                continue;
986            }
987            return Some(element.clone());
988        }
989        None
990    }
991
992    pub fn parts(&self) -> Option<WhileExprParts> {
993        self.while_keyword()?;
994        self.lparen_index()?;
995        self.clause_bounds()?;
996
997        Some(WhileExprParts {
998            leading_comments: self.leading_comments()?,
999            condition_elements: self.condition_elements()?,
1000            post_clause_comments: self.post_clause_comments()?,
1001            body: self.body_element(),
1002        })
1003    }
1004}
1005
1006impl BinaryExpr {
1007    /// The left-hand operand — an element, since a leaf operand is a bare token
1008    /// (`x`, `1`, `TRUE`) while a compound operand is a node (`a + b`).
1009    pub fn lhs(&self) -> Option<SyntaxElement<RLanguage>> {
1010        self.parts().map(|(lhs, _, _)| lhs)
1011    }
1012
1013    /// The operator token (`==`, `+`, `%in%`, `::`, …).
1014    pub fn op(&self) -> Option<SyntaxToken<RLanguage>> {
1015        self.parts().map(|(_, op, _)| op)
1016    }
1017
1018    pub fn op_kind(&self) -> Option<SyntaxKind> {
1019        self.op().map(|op| op.kind())
1020    }
1021
1022    /// The right-hand operand element.
1023    pub fn rhs(&self) -> Option<SyntaxElement<RLanguage>> {
1024        self.parts().map(|(_, _, rhs)| rhs)
1025    }
1026
1027    /// Split into `(lhs, operator, rhs)` at the top-level operator token.
1028    pub fn parts(
1029        &self,
1030    ) -> Option<(
1031        SyntaxElement<RLanguage>,
1032        SyntaxToken<RLanguage>,
1033        SyntaxElement<RLanguage>,
1034    )> {
1035        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1036        let op_idx = elements
1037            .iter()
1038            .position(|e| matches!(e, SyntaxElement::Token(t) if is_binary_operator(t.kind())))?;
1039        let op = elements[op_idx].as_token()?.clone();
1040        let lhs = elements[..op_idx]
1041            .iter()
1042            .rev()
1043            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)?
1044            .clone();
1045        let rhs = elements[op_idx + 1..]
1046            .iter()
1047            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)?
1048            .clone();
1049        Some((lhs, op, rhs))
1050    }
1051}
1052
1053impl UnaryExpr {
1054    /// The prefix operator token (`!`, `-`, `+`, `~`, `?`).
1055    pub fn op(&self) -> Option<SyntaxToken<RLanguage>> {
1056        self.syntax().children_with_tokens().find_map(|e| match e {
1057            SyntaxElement::Token(t) if is_unary_operator(t.kind()) => Some(t),
1058            _ => None,
1059        })
1060    }
1061
1062    pub fn op_kind(&self) -> Option<SyntaxKind> {
1063        self.op().map(|op| op.kind())
1064    }
1065
1066    /// The operand element following the operator.
1067    pub fn operand(&self) -> Option<SyntaxElement<RLanguage>> {
1068        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1069        let op_idx = elements
1070            .iter()
1071            .position(|e| matches!(e, SyntaxElement::Token(t) if is_unary_operator(t.kind())))?;
1072        elements[op_idx + 1..]
1073            .iter()
1074            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
1075            .cloned()
1076    }
1077}
1078
1079impl ParenExpr {
1080    pub fn lparen(&self) -> Option<SyntaxToken<RLanguage>> {
1081        first_child_token(self.syntax(), SyntaxKind::LPAREN)
1082    }
1083
1084    pub fn rparen(&self) -> Option<SyntaxToken<RLanguage>> {
1085        first_child_token(self.syntax(), SyntaxKind::RPAREN)
1086    }
1087
1088    /// The parenthesized inner expression element.
1089    pub fn inner(&self) -> Option<SyntaxElement<RLanguage>> {
1090        self.syntax().children_with_tokens().find(|e| {
1091            !is_trivia(e.kind())
1092                && !matches!(
1093                    e.kind(),
1094                    SyntaxKind::COMMENT | SyntaxKind::LPAREN | SyntaxKind::RPAREN
1095                )
1096        })
1097    }
1098}
1099
1100impl BlockExpr {
1101    pub fn lbrace(&self) -> Option<SyntaxToken<RLanguage>> {
1102        first_child_token(self.syntax(), SyntaxKind::LBRACE)
1103    }
1104
1105    pub fn rbrace(&self) -> Option<SyntaxToken<RLanguage>> {
1106        first_child_token(self.syntax(), SyntaxKind::RBRACE)
1107    }
1108
1109    /// The statement elements between the braces, in source order — the
1110    /// non-trivia children that are not the braces, comments, or `;` separators.
1111    /// Each is a bare token (`x`) or an expression node.
1112    pub fn statements(&self) -> impl Iterator<Item = SyntaxElement<RLanguage>> + '_ {
1113        self.syntax().children_with_tokens().filter(|e| {
1114            !is_trivia(e.kind())
1115                && !matches!(
1116                    e.kind(),
1117                    SyntaxKind::COMMENT
1118                        | SyntaxKind::LBRACE
1119                        | SyntaxKind::RBRACE
1120                        | SyntaxKind::SEMICOLON
1121                )
1122        })
1123    }
1124}
1125
1126impl Arg {
1127    /// The `=` token of a named argument (`name = value`), if any.
1128    pub fn eq_token(&self) -> Option<SyntaxToken<RLanguage>> {
1129        first_child_token(self.syntax(), SyntaxKind::ASSIGN_EQ)
1130    }
1131
1132    /// The name token of a named argument — the `IDENT`/`STRING` immediately
1133    /// before `=`. `None` for a positional argument.
1134    pub fn name_token(&self) -> Option<SyntaxToken<RLanguage>> {
1135        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1136        let eq = elements
1137            .iter()
1138            .position(|e| e.kind() == SyntaxKind::ASSIGN_EQ)?;
1139        elements[..eq].iter().rev().find_map(|e| match e {
1140            SyntaxElement::Token(t)
1141                if matches!(t.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
1142            {
1143                Some(t.clone())
1144            }
1145            _ => None,
1146        })
1147    }
1148
1149    /// The argument name, unquoted. `None` for a positional argument.
1150    pub fn name(&self) -> Option<SmolStr> {
1151        self.name_token().as_ref().map(token_name)
1152    }
1153
1154    pub fn is_named(&self) -> bool {
1155        self.name_token().is_some()
1156    }
1157
1158    /// The value element: for `name = value`, the first non-trivia element after
1159    /// `=`; for a positional argument, its first non-trivia element.
1160    pub fn value(&self) -> Option<SyntaxElement<RLanguage>> {
1161        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1162        let start = match elements
1163            .iter()
1164            .position(|e| e.kind() == SyntaxKind::ASSIGN_EQ)
1165        {
1166            Some(eq) => eq + 1,
1167            None => 0,
1168        };
1169        elements[start..]
1170            .iter()
1171            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
1172            .cloned()
1173    }
1174}
1175
1176impl RepeatExpr {
1177    pub fn repeat_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
1178        first_child_token(self.syntax(), SyntaxKind::REPEAT_KW)
1179    }
1180
1181    /// The loop body element following the `repeat` keyword.
1182    pub fn body(&self) -> Option<SyntaxElement<RLanguage>> {
1183        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1184        let kw_idx = elements
1185            .iter()
1186            .position(|e| e.kind() == SyntaxKind::REPEAT_KW)?;
1187        elements[kw_idx + 1..]
1188            .iter()
1189            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
1190            .cloned()
1191    }
1192}
1193
1194fn find_token_index(elements: &[SyntaxElement<RLanguage>], kind: SyntaxKind) -> Option<usize> {
1195    elements
1196        .iter()
1197        .position(|element| matches!(element, SyntaxElement::Token(token) if token.kind() == kind))
1198}
1199
1200fn find_token_after_index(
1201    elements: &[SyntaxElement<RLanguage>],
1202    start_idx: usize,
1203    kind: SyntaxKind,
1204) -> Option<usize> {
1205    elements
1206        .iter()
1207        .enumerate()
1208        .skip(start_idx + 1)
1209        .find_map(|(idx, element)| match element {
1210            SyntaxElement::Token(token) if token.kind() == kind => Some(idx),
1211            _ => None,
1212        })
1213}