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