taplo/util/
mod.rs

1use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxNode};
2use rowan::TextRange;
3use rowan::TextSize;
4
5pub(crate) mod iter;
6pub(crate) mod shared;
7
8mod escape;
9pub mod syntax;
10
11pub use escape::check_escape;
12pub use escape::{escape, unescape};
13
14pub(crate) mod allowed_chars {
15    pub(crate) fn comment(s: &str) -> Result<(), Vec<usize>> {
16        let mut err_indices = Vec::new();
17
18        for (i, c) in s.chars().enumerate() {
19            if c != '\t' && c.is_control() {
20                err_indices.push(i);
21            }
22        }
23
24        if err_indices.is_empty() {
25            Ok(())
26        } else {
27            Err(err_indices)
28        }
29    }
30
31    pub(crate) fn string(s: &str) -> Result<(), Vec<usize>> {
32        let mut err_indices = Vec::new();
33
34        for (i, c) in s.chars().enumerate() {
35            if c != '\t'
36                && (('\u{0000}'..='\u{0008}').contains(&c)
37                    || ('\u{000A}'..='\u{001F}').contains(&c)
38                    || c == '\u{007F}')
39            {
40                err_indices.push(i);
41            }
42        }
43
44        if err_indices.is_empty() {
45            Ok(())
46        } else {
47            Err(err_indices)
48        }
49    }
50
51    pub(crate) fn multi_line_string(s: &str) -> Result<(), Vec<usize>> {
52        let mut err_indices = Vec::new();
53
54        for (i, c) in s.chars().enumerate() {
55            if c != '\t'
56                && c != '\n'
57                && c != '\r'
58                && (('\u{0000}'..='\u{0008}').contains(&c)
59                    || ('\u{000A}'..='\u{001F}').contains(&c)
60                    || c == '\u{007F}')
61            {
62                err_indices.push(i);
63            }
64        }
65
66        if err_indices.is_empty() {
67            Ok(())
68        } else {
69            Err(err_indices)
70        }
71    }
72
73    pub(crate) fn string_literal(s: &str) -> Result<(), Vec<usize>> {
74        let mut err_indices = Vec::new();
75
76        for (i, c) in s.chars().enumerate() {
77            if c != '\t' && c.is_control() {
78                err_indices.push(i);
79            }
80        }
81
82        if err_indices.is_empty() {
83            Ok(())
84        } else {
85            Err(err_indices)
86        }
87    }
88
89    pub(crate) fn multi_line_string_literal(s: &str) -> Result<(), Vec<usize>> {
90        let mut err_indices = Vec::new();
91
92        for (i, c) in s.chars().enumerate() {
93            if c != '\t' && c != '\n' && c != '\r' && c.is_control() {
94                err_indices.push(i);
95            }
96        }
97
98        if err_indices.is_empty() {
99            Ok(())
100        } else {
101            Err(err_indices)
102        }
103    }
104}
105
106pub trait StrExt {
107    fn strip_quotes(self) -> Self;
108}
109
110impl StrExt for &str {
111    fn strip_quotes(self) -> Self {
112        if self.starts_with('\"') || self.starts_with('\'') {
113            &self[1..self.len() - 1]
114        } else {
115            self
116        }
117    }
118}
119
120/// Utility extension methods for Syntax Nodes.
121pub trait SyntaxExt {
122    /// Return a syntax node that contains the given offset.
123    fn find_node(&self, offset: TextSize, inclusive: bool) -> Option<SyntaxNode>;
124
125    /// Find the deepest node that contains the given offset.
126    fn find_node_deep(&self, offset: TextSize, inclusive: bool) -> Option<SyntaxNode> {
127        let mut node = self.find_node(offset, inclusive);
128        while let Some(n) = &node {
129            let new_node = n.find_node(offset, inclusive);
130            if new_node.is_some() {
131                node = new_node;
132            } else {
133                break;
134            }
135        }
136
137        node
138    }
139
140    /// Find a node or token by its kind.
141    fn find(&self, kind: SyntaxKind) -> Option<SyntaxElement>;
142}
143
144impl SyntaxExt for SyntaxNode {
145    fn find_node(&self, offset: TextSize, inclusive: bool) -> Option<SyntaxNode> {
146        for d in self.descendants().skip(1) {
147            let range = d.text_range();
148
149            if (inclusive && range.contains_inclusive(offset)) || range.contains(offset) {
150                return Some(d);
151            }
152        }
153
154        None
155    }
156
157    fn find(&self, kind: SyntaxKind) -> Option<SyntaxElement> {
158        self.descendants_with_tokens().find(|d| d.kind() == kind)
159    }
160}
161
162pub fn join_ranges<I: IntoIterator<Item = TextRange>>(ranges: I) -> TextRange {
163    ranges
164        .into_iter()
165        .fold(None, |ranges, range| match ranges {
166            Some(r) => Some(range.cover(r)),
167            None => Some(range),
168        })
169        .unwrap()
170}
171
172pub fn try_join_ranges<I: IntoIterator<Item = TextRange>>(ranges: I) -> Option<TextRange> {
173    ranges.into_iter().fold(None, |ranges, range| match ranges {
174        Some(r) => Some(range.cover(r)),
175        None => Some(range),
176    })
177}
178
179pub fn overlaps(range: TextRange, other: TextRange) -> bool {
180    range.contains_range(other)
181        || other.contains_range(range)
182        || range.contains(other.start())
183        || range.contains(other.end())
184        || other.contains(range.start())
185        || other.contains(range.end())
186}