biome_rowan/
syntax_node_text.rs

1use crate::{
2    cursor::{SyntaxNode, SyntaxToken},
3    TextRange, TextSize, TokenAtOffset,
4};
5use biome_text_size::TextLen;
6use std::iter::FusedIterator;
7use std::{cmp::Ordering, fmt};
8
9#[derive(Clone)]
10pub struct SyntaxNodeText {
11    node: SyntaxNode,
12    range: TextRange,
13}
14
15impl SyntaxNodeText {
16    pub(crate) fn new(node: SyntaxNode) -> SyntaxNodeText {
17        let range = node.text_range();
18        SyntaxNodeText { node, range }
19    }
20
21    pub(crate) fn with_range(node: SyntaxNode, range: TextRange) -> SyntaxNodeText {
22        SyntaxNodeText { node, range }
23    }
24
25    pub fn len(&self) -> TextSize {
26        self.range.len()
27    }
28
29    pub fn is_empty(&self) -> bool {
30        self.range.is_empty()
31    }
32
33    pub fn contains_char(&self, c: char) -> bool {
34        self.try_for_each_chunk(|chunk| if chunk.contains(c) { Err(()) } else { Ok(()) })
35            .is_err()
36    }
37
38    pub fn find_char(&self, c: char) -> Option<TextSize> {
39        let mut acc: TextSize = 0.into();
40        let res = self.try_for_each_chunk(|chunk| {
41            if let Some(pos) = chunk.find(c) {
42                let pos: TextSize = (pos as u32).into();
43                return Err(acc + pos);
44            }
45            acc += TextSize::of(chunk);
46            Ok(())
47        });
48        found(res)
49    }
50
51    pub fn char_at(&self, offset: TextSize) -> Option<char> {
52        let mut start: TextSize = 0.into();
53        let res = self.try_for_each_chunk(|chunk| {
54            let end = start + TextSize::of(chunk);
55            if start <= offset && offset < end {
56                let off: usize = u32::from(offset - start) as usize;
57                return Err(chunk[off..].chars().next().unwrap());
58            }
59            start = end;
60            Ok(())
61        });
62        found(res)
63    }
64
65    pub fn slice<R: private::SyntaxTextRange>(&self, range: R) -> SyntaxNodeText {
66        let start = range.start().unwrap_or_default();
67        let end = range.end().unwrap_or_else(|| self.len());
68        assert!(start <= end);
69        let len = end - start;
70        let start = self.range.start() + start;
71        let end = start + len;
72        assert!(
73            start <= end,
74            "invalid slice, range: {:?}, slice: {:?}",
75            self.range,
76            (range.start(), range.end()),
77        );
78        let range = TextRange::new(start, end);
79        assert!(
80            self.range.contains_range(range),
81            "invalid slice, range: {:?}, slice: {:?}",
82            self.range,
83            range,
84        );
85        SyntaxNodeText {
86            node: self.node.clone(),
87            range,
88        }
89    }
90
91    pub fn starts_with(&self, mut prefix: &str) -> bool {
92        for (token, range) in self.tokens_with_ranges() {
93            if prefix.is_empty() {
94                return true;
95            }
96
97            let text = &token.text()[range];
98            match text.len().cmp(&prefix.len()) {
99                Ordering::Equal => return text == prefix,
100                Ordering::Greater => return text.starts_with(prefix),
101                Ordering::Less => {
102                    if text == &prefix[..text.len()] {
103                        prefix = &prefix[text.len()..];
104                    } else {
105                        return false;
106                    }
107                }
108            }
109        }
110
111        prefix.is_empty()
112    }
113
114    pub fn try_fold_chunks<T, F, E>(&self, init: T, mut f: F) -> Result<T, E>
115    where
116        F: FnMut(T, &str) -> Result<T, E>,
117    {
118        self.tokens_with_ranges()
119            .try_fold(init, move |acc, (token, range)| {
120                f(acc, &token.text()[range])
121            })
122    }
123
124    pub fn try_for_each_chunk<F: FnMut(&str) -> Result<(), E>, E>(
125        &self,
126        mut f: F,
127    ) -> Result<(), E> {
128        self.try_fold_chunks((), move |(), chunk| f(chunk))
129    }
130
131    pub fn for_each_chunk<F: FnMut(&str)>(&self, mut f: F) {
132        enum Void {}
133        let out = self.try_for_each_chunk(|chunk| {
134            f(chunk);
135            Ok::<(), Void>(())
136        });
137        match out {
138            Ok(()) => (),
139            Err(void) => match void {},
140        }
141    }
142
143    fn tokens_with_ranges(&self) -> impl FusedIterator<Item = (SyntaxToken, TextRange)> {
144        SyntaxNodeTokenWithRanges::new(self)
145    }
146
147    pub fn chars(&self) -> impl FusedIterator<Item = char> {
148        SyntaxNodeTextChars::new(self)
149    }
150}
151
152#[derive(Clone)]
153struct SyntaxNodeTokenWithRanges {
154    text_range: TextRange,
155    next_token: Option<(SyntaxToken, TextRange)>,
156}
157
158impl SyntaxNodeTokenWithRanges {
159    fn new(text: &SyntaxNodeText) -> Self {
160        let text_range = text.range;
161
162        let token = match text.node.token_at_offset(text_range.start()) {
163            TokenAtOffset::None => None,
164            TokenAtOffset::Single(token) => Some(token),
165            TokenAtOffset::Between(_, next) => Some(next),
166        };
167
168        Self {
169            next_token: token.and_then(|token| Self::with_intersecting_range(token, text_range)),
170            text_range,
171        }
172    }
173
174    fn with_intersecting_range(
175        token: SyntaxToken,
176        text_range: TextRange,
177    ) -> Option<(SyntaxToken, TextRange)> {
178        let token_range = token.text_range();
179
180        let range = text_range.intersect(token_range)?;
181        Some((token, range - token_range.start()))
182    }
183}
184
185impl Iterator for SyntaxNodeTokenWithRanges {
186    type Item = (SyntaxToken, TextRange);
187
188    fn next(&mut self) -> Option<Self::Item> {
189        let (token, range) = self.next_token.take()?;
190
191        self.next_token = token
192            .next_token()
193            .and_then(|token| Self::with_intersecting_range(token, self.text_range));
194
195        Some((token, range))
196    }
197}
198
199impl FusedIterator for SyntaxNodeTokenWithRanges {}
200
201#[derive(Clone)]
202struct SyntaxNodeTextChars {
203    head: Option<(SyntaxToken, TextRange)>,
204    tail: SyntaxNodeTokenWithRanges,
205    index: TextSize,
206}
207
208impl SyntaxNodeTextChars {
209    fn new(text: &SyntaxNodeText) -> Self {
210        let mut chunks = SyntaxNodeTokenWithRanges::new(text);
211
212        Self {
213            head: chunks.next(),
214            tail: chunks,
215            index: TextSize::default(),
216        }
217    }
218}
219
220impl Iterator for SyntaxNodeTextChars {
221    type Item = char;
222
223    fn next(&mut self) -> Option<Self::Item> {
224        loop {
225            let (token, range) = self.head.as_ref()?;
226
227            if self.index >= range.end() {
228                self.head = self.tail.next();
229                self.index = TextSize::default();
230                continue;
231            }
232
233            let text = token.text();
234
235            // SAFETY: Index check above guarantees that there's at least some text left
236            let next_char = text[TextRange::new(self.index, range.end())]
237                .chars()
238                .next()
239                .unwrap();
240
241            self.index += next_char.text_len();
242            break Some(next_char);
243        }
244    }
245}
246
247impl FusedIterator for SyntaxNodeTextChars {}
248
249fn found<T>(res: Result<(), T>) -> Option<T> {
250    match res {
251        Ok(()) => None,
252        Err(it) => Some(it),
253    }
254}
255
256impl fmt::Debug for SyntaxNodeText {
257    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258        fmt::Debug::fmt(&self.to_string(), f)
259    }
260}
261
262impl fmt::Display for SyntaxNodeText {
263    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
264        self.try_for_each_chunk(|chunk| fmt::Display::fmt(chunk, f))
265    }
266}
267
268impl From<SyntaxNodeText> for String {
269    fn from(text: SyntaxNodeText) -> String {
270        text.to_string()
271    }
272}
273
274impl PartialEq<str> for SyntaxNodeText {
275    fn eq(&self, mut rhs: &str) -> bool {
276        self.try_for_each_chunk(|chunk| {
277            if !rhs.starts_with(chunk) {
278                return Err(());
279            }
280            rhs = &rhs[chunk.len()..];
281            Ok(())
282        })
283        .is_ok()
284            && rhs.is_empty()
285    }
286}
287
288impl PartialEq<SyntaxNodeText> for str {
289    fn eq(&self, rhs: &SyntaxNodeText) -> bool {
290        rhs == self
291    }
292}
293
294impl PartialEq<&'_ str> for SyntaxNodeText {
295    fn eq(&self, rhs: &&str) -> bool {
296        self == *rhs
297    }
298}
299
300impl PartialEq<SyntaxNodeText> for &'_ str {
301    fn eq(&self, rhs: &SyntaxNodeText) -> bool {
302        rhs == self
303    }
304}
305
306impl PartialEq for SyntaxNodeText {
307    fn eq(&self, other: &SyntaxNodeText) -> bool {
308        if self.range.len() != other.range.len() {
309            return false;
310        }
311        let mut lhs = self.tokens_with_ranges();
312        let mut rhs = other.tokens_with_ranges();
313        zip_texts(&mut lhs, &mut rhs).is_none()
314            && lhs.all(|it| it.1.is_empty())
315            && rhs.all(|it| it.1.is_empty())
316    }
317}
318
319fn zip_texts<I: Iterator<Item = (SyntaxToken, TextRange)>>(xs: &mut I, ys: &mut I) -> Option<()> {
320    let mut x = xs.next()?;
321    let mut y = ys.next()?;
322    loop {
323        while x.1.is_empty() {
324            x = xs.next()?;
325        }
326        while y.1.is_empty() {
327            y = ys.next()?;
328        }
329        let x_text = &x.0.text()[x.1];
330        let y_text = &y.0.text()[y.1];
331        if !(x_text.starts_with(y_text) || y_text.starts_with(x_text)) {
332            return Some(());
333        }
334        let advance = std::cmp::min(x.1.len(), y.1.len());
335        x.1 = TextRange::new(x.1.start() + advance, x.1.end());
336        y.1 = TextRange::new(y.1.start() + advance, y.1.end());
337    }
338}
339
340impl Eq for SyntaxNodeText {}
341
342mod private {
343    use std::ops;
344
345    use crate::{TextRange, TextSize};
346
347    pub trait SyntaxTextRange {
348        fn start(&self) -> Option<TextSize>;
349        fn end(&self) -> Option<TextSize>;
350    }
351
352    impl SyntaxTextRange for TextRange {
353        fn start(&self) -> Option<TextSize> {
354            Some(TextRange::start(*self))
355        }
356        fn end(&self) -> Option<TextSize> {
357            Some(TextRange::end(*self))
358        }
359    }
360
361    impl SyntaxTextRange for ops::Range<TextSize> {
362        fn start(&self) -> Option<TextSize> {
363            Some(self.start)
364        }
365        fn end(&self) -> Option<TextSize> {
366            Some(self.end)
367        }
368    }
369
370    impl SyntaxTextRange for ops::RangeFrom<TextSize> {
371        fn start(&self) -> Option<TextSize> {
372            Some(self.start)
373        }
374        fn end(&self) -> Option<TextSize> {
375            None
376        }
377    }
378
379    impl SyntaxTextRange for ops::RangeTo<TextSize> {
380        fn start(&self) -> Option<TextSize> {
381            None
382        }
383        fn end(&self) -> Option<TextSize> {
384            Some(self.end)
385        }
386    }
387
388    impl SyntaxTextRange for ops::RangeFull {
389        fn start(&self) -> Option<TextSize> {
390            None
391        }
392        fn end(&self) -> Option<TextSize> {
393            None
394        }
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use crate::raw_language::{RawLanguage, RawLanguageKind, RawSyntaxTreeBuilder};
401    use crate::SyntaxNode;
402
403    fn build_tree(chunks: &[&str]) -> SyntaxNode<RawLanguage> {
404        let mut builder = RawSyntaxTreeBuilder::new();
405        builder.start_node(RawLanguageKind::ROOT);
406        for &chunk in chunks.iter() {
407            builder.token(RawLanguageKind::STRING_TOKEN, chunk);
408        }
409        builder.finish_node();
410        builder.finish()
411    }
412
413    #[test]
414    fn test_text_equality() {
415        fn do_check(t1: &[&str], t2: &[&str]) {
416            let t1 = build_tree(t1).text();
417            let t2 = build_tree(t2).text();
418            let expected = t1.to_string() == t2.to_string();
419            let actual = t1 == t2;
420            assert_eq!(expected, actual, "`{t1}` (SyntaxText) `{t2}` (SyntaxText)");
421            let actual = t1 == *t2.to_string();
422            assert_eq!(expected, actual, "`{t1}` (SyntaxText) `{t2}` (&str)");
423        }
424        fn check(t1: &[&str], t2: &[&str]) {
425            do_check(t1, t2);
426            do_check(t2, t1)
427        }
428
429        check(&[""], &[""]);
430        check(&["a"], &[""]);
431        check(&["a"], &["a"]);
432        check(&["abc"], &["def"]);
433        check(&["hello", "world"], &["hello", "world"]);
434        check(&["hellowo", "rld"], &["hell", "oworld"]);
435        check(&["hel", "lowo", "rld"], &["helloworld"]);
436        check(&["{", "abc", "}"], &["{", "123", "}"]);
437        check(&["{", "abc", "}", "{"], &["{", "123", "}"]);
438        check(&["{", "abc", "}"], &["{", "123", "}", "{"]);
439        check(&["{", "abc", "}ab"], &["{", "abc", "}", "ab"]);
440    }
441
442    #[test]
443    fn test_chars() {
444        fn check(t1: &[&str], expected: &str) {
445            let t1 = build_tree(t1).text();
446            let actual = t1.chars().collect::<String>();
447
448            assert_eq!(
449                expected, &actual,
450                "`{actual}` (SyntaxText) `{expected}` (SyntaxText)"
451            );
452        }
453
454        check(&[""], "");
455        check(&["a"], "a");
456        check(&["hello", "world"], "helloworld");
457        check(&["hellowo", "rld"], "helloworld");
458        check(&["hel", "lowo", "rld"], "helloworld");
459        check(&["{", "abc", "}"], "{abc}");
460        check(&["{", "abc", "}", "{"], "{abc}{");
461        check(&["{", "abc", "}ab"], "{abc}ab");
462    }
463}