solidity_language_server/
utils.rs1use lintspec_core::textindex::{TextIndex, compute_indices};
2use serde_json::Value;
3use std::sync::OnceLock;
4use tower_lsp::lsp_types::{Position, PositionEncodingKind};
5
6static ENCODING: OnceLock<PositionEncoding> = OnceLock::new();
11
12pub fn set_encoding(enc: PositionEncoding) {
15 let _ = ENCODING.set(enc);
16}
17
18pub fn encoding() -> PositionEncoding {
20 ENCODING.get().copied().unwrap_or_default()
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum PositionEncoding {
30 Utf8,
32 #[default]
35 Utf16,
36}
37
38impl PositionEncoding {
39 pub fn negotiate(client_encodings: Option<&[PositionEncodingKind]>) -> Self {
43 let Some(encodings) = client_encodings else {
44 return Self::default();
45 };
46 if encodings.contains(&PositionEncodingKind::UTF8) {
47 PositionEncoding::Utf8
48 } else {
49 PositionEncoding::Utf16
50 }
51 }
52}
53
54impl From<PositionEncoding> for PositionEncodingKind {
55 fn from(value: PositionEncoding) -> Self {
56 match value {
57 PositionEncoding::Utf8 => PositionEncodingKind::UTF8,
58 PositionEncoding::Utf16 => PositionEncodingKind::UTF16,
59 }
60 }
61}
62
63pub fn byte_column_to_position(
81 source: &str,
82 row: usize,
83 byte_column: usize,
84 byte_offset: usize,
85) -> Position {
86 let column = match encoding() {
87 PositionEncoding::Utf8 => byte_column as u32,
88 PositionEncoding::Utf16 => {
89 let line_start = byte_offset.saturating_sub(byte_column);
90 source
91 .get(line_start..byte_offset)
92 .map_or(byte_column as u32, |prefix| {
93 prefix.encode_utf16().count() as u32
94 })
95 }
96 };
97 Position::new(row as u32, column)
98}
99
100pub fn byte_offset_to_position(source: &str, byte_offset: usize) -> Position {
103 if source.is_empty() {
104 return Position::new(0, 0);
105 }
106
107 let idx = if byte_offset >= source.len() {
108 let mut ti = TextIndex::ZERO;
111 let mut chars = source.chars().peekable();
112 while let Some(c) = chars.next() {
113 ti.advance(c, chars.peek());
114 }
115 ti
116 } else {
117 let indices = compute_indices(source, &[byte_offset]);
119 match indices.first() {
120 Some(ti) => *ti,
121 None => return Position::new(0, 0),
122 }
123 };
124
125 Position {
126 line: idx.line,
127 character: match encoding() {
128 PositionEncoding::Utf8 => idx.col_utf8,
129 PositionEncoding::Utf16 => idx.col_utf16,
130 },
131 }
132}
133
134pub fn position_to_byte_offset(source: &str, pos: Position) -> usize {
142 if source.is_empty() {
143 return 0;
144 }
145
146 let enc = encoding();
147
148 let chunk_offsets: Vec<usize> = (0..source.len()).step_by(128).collect();
150
151 let chunk_indices = compute_indices(source, &chunk_offsets);
153
154 let start = chunk_indices
158 .iter()
159 .take_while(|ti| {
160 if ti.line < pos.line {
161 return true;
162 }
163 if ti.line == pos.line {
164 let col = match enc {
165 PositionEncoding::Utf8 => ti.col_utf8,
166 PositionEncoding::Utf16 => ti.col_utf16,
167 };
168 return col <= pos.character;
169 }
170 false
171 })
172 .last()
173 .copied()
174 .unwrap_or(TextIndex::ZERO);
175
176 let mut idx = start;
178 let mut chars = source[idx.utf8..].chars().peekable();
179
180 while let Some(c) = chars.next() {
181 let col = match enc {
182 PositionEncoding::Utf8 => idx.col_utf8,
183 PositionEncoding::Utf16 => idx.col_utf16,
184 };
185 if idx.line >= pos.line && col >= pos.character {
186 return idx.utf8;
187 }
188 if idx.line == pos.line && c == '\n' {
189 return idx.utf8; }
191 idx.advance(c, chars.peek());
192 }
193 source.len() }
195
196pub fn is_valid_solidity_identifier(name: &str) -> bool {
202 let mut chars = name.chars();
203 let Some(first) = chars.next() else {
204 return false;
205 };
206 if !first.is_ascii_alphabetic() && first != '_' && first != '$' {
207 return false;
208 }
209 if !chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$') {
210 return false;
211 }
212 if SOLIDITY_KEYWORDS.contains(&name) {
213 return false;
214 }
215 if is_numeric_type_keyword(name) {
216 return false;
217 }
218 true
219}
220
221const SOLIDITY_KEYWORDS: &[&str] = &[
227 "abstract",
229 "address",
230 "anonymous",
231 "as",
232 "assembly",
233 "bool",
234 "break",
235 "bytes",
236 "calldata",
237 "catch",
238 "constant",
239 "constructor",
240 "continue",
241 "contract",
242 "delete",
243 "do",
244 "else",
245 "emit",
246 "enum",
247 "event",
248 "external",
249 "fallback",
250 "false",
251 "fixed",
252 "for",
253 "function",
254 "hex",
255 "if",
256 "immutable",
257 "import",
258 "indexed",
259 "interface",
260 "internal",
261 "is",
262 "library",
263 "mapping",
264 "memory",
265 "modifier",
266 "new",
267 "override",
268 "payable",
269 "pragma",
270 "private",
271 "public",
272 "pure",
273 "receive",
274 "return",
275 "returns",
276 "storage",
277 "string",
278 "struct",
279 "true",
280 "try",
281 "type",
282 "ufixed",
283 "unchecked",
284 "unicode",
285 "using",
286 "view",
287 "virtual",
288 "while",
289 "after",
291 "alias",
292 "apply",
293 "auto",
294 "byte",
295 "case",
296 "copyof",
297 "default",
298 "define",
299 "final",
300 "implements",
301 "in",
302 "inline",
303 "let",
304 "macro",
305 "match",
306 "mutable",
307 "null",
308 "of",
309 "partial",
310 "promise",
311 "reference",
312 "relocatable",
313 "sealed",
314 "sizeof",
315 "static",
316 "supports",
317 "switch",
318 "typedef",
319 "typeof",
320 "var",
321];
322
323fn is_numeric_type_keyword(name: &str) -> bool {
325 if let Some(suffix) = name
326 .strip_prefix("uint")
327 .or_else(|| name.strip_prefix("int"))
328 {
329 if suffix.is_empty() {
330 return true;
331 }
332 let Ok(n) = suffix.parse::<u16>() else {
333 return false;
334 };
335 return (8..=256).contains(&n) && n % 8 == 0;
336 }
337 if let Some(suffix) = name.strip_prefix("bytes") {
338 let Ok(n) = suffix.parse::<u16>() else {
340 return false;
341 };
342 return (1..=32).contains(&n);
343 }
344 false
345}
346
347pub fn push_if_node_or_array<'a>(tree: &'a Value, key: &str, stack: &mut Vec<&'a Value>) {
357 if let Some(value) = tree.get(key) {
358 match value {
359 Value::Array(arr) => stack.extend(arr),
360 Value::Object(_) => stack.push(value),
361 _ => {}
362 }
363 }
364}