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