1use std::{
4 borrow::Cow,
5 cmp::{self, Ordering},
6 fmt::{self, Display},
7};
8
9use sway_types::{SourceEngine, SourceId, Span};
10
11use crate::diagnostic::Hint;
12
13pub fn get_file_name(source_engine: &SourceEngine, source_id: Option<&SourceId>) -> Option<String> {
17 match source_id {
18 Some(source_id) => source_engine.get_file_name(source_id),
19 None => None,
20 }
21}
22
23pub fn num_to_str(num: usize) -> String {
26 match num {
27 0 => "zero".to_string(),
28 1 => "one".to_string(),
29 2 => "two".to_string(),
30 3 => "three".to_string(),
31 4 => "four".to_string(),
32 5 => "five".to_string(),
33 6 => "six".to_string(),
34 7 => "seven".to_string(),
35 8 => "eight".to_string(),
36 9 => "nine".to_string(),
37 10 => "ten".to_string(),
38 _ => format!("{num}"),
39 }
40}
41
42pub fn num_to_str_or_none(num: usize) -> String {
47 if num == 0 {
48 "none".to_string()
49 } else {
50 num_to_str(num)
51 }
52}
53
54pub enum Enclosing {
55 #[allow(dead_code)]
56 None,
57 DoubleQuote,
58}
59
60impl Display for Enclosing {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(
63 f,
64 "{}",
65 match self {
66 Self::None => "",
67 Self::DoubleQuote => "\"",
68 },
69 )
70 }
71}
72
73pub enum Indent {
74 #[allow(dead_code)]
75 None,
76 Single,
77 Double,
78}
79
80impl Display for Indent {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(
83 f,
84 "{}",
85 match self {
86 Self::None => "",
87 Self::Single => " ",
88 Self::Double => " ",
89 },
90 )
91 }
92}
93
94pub fn sequence_to_str<T>(sequence: &[T], enclosing: Enclosing, max_items: usize) -> String
108where
109 T: Display,
110{
111 sequence_to_str_impl(sequence, enclosing, max_items, "and")
112}
113
114pub fn sequence_to_str_or<T>(sequence: &[T], enclosing: Enclosing, max_items: usize) -> String
128where
129 T: Display,
130{
131 sequence_to_str_impl(sequence, enclosing, max_items, "or")
132}
133
134fn sequence_to_str_impl<T>(
135 sequence: &[T],
136 enclosing: Enclosing,
137 max_items: usize,
138 and_or: &str,
139) -> String
140where
141 T: Display,
142{
143 assert!(
144 !sequence.is_empty(),
145 "Sequence to display must not be empty."
146 );
147 assert!(
148 max_items > 0,
149 "Maximum number of items to display must be greater than zero."
150 );
151
152 let max_items = cmp::min(max_items, sequence.len());
153
154 let (to_display, remaining) = sequence.split_at(max_items);
155
156 let fmt_item = |item: &T| format!("{enclosing}{item}{enclosing}");
157
158 if !remaining.is_empty() {
159 format!(
160 "{}, {} {} more",
161 to_display
162 .iter()
163 .map(fmt_item)
164 .collect::<Vec<_>>()
165 .join(", "),
166 and_or,
167 num_to_str(remaining.len())
168 )
169 } else {
170 match to_display {
171 [] => unreachable!("There must be at least one item in the sequence."),
172 [item] => fmt_item(item),
173 [first_item, second_item] => {
174 format!(
175 "{} {} {}",
176 fmt_item(first_item),
177 and_or,
178 fmt_item(second_item)
179 )
180 }
181 _ => format!(
182 "{}, {} {}",
183 to_display
184 .split_last()
185 .unwrap()
186 .1
187 .iter()
188 .map(fmt_item)
189 .collect::<Vec::<_>>()
190 .join(", "),
191 and_or,
192 fmt_item(to_display.last().unwrap())
193 ),
194 }
195 }
196}
197
198pub fn sequence_to_list<T>(sequence: &[T], indent: Indent, max_items: usize) -> Vec<String>
216where
217 T: Display,
218{
219 assert!(
220 !sequence.is_empty(),
221 "Sequence to display must not be empty."
222 );
223 assert!(
224 max_items > 0,
225 "Maximum number of items to display must be greater than zero."
226 );
227
228 let mut result = vec![];
229
230 let max_items = cmp::min(max_items, sequence.len());
231 let (to_display, remaining) = sequence.split_at(max_items);
232 for item in to_display {
233 result.push(format!("{indent}- {item}"));
234 }
235 if !remaining.is_empty() {
236 result.push(format!(
237 "{indent}- and {} more",
238 num_to_str(remaining.len())
239 ));
240 }
241
242 result
243}
244
245pub fn plural_s(count: usize) -> &'static str {
248 if count == 1 {
249 ""
250 } else {
251 "s"
252 }
253}
254
255pub fn is_are(count: usize) -> &'static str {
257 if count == 1 {
258 "is"
259 } else {
260 "are"
261 }
262}
263
264pub fn singular_plural<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str {
266 if count == 1 {
267 singular
268 } else {
269 plural
270 }
271}
272
273pub fn short_name(full_name: &str) -> String {
290 let mut name_start_index = 0;
292 loop {
293 let reminder = &full_name[name_start_index..];
294 if reminder.starts_with('&') {
295 name_start_index += 1;
296 } else if reminder.starts_with("mut ") {
297 name_start_index += 4;
298 } else {
299 break;
300 }
301 }
302 let full_name_without_refs = &full_name[name_start_index..];
303 let full_name_without_generics = match full_name_without_refs.find('<') {
304 Some(index) => &full_name_without_refs[..index],
305 None => full_name_without_refs,
306 };
307 let short_name = match full_name_without_generics.rfind(':') {
308 Some(index) if index < full_name_without_generics.len() - 1 => {
309 full_name_without_generics.split_at(index + 1).1.to_string()
310 }
311 _ => full_name_without_generics.to_string(),
312 };
313 format!("{}{short_name}", &full_name[..name_start_index])
314}
315
316pub fn a_or_an<S: AsRef<str> + ?Sized>(word: &S) -> &'static str {
324 let is_a = in_definite::is_an(word.as_ref());
325 match is_a {
326 in_definite::Is::An => "an ",
327 in_definite::Is::A => "a ",
328 in_definite::Is::None => "",
329 }
330}
331
332pub fn ord_num_suffix(num: usize) -> &'static str {
335 match num % 100 {
336 11..=13 => "th",
337 _ => match num % 10 {
338 1 => "st",
339 2 => "nd", 3 => "rd",
341 _ => "th",
342 },
343 }
344}
345
346pub fn ascii_sentence_case(text: &String) -> Cow<String> {
348 if text.is_empty() || text.chars().next().unwrap().is_uppercase() {
349 Cow::Borrowed(text)
350 } else {
351 let mut result = text.clone();
352 result[0..1].make_ascii_uppercase();
353 Cow::Owned(result.to_owned())
354 }
355}
356
357pub fn first_line(text: &str, with_ellipses: bool) -> Cow<str> {
373 if !text.contains('\n') {
374 Cow::Borrowed(text)
375 } else {
376 let index_of_new_line = text.find('\n').unwrap();
377 Cow::Owned(text[..index_of_new_line].to_string() + if with_ellipses { "..." } else { "" })
378 }
379}
380
381pub fn did_you_mean<T, I>(v: &str, possible_values: I, max_num_of_suggestions: usize) -> Vec<String>
388where
389 T: AsRef<str>,
390 I: IntoIterator<Item = T>,
391{
392 let mut candidates: Vec<_> = possible_values
393 .into_iter()
394 .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned()))
395 .filter(|(confidence, _)| *confidence > 0.7)
397 .collect();
398 candidates.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
399 candidates
400 .into_iter()
401 .take(max_num_of_suggestions)
402 .map(|(_, pv)| pv)
403 .collect()
404}
405
406pub fn did_you_mean_help<T, I>(
411 source_engine: &SourceEngine,
412 span: Span,
413 possible_values: I,
414 max_num_of_suggestions: usize,
415 enclosing: Enclosing,
416) -> Hint
417where
418 T: AsRef<str>,
419 I: IntoIterator<Item = T>,
420{
421 let suggestions = &did_you_mean(span.as_str(), possible_values, max_num_of_suggestions);
422 if suggestions.is_empty() {
423 Hint::none()
424 } else {
425 Hint::help(
426 source_engine,
427 span,
428 format!(
429 "Did you mean {}?",
430 sequence_to_str_or(suggestions, enclosing, max_num_of_suggestions)
431 ),
432 )
433 }
434}
435
436mod test {
437 #[test]
438 fn test_short_name() {
439 use super::short_name;
440
441 let test = |full_name: &str, expected: &str| {
442 let short_name = short_name(full_name);
443 assert_eq!(short_name, expected, "Full name: {full_name}.");
444 };
445
446 test("SomeType", "SomeType");
447 test("&SomeType", "&SomeType");
448 test("&&&SomeType", "&&&SomeType");
449 test("&mut &&mut SomeType", "&mut &&mut SomeType");
450 test("&&&mut &mut SomeType", "&&&mut &mut SomeType");
451 test("SomeType<T>", "SomeType");
452 test("&SomeType<&T>", "&SomeType");
453 test("&&&SomeType<&&&T>", "&&&SomeType");
454 test("&mut &&mut SomeType<&mut &&mut T>", "&mut &&mut SomeType");
455 test(
456 "&&&mut &mut SomeType<&&&mut &mut T>",
457 "&&&mut &mut SomeType",
458 );
459 test("std::ops::Eq", "Eq");
460 test("some_lib::Struct<A, B>", "Struct");
461 test("&&mut some_lib::Struct<&A, &mut B>", "&&mut Struct");
462 test(
463 "some_lib::Struct<some::other::lib::A, some::other::lib::B>",
464 "Struct",
465 );
466 test(
467 "&&&mut some_lib::Struct<some::other::lib::A, some::other::lib::B>",
468 "&&&mut Struct",
469 );
470 test(
471 "some_lib::fn::function<some::other::lib::A<T1, T2>, some::other::lib::B<T3>>",
472 "function",
473 );
474 }
475}