use std::{
borrow::Cow,
cmp::{self, Ordering},
fmt::{self, Display},
};
use sway_types::{SourceEngine, SourceId, Span};
use crate::diagnostic::Hint;
pub fn get_file_name(source_engine: &SourceEngine, source_id: Option<&SourceId>) -> Option<String> {
match source_id {
Some(source_id) => source_engine.get_file_name(source_id),
None => None,
}
}
pub fn num_to_str(num: usize) -> String {
match num {
0 => "zero".to_string(),
1 => "one".to_string(),
2 => "two".to_string(),
3 => "three".to_string(),
4 => "four".to_string(),
5 => "five".to_string(),
6 => "six".to_string(),
7 => "seven".to_string(),
8 => "eight".to_string(),
9 => "nine".to_string(),
10 => "ten".to_string(),
_ => format!("{num}"),
}
}
pub fn num_to_str_or_none(num: usize) -> String {
if num == 0 {
"none".to_string()
} else {
num_to_str(num)
}
}
pub enum Enclosing {
#[allow(dead_code)]
None,
DoubleQuote,
}
impl Display for Enclosing {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::None => "",
Self::DoubleQuote => "\"",
},
)
}
}
pub enum Indent {
#[allow(dead_code)]
None,
Single,
Double,
}
impl Display for Indent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::None => "",
Self::Single => " ",
Self::Double => " ",
},
)
}
}
pub fn sequence_to_str<T>(sequence: &[T], enclosing: Enclosing, max_items: usize) -> String
where
T: Display,
{
sequence_to_str_impl(sequence, enclosing, max_items, "and")
}
pub fn sequence_to_str_or<T>(sequence: &[T], enclosing: Enclosing, max_items: usize) -> String
where
T: Display,
{
sequence_to_str_impl(sequence, enclosing, max_items, "or")
}
fn sequence_to_str_impl<T>(
sequence: &[T],
enclosing: Enclosing,
max_items: usize,
and_or: &str,
) -> String
where
T: Display,
{
assert!(
!sequence.is_empty(),
"Sequence to display must not be empty."
);
assert!(
max_items > 0,
"Maximum number of items to display must be greater than zero."
);
let max_items = cmp::min(max_items, sequence.len());
let (to_display, remaining) = sequence.split_at(max_items);
let fmt_item = |item: &T| format!("{enclosing}{item}{enclosing}");
if !remaining.is_empty() {
format!(
"{}, {} {} more",
to_display
.iter()
.map(fmt_item)
.collect::<Vec<_>>()
.join(", "),
and_or,
num_to_str(remaining.len())
)
} else {
match to_display {
[] => unreachable!("There must be at least one item in the sequence."),
[item] => fmt_item(item),
[first_item, second_item] => {
format!(
"{} {} {}",
fmt_item(first_item),
and_or,
fmt_item(second_item)
)
}
_ => format!(
"{}, {} {}",
to_display
.split_last()
.unwrap()
.1
.iter()
.map(fmt_item)
.collect::<Vec::<_>>()
.join(", "),
and_or,
fmt_item(to_display.last().unwrap())
),
}
}
}
pub fn sequence_to_list<T>(sequence: &[T], indent: Indent, max_items: usize) -> Vec<String>
where
T: Display,
{
assert!(
!sequence.is_empty(),
"Sequence to display must not be empty."
);
assert!(
max_items > 0,
"Maximum number of items to display must be greater than zero."
);
let mut result = vec![];
let max_items = cmp::min(max_items, sequence.len());
let (to_display, remaining) = sequence.split_at(max_items);
for item in to_display {
result.push(format!("{indent}- {item}"));
}
if !remaining.is_empty() {
result.push(format!(
"{indent}- and {} more",
num_to_str(remaining.len())
));
}
result
}
pub fn plural_s(count: usize) -> &'static str {
if count == 1 {
""
} else {
"s"
}
}
pub fn is_are(count: usize) -> &'static str {
if count == 1 {
"is"
} else {
"are"
}
}
pub fn singular_plural<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str {
if count == 1 {
singular
} else {
plural
}
}
pub fn short_name(full_name: &str) -> String {
let mut name_start_index = 0;
loop {
let reminder = &full_name[name_start_index..];
if reminder.starts_with('&') {
name_start_index += 1;
} else if reminder.starts_with("mut ") {
name_start_index += 4;
} else {
break;
}
}
let full_name_without_refs = &full_name[name_start_index..];
let full_name_without_generics = match full_name_without_refs.find('<') {
Some(index) => &full_name_without_refs[..index],
None => full_name_without_refs,
};
let short_name = match full_name_without_generics.rfind(':') {
Some(index) if index < full_name_without_generics.len() - 1 => {
full_name_without_generics.split_at(index + 1).1.to_string()
}
_ => full_name_without_generics.to_string(),
};
format!("{}{short_name}", &full_name[..name_start_index])
}
pub fn a_or_an<S: AsRef<str> + ?Sized>(word: &S) -> &'static str {
let is_a = in_definite::is_an(word.as_ref());
match is_a {
in_definite::Is::An => "an ",
in_definite::Is::A => "a ",
in_definite::Is::None => "",
}
}
pub fn ord_num_suffix(num: usize) -> &'static str {
match num % 100 {
11..=13 => "th",
_ => match num % 10 {
1 => "st",
2 => "nd", 3 => "rd",
_ => "th",
},
}
}
pub fn ascii_sentence_case(text: &String) -> Cow<String> {
if text.is_empty() || text.chars().next().unwrap().is_uppercase() {
Cow::Borrowed(text)
} else {
let mut result = text.clone();
result[0..1].make_ascii_uppercase();
Cow::Owned(result.to_owned())
}
}
pub fn first_line(text: &str, with_ellipses: bool) -> Cow<str> {
if !text.contains('\n') {
Cow::Borrowed(text)
} else {
let index_of_new_line = text.find('\n').unwrap();
Cow::Owned(text[..index_of_new_line].to_string() + if with_ellipses { "..." } else { "" })
}
}
pub fn did_you_mean<T, I>(v: &str, possible_values: I, max_num_of_suggestions: usize) -> Vec<String>
where
T: AsRef<str>,
I: IntoIterator<Item = T>,
{
let mut candidates: Vec<_> = possible_values
.into_iter()
.map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned()))
.filter(|(confidence, _)| *confidence > 0.7)
.collect();
candidates.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
candidates
.into_iter()
.take(max_num_of_suggestions)
.map(|(_, pv)| pv)
.collect()
}
pub fn did_you_mean_help<T, I>(
source_engine: &SourceEngine,
span: Span,
possible_values: I,
max_num_of_suggestions: usize,
enclosing: Enclosing,
) -> Hint
where
T: AsRef<str>,
I: IntoIterator<Item = T>,
{
let suggestions = &did_you_mean(span.as_str(), possible_values, max_num_of_suggestions);
if suggestions.is_empty() {
Hint::none()
} else {
Hint::help(
source_engine,
span,
format!(
"Did you mean {}?",
sequence_to_str_or(suggestions, enclosing, max_num_of_suggestions)
),
)
}
}
mod test {
#[test]
fn test_short_name() {
use super::short_name;
let test = |full_name: &str, expected: &str| {
let short_name = short_name(full_name);
assert_eq!(short_name, expected, "Full name: {full_name}.");
};
test("SomeType", "SomeType");
test("&SomeType", "&SomeType");
test("&&&SomeType", "&&&SomeType");
test("&mut &&mut SomeType", "&mut &&mut SomeType");
test("&&&mut &mut SomeType", "&&&mut &mut SomeType");
test("SomeType<T>", "SomeType");
test("&SomeType<&T>", "&SomeType");
test("&&&SomeType<&&&T>", "&&&SomeType");
test("&mut &&mut SomeType<&mut &&mut T>", "&mut &&mut SomeType");
test(
"&&&mut &mut SomeType<&&&mut &mut T>",
"&&&mut &mut SomeType",
);
test("std::ops::Eq", "Eq");
test("some_lib::Struct<A, B>", "Struct");
test("&&mut some_lib::Struct<&A, &mut B>", "&&mut Struct");
test(
"some_lib::Struct<some::other::lib::A, some::other::lib::B>",
"Struct",
);
test(
"&&&mut some_lib::Struct<some::other::lib::A, some::other::lib::B>",
"&&&mut Struct",
);
test(
"some_lib::fn::function<some::other::lib::A<T1, T2>, some::other::lib::B<T3>>",
"function",
);
}
}