diff --git a/crates/biome_configuration/src/editorconfig.rs b/crates/biome_configuration/src/editorconfig.rs index 9a265b7164d9..e286370075e4 100644 --- a/crates/biome_configuration/src/editorconfig.rs +++ b/crates/biome_configuration/src/editorconfig.rs @@ -279,18 +279,18 @@ fn expand_unknown_glob_patterns(pattern: &str) -> Result, EditorConf let mut all_variants = vec![]; let mut current_variants = None; - for (i, c) in pattern.chars().enumerate() { - match c { - '{' => { + for (index, byte) in pattern.bytes().enumerate() { + match byte { + b'{' => { if current_variants.is_none() { - current_variants = Some(Variants::new(i)); + current_variants = Some(Variants::new(index)); } else { // TODO: error, recursive brace expansion is not supported } } - '}' => { + b'}' => { if let Some(mut v) = current_variants.take() { - v.end = i; + v.end = index; v.parse_to_variants(&pattern[v.start..=v.end])?; all_variants.push(v); } diff --git a/crates/biome_css_analyze/src/lint/correctness/no_invalid_direction_in_linear_gradient.rs b/crates/biome_css_analyze/src/lint/correctness/no_invalid_direction_in_linear_gradient.rs index 104ce1c10e8b..3f62a2b1ff4c 100644 --- a/crates/biome_css_analyze/src/lint/correctness/no_invalid_direction_in_linear_gradient.rs +++ b/crates/biome_css_analyze/src/lint/correctness/no_invalid_direction_in_linear_gradient.rs @@ -92,8 +92,8 @@ impl Rule for NoInvalidDirectionInLinearGradient { if IN_KEYWORD.is_match(&first_css_parameter_text) { return None; } - if let Some(first_char) = first_css_parameter_text.chars().next() { - if first_char.is_ascii_digit() { + if let Some(first_byte) = first_css_parameter_text.bytes().next() { + if first_byte.is_ascii_digit() { if ANGLE.is_match(&first_css_parameter_text) { return None; } diff --git a/crates/biome_css_analyze/src/lint/correctness/no_invalid_grid_areas.rs b/crates/biome_css_analyze/src/lint/correctness/no_invalid_grid_areas.rs index 14aee6b9b16e..4c7074c7c73e 100644 --- a/crates/biome_css_analyze/src/lint/correctness/no_invalid_grid_areas.rs +++ b/crates/biome_css_analyze/src/lint/correctness/no_invalid_grid_areas.rs @@ -111,7 +111,7 @@ impl Rule for NoInvalidGridAreas { // Need to remove `"` with escaping slash from the grid area // Ex: "\"a a a\"" .map(|x| { - let trimmed_text = x.token_text(); + let trimmed_text = x.token_text_trimmed(); let text_range = x.text_range(); (trimmed_text, text_range) }) @@ -168,12 +168,12 @@ impl Rule for NoInvalidGridAreas { // Check if the grid areas are consistent fn is_consistent_grids(grid_areas_props: GridAreasProps) -> Option { - let first_prop = clean_text(&grid_areas_props[0].0); + let first_prop = inner_string_text(&grid_areas_props[0].0); let first_len = first_prop.len(); let mut shortest = &grid_areas_props[0]; for grid_areas_prop in &grid_areas_props { - let cleaned_text = clean_text(&grid_areas_prop.0); + let cleaned_text = inner_string_text(&grid_areas_prop.0); // Check if the grid areas are empty if cleaned_text.is_empty() { return Some(UseConsistentGridAreasState { @@ -184,7 +184,7 @@ fn is_consistent_grids(grid_areas_props: GridAreasProps) -> Option Option bool { - let prop = clean_text(&token_text); - let chars: Vec = prop.chars().filter(|c| !c.is_whitespace()).collect(); - let head = chars[0]; - chars.iter().all(|&c| c == head) + let prop = inner_string_text(&token_text); + let mut iter = prop.chars().filter(|c| !c.is_whitespace()); + let Some(head) = iter.next() else { + return true; + }; + iter.all(|c| c == head) } fn has_partial_match(grid_areas_props: &GridAreasProps) -> Option { let mut seen_parts = FxHashSet::default(); for (text, range) in grid_areas_props { - let prop = clean_text(text); + let prop = inner_string_text(text); let parts: FxHashSet = prop .split_whitespace() .map(|part| part.to_string()) @@ -248,6 +250,15 @@ fn has_partial_match(grid_areas_props: &GridAreasProps) -> Option None } -fn clean_text(text: &TokenText) -> String { - text.replace('"', "").trim().to_string() +fn inner_string_text(text: &TokenText) -> &str { + let result = text.text(); + if result.len() >= 2 { + debug_assert!( + (result.starts_with('"') && result.len() >= 2 && result.ends_with('"')) + || (result.starts_with('\'') && result.len() >= 2 && result.ends_with('\'')) + ); + result[1..result.len() - 1].trim() + } else { + result + } } diff --git a/crates/biome_css_analyze/src/lint/suspicious/no_duplicate_font_names.rs b/crates/biome_css_analyze/src/lint/suspicious/no_duplicate_font_names.rs index 2447e14a08fd..3f5a83c82e23 100644 --- a/crates/biome_css_analyze/src/lint/suspicious/no_duplicate_font_names.rs +++ b/crates/biome_css_analyze/src/lint/suspicious/no_duplicate_font_names.rs @@ -115,6 +115,7 @@ impl Rule for NoDuplicateFontNames { } // A font family name. e.g "Lucida Grande", "Arial". AnyCssValue::CssString(val) => { + // FIXME: avoid String allocation let normalized_font_name: String = val .text() .chars() diff --git a/crates/biome_diagnostics/src/display/frame.rs b/crates/biome_diagnostics/src/display/frame.rs index 92837307399e..f8cf9250b4da 100644 --- a/crates/biome_diagnostics/src/display/frame.rs +++ b/crates/biome_diagnostics/src/display/frame.rs @@ -393,16 +393,11 @@ pub(super) fn print_invisibles( // Get the first trailing whitespace character in the string let trailing_whitespace_index = input - .char_indices() + .bytes() + .enumerate() .rev() - .find_map(|(index, char)| { - if !char.is_ascii_whitespace() { - Some(index) - } else { - None - } - }) - .unwrap_or(input.len()); + .find(|(_, byte)| !byte.is_ascii_whitespace()) + .map_or(input.len(), |(index, _)| index); let mut iter = input.char_indices().peekable(); let mut prev_char_was_whitespace = false; diff --git a/crates/biome_formatter/src/token/number.rs b/crates/biome_formatter/src/token/number.rs index 7bba6911a23b..62998488cdb4 100644 --- a/crates/biome_formatter/src/token/number.rs +++ b/crates/biome_formatter/src/token/number.rs @@ -60,7 +60,7 @@ fn format_trimmed_number(text: &str) -> Cow { let text = text.to_ascii_lowercase_cow(); let mut copied_or_ignored_chars = 0usize; - let mut iter = text.chars().enumerate(); + let mut iter = text.bytes().enumerate(); let mut curr = iter.next(); let mut state = IntegerPart; @@ -68,10 +68,10 @@ fn format_trimmed_number(text: &str) -> Cow { let mut cleaned_text = String::new(); // Look at only the start of the text, ignore any sign, and make sure numbers always start with a digit. Add 0 if missing. - if let Some((_, '+' | '-')) = curr { + if let Some((_, b'+' | b'-')) = curr { curr = iter.next(); } - if let Some((curr_index, '.')) = curr { + if let Some((curr_index, b'.')) = curr { cleaned_text.push_str(&text[copied_or_ignored_chars..curr_index]); copied_or_ignored_chars = curr_index; cleaned_text.push('0'); @@ -91,7 +91,7 @@ fn format_trimmed_number(text: &str) -> Cow { dot_index, last_non_zero_index: None, }), - (curr_index, Some('e') | None), + (curr_index, Some(b'e') | None), ) => { // The decimal part equals zero, ignore it completely. // Caveat: Prettier still prints a single `.0` unless there was *only* a trailing dot. @@ -108,7 +108,7 @@ fn format_trimmed_number(text: &str) -> Cow { last_non_zero_index: Some(last_non_zero_index), .. }), - (curr_index, Some('e') | None), + (curr_index, Some(b'e') | None), ) if last_non_zero_index.get() < curr_index - 1 => { // The decimal part ends with at least one zero, ignore them but copy the part from the dot until the last non-zero. cleaned_text.push_str(&text[copied_or_ignored_chars..=last_non_zero_index.get()]); @@ -151,13 +151,13 @@ fn format_trimmed_number(text: &str) -> Cow { // Update state after the current char match (&state, curr) { // Cases entering or remaining in decimal part - (_, Some((curr_index, '.'))) => { + (_, Some((curr_index, b'.'))) => { state = DecimalPart(FormatNumberLiteralDecimalPart { dot_index: curr_index, last_non_zero_index: None, }); } - (DecimalPart(decimal_part), Some((curr_index, '1'..='9'))) => { + (DecimalPart(decimal_part), Some((curr_index, b'1'..=b'9'))) => { state = DecimalPart(FormatNumberLiteralDecimalPart { last_non_zero_index: Some(unsafe { // We've already entered InDecimalPart, so curr_index must be >0 @@ -167,7 +167,7 @@ fn format_trimmed_number(text: &str) -> Cow { }); } // Cases entering or remaining in exponent - (_, Some((curr_index, 'e'))) => { + (_, Some((curr_index, b'e'))) => { state = Exponent(FormatNumberLiteralExponent { e_index: curr_index, is_negative: false, @@ -175,7 +175,7 @@ fn format_trimmed_number(text: &str) -> Cow { first_non_zero_index: None, }); } - (Exponent(exponent), Some((_, '-'))) => { + (Exponent(exponent), Some((_, b'-'))) => { state = Exponent(FormatNumberLiteralExponent { is_negative: true, ..*exponent @@ -188,14 +188,14 @@ fn format_trimmed_number(text: &str) -> Cow { .. }, ), - Some((curr_index, curr_char @ '0'..='9')), + Some((curr_index, curr_char @ b'0'..=b'9')), ) => { state = Exponent(FormatNumberLiteralExponent { first_digit_index: Some(unsafe { // We've already entered InExponent, so curr_index must be >0 NonZeroUsize::new_unchecked(curr_index) }), - first_non_zero_index: if curr_char != '0' { + first_non_zero_index: if curr_char != b'0' { Some(unsafe { // We've already entered InExponent, so curr_index must be >0 NonZeroUsize::new_unchecked(curr_index) @@ -213,7 +213,7 @@ fn format_trimmed_number(text: &str) -> Cow { .. }, ), - Some((curr_index, '1'..='9')), + Some((curr_index, b'1'..=b'9')), ) => { state = Exponent(FormatNumberLiteralExponent { first_non_zero_index: Some(unsafe { NonZeroUsize::new_unchecked(curr_index) }), @@ -225,7 +225,7 @@ fn format_trimmed_number(text: &str) -> Cow { // Repeat or exit match curr { - None | Some((_, 'x') /* hex bailout */) => break, + None | Some((_, b'x') /* hex bailout */) => break, Some(_) => curr = iter.next(), } } diff --git a/crates/biome_graphql_formatter/src/graphql/value/string_value.rs b/crates/biome_graphql_formatter/src/graphql/value/string_value.rs index d6da9de31385..241f4e70cf4b 100644 --- a/crates/biome_graphql_formatter/src/graphql/value/string_value.rs +++ b/crates/biome_graphql_formatter/src/graphql/value/string_value.rs @@ -28,7 +28,7 @@ impl FormatNodeRule for FormatGraphqlStringValue { let min_indent = trimmed_content .lines() .filter(|line| !line.trim().is_empty()) // Ignore empty lines - .map(|line| line.chars().take_while(|&c| c.is_whitespace()).count()) + .map(|line| line.bytes().take_while(|b| b.is_ascii_whitespace()).count()) .min() .unwrap_or(0); @@ -73,5 +73,5 @@ impl FormatNodeRule for FormatGraphqlStringValue { } fn is_blank(line: &str) -> bool { - line.chars().all(|c| c.is_whitespace()) + line.bytes().all(|byte| byte.is_ascii_whitespace()) } diff --git a/crates/biome_grit_patterns/src/grit_built_in_functions.rs b/crates/biome_grit_patterns/src/grit_built_in_functions.rs index 108c356f5118..c0c6a02018f4 100644 --- a/crates/biome_grit_patterns/src/grit_built_in_functions.rs +++ b/crates/biome_grit_patterns/src/grit_built_in_functions.rs @@ -116,7 +116,9 @@ fn capitalize_fn<'a>( }; let string = arg1.text(&state.files, context.language())?; - Ok(ResolvedPattern::from_string(capitalize(&string))) + Ok(ResolvedPattern::from_string( + capitalize(&string).to_string(), + )) } fn distinct_fn<'a>( @@ -379,12 +381,14 @@ fn uppercase_fn<'a>( Ok(ResolvedPattern::from_string(string.to_uppercase())) } -fn capitalize(s: &str) -> String { - let mut chars = s.chars(); - match chars.next() { - None => String::new(), - Some(c) => c.to_uppercase().collect::() + chars.as_str(), +fn capitalize(s: &str) -> Cow { + if let Some(first_char) = s.chars().next() { + if !first_char.is_uppercase() { + let rest = &s[first_char.len_utf8()..]; + return Cow::Owned(first_char.to_ascii_uppercase().to_string() + rest); + } } + Cow::Borrowed(s) } fn resolve<'a>(target_path: Cow<'a, str>, from_file: Cow<'a, str>) -> Result { diff --git a/crates/biome_html_formatter/src/utils/children.rs b/crates/biome_html_formatter/src/utils/children.rs index 6af5a2ba1aac..44a783dfce9e 100644 --- a/crates/biome_html_formatter/src/utils/children.rs +++ b/crates/biome_html_formatter/src/utils/children.rs @@ -11,7 +11,7 @@ use biome_rowan::{SyntaxResult, TextLen, TextRange, TextSize, TokenText}; use crate::{comments::HtmlComments, context::HtmlFormatContext, HtmlFormatter}; -pub(crate) static HTML_WHITESPACE_CHARS: [char; 4] = [' ', '\n', '\t', '\r']; +pub(crate) static HTML_WHITESPACE_CHARS: [u8; 4] = [b' ', b'\n', b'\t', b'\r']; /// Meaningful HTML text is defined to be text that has either non-whitespace /// characters, or does not contain a newline. Whitespace is defined as ASCII @@ -29,11 +29,11 @@ pub(crate) static HTML_WHITESPACE_CHARS: [char; 4] = [' ', '\n', '\t', '\r']; /// ``` pub fn is_meaningful_html_text(text: &str) -> bool { let mut has_newline = false; - for c in text.chars() { + for byte in text.bytes() { // If there is a non-whitespace character - if !HTML_WHITESPACE_CHARS.contains(&c) { + if !HTML_WHITESPACE_CHARS.contains(&byte) { return true; - } else if c == '\n' { + } else if byte == b'\n' { has_newline = true; } } @@ -191,7 +191,7 @@ where // A text only consisting of whitespace that also contains a new line isn't considered meaningful text. // It can be entirely removed from the content without changing the semantics. let newlines = - whitespace.chars().filter(|c| *c == '\n').count(); + whitespace.bytes().filter(|b| *b == b'\n').count(); // Keep up to one blank line between tags. // ```html diff --git a/crates/biome_js_analyze/src/lint/complexity/no_multiple_spaces_in_regular_expression_literals.rs b/crates/biome_js_analyze/src/lint/complexity/no_multiple_spaces_in_regular_expression_literals.rs index 70b2beb608b4..f7fc16217bb4 100644 --- a/crates/biome_js_analyze/src/lint/complexity/no_multiple_spaces_in_regular_expression_literals.rs +++ b/crates/biome_js_analyze/src/lint/complexity/no_multiple_spaces_in_regular_expression_literals.rs @@ -67,7 +67,6 @@ impl Rule for NoMultipleSpacesInRegularExpressionLiterals { let mut range_list = vec![]; let mut previous_is_space = false; let mut first_consecutive_space_index = 0; - // We use `char_indices` to get the byte index of every character for (i, ch) in trimmed_text.bytes().enumerate() { if ch == b' ' { if !previous_is_space { diff --git a/crates/biome_js_analyze/src/lint/complexity/use_simple_number_keys.rs b/crates/biome_js_analyze/src/lint/complexity/use_simple_number_keys.rs index 680039668146..afff5011e0c0 100644 --- a/crates/biome_js_analyze/src/lint/complexity/use_simple_number_keys.rs +++ b/crates/biome_js_analyze/src/lint/complexity/use_simple_number_keys.rs @@ -102,51 +102,48 @@ impl TryFrom for NumberLiteral { }; match token.kind() { JsSyntaxKind::JS_NUMBER_LITERAL | JsSyntaxKind::JS_BIGINT_LITERAL => { - let chars: Vec = token.text_trimmed().chars().collect(); + let text = token.text_trimmed(); let mut value = String::new(); let mut is_first_char_zero: bool = false; - let mut is_second_char_a_letter: Option = None; + let mut is_second_char_a_letter: Option = None; let mut contains_dot: bool = false; let mut exponent: bool = false; - let mut largest_digit: char = '0'; + let mut largest_digit: u8 = b'0'; let mut underscore: bool = false; let mut big_int: bool = false; - for i in 0..chars.len() { - if i == 0 && chars[i] == '0' && chars.len() > 1 { - is_first_char_zero = true; - continue; + for (i, b) in text.bytes().enumerate() { + match b { + b'0' if i == 0 && text.len() > 1 => { + is_first_char_zero = true; + continue; + } + b'n' => { + big_int = true; + break; + } + b'e' | b'E' => { + exponent = true; + } + b'_' => { + underscore = true; + continue; + } + b'.' => { + contains_dot = true; + } + b if i == 1 && b.is_ascii_alphabetic() => { + is_second_char_a_letter = Some(b); + continue; + } + _ => { + if largest_digit < b { + largest_digit = b; + } + } } - - if chars[i] == 'n' { - big_int = true; - break; - } - - if chars[i] == 'e' || chars[i] == 'E' { - exponent = true; - } - - if i == 1 && chars[i].is_alphabetic() && !exponent { - is_second_char_a_letter = Some(chars[i]); - continue; - } - - if chars[i] == '_' { - underscore = true; - continue; - } - - if chars[i] == '.' { - contains_dot = true; - } - - if largest_digit < chars[i] { - largest_digit = chars[i]; - } - - value.push(chars[i]) + value.push(b as char); } if contains_dot { @@ -167,21 +164,21 @@ impl TryFrom for NumberLiteral { }; match is_second_char_a_letter { - Some('b' | 'B') => { + Some(b'b' | b'B') => { return Ok(Self::Binary { node: literal_member_name, value, big_int, }) } - Some('o' | 'O') => { + Some(b'o' | b'O') => { return Ok(Self::Octal { node: literal_member_name, value, big_int, }) } - Some('x' | 'X') => { + Some(b'x' | b'X') => { return Ok(Self::Hexadecimal { node: literal_member_name, value, @@ -191,7 +188,7 @@ impl TryFrom for NumberLiteral { _ => (), } - if largest_digit < '8' { + if largest_digit < b'8' { return Ok(Self::Octal { node: literal_member_name, value, diff --git a/crates/biome_js_analyze/src/lint/correctness/no_empty_character_class_in_regex.rs b/crates/biome_js_analyze/src/lint/correctness/no_empty_character_class_in_regex.rs index 3ad0d7f0e481..6744b85a9be7 100644 --- a/crates/biome_js_analyze/src/lint/correctness/no_empty_character_class_in_regex.rs +++ b/crates/biome_js_analyze/src/lint/correctness/no_empty_character_class_in_regex.rs @@ -65,7 +65,6 @@ impl Rule for NoEmptyCharacterClassInRegex { let trimmed_text = pattern.text(); let mut class_start_index = None; let mut is_negated_class = false; - // We use `char_indices` to get the byte index of every character let mut enumerated_char_iter = trimmed_text.bytes().enumerate(); while let Some((i, ch)) = enumerated_char_iter.next() { match ch { diff --git a/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs b/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs index 6011e23b0f8b..b3aee6329d47 100644 --- a/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs +++ b/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs @@ -146,7 +146,7 @@ fn get_utility_info( // `gap-x-4`, and there are targets like `gap-` and `gap-x-`, we want to // make sure that the `gap-x-` target is matched as it is more specific, // regardless of the order in which the targets are defined. - let target_size = target.chars().count(); + let target_size = target.len(); if target_size > last_size { layer = Some(layer_data.name); match_index = index; @@ -305,26 +305,25 @@ impl From<(&str, &str)> for VariantMatch { return VariantMatch::Exact; }; - let variant_chars = variant_text.chars(); - let mut target_chars = target.chars(); + let mut target_chars = target.bytes(); let mut target_found = true; let mut dash_found = false; let mut bracket_found = false; // Checks if variant text has a custom value thus it starts with the target and it's followed by "-[" - for char in variant_chars { - match (char, target_chars.next()) { - (_, Some(target_char)) => { - if target_char != char { + for byte in variant_text.bytes() { + match (byte, target_chars.next()) { + (_, Some(target_byte)) => { + if target_byte != byte { target_found = false; break; } } - ('-', None) => { + (b'-', None) => { if target_found { dash_found = true; } } - ('[', None) => { + (b'[', None) => { if target_found && dash_found { bracket_found = true; } @@ -414,7 +413,7 @@ fn find_variant_position(config_variants: VariantsConfig, variant_text: &str) -> // `group-aria-[.custom-class]`, and there are targets like `group` and `group-aria`, we want to // make sure that the `group-aria` target is matched as it is more specific, // so when the target is `group` a Partial match will occur. - let target_size = target.chars().count(); + let target_size = target.len(); if target_size > last_size { variant = Some(target); match_index = index; diff --git a/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_lexer.rs b/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_lexer.rs index 813f7f5602da..e900f6bfa9ea 100644 --- a/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_lexer.rs +++ b/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_lexer.rs @@ -104,12 +104,12 @@ pub fn tokenize_class(class_name: &str) -> Option { let mut last_char = CharKind::Other; let mut delimiter_indexes: Vec = Vec::new(); - for (index, c) in class_name.char_indices() { + for (index, byte) in class_name.bytes().enumerate() { let mut next_last_char = CharKind::Other; let mut is_start_of_arbitrary_block = false; - match c { - '[' => { + match byte { + b'[' => { if arbitrary_block_depth == 0 { arbitrary_block_depth = 1; at_arbitrary_block_start = true; @@ -118,24 +118,24 @@ pub fn tokenize_class(class_name: &str) -> Option { arbitrary_block_depth += 1; } } - '\'' | '"' | '`' => { + b'\'' | b'"' | b'`' => { if at_arbitrary_block_start { - quoted_arbitrary_block_type = Quote::from_char(c); + quoted_arbitrary_block_type = Quote::from_char(byte as char); } else if let CharKind::Backslash = last_char { // Escaped, ignore. } else { - let quote = Quote::from_char(c)?; + let quote = Quote::from_char(byte as char)?; next_last_char = CharKind::Quote(quote); } } - '\\' => { + b'\\' => { if let CharKind::Backslash = last_char { // Consider escaped backslashes as other characters. } else { next_last_char = CharKind::Backslash; } } - ']' => { + b']' => { if arbitrary_block_depth > 0 { match "ed_arbitrary_block_type { // If in quoted arbitrary block... @@ -159,7 +159,7 @@ pub fn tokenize_class(class_name: &str) -> Option { return None; } } - ':' => { + b':' => { if arbitrary_block_depth == 0 { delimiter_indexes.push(index); } diff --git a/crates/biome_js_analyze/src/lint/style/no_unused_template_literal.rs b/crates/biome_js_analyze/src/lint/style/no_unused_template_literal.rs index aaa3e4e44c7a..9d368c891f77 100644 --- a/crates/biome_js_analyze/src/lint/style/no_unused_template_literal.rs +++ b/crates/biome_js_analyze/src/lint/style/no_unused_template_literal.rs @@ -126,8 +126,8 @@ fn can_convert_to_string_literal(node: &JsTemplateExpression) -> bool { // if token text has any special character token .text() - .chars() - .any(|ch| matches!(ch, '\n' | '\'' | '"')) + .bytes() + .any(|byte| matches!(byte, b'\n' | b'\'' | b'"')) } Err(_) => { // if we found an error, then just return `true`, which means that this template literal can't be converted to diff --git a/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs b/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs index 3f30f3af0e60..9bfafd14293b 100644 --- a/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs +++ b/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs @@ -167,8 +167,8 @@ impl Rule for UseFilenamingConvention { }; if !name.ends_with(ends) || !name[..name.len() - count] - .chars() - .all(|c| c.is_alphanumeric() || matches!(c, '-' | '_')) + .bytes() + .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_')) { return Some(FileNamingConventionState::Filename); } diff --git a/crates/biome_js_analyze/src/lint/style/use_nodejs_import_protocol.rs b/crates/biome_js_analyze/src/lint/style/use_nodejs_import_protocol.rs index 77cf8cfe8bba..bc84f32cdf5d 100644 --- a/crates/biome_js_analyze/src/lint/style/use_nodejs_import_protocol.rs +++ b/crates/biome_js_analyze/src/lint/style/use_nodejs_import_protocol.rs @@ -99,11 +99,11 @@ impl Rule for UseNodejsImportProtocol { module_name.kind() == JsSyntaxKind::JS_STRING_LITERAL, "The module name token should be a string literal." ); - let delimiter = module_name.text_trimmed().chars().nth(0)?; + let str_delimiter = (*module_name.text_trimmed().as_bytes().first()?) as char; let module_inner_name = inner_string_text(module_name); let new_module_name = JsSyntaxToken::new_detached( JsSyntaxKind::JS_STRING_LITERAL, - &format!("{delimiter}node:{module_inner_name}{delimiter}"), + &format!("{str_delimiter}node:{module_inner_name}{str_delimiter}"), [], [], ); diff --git a/crates/biome_js_analyze/src/lint/suspicious/no_misleading_character_class.rs b/crates/biome_js_analyze/src/lint/suspicious/no_misleading_character_class.rs index 46a0639701ab..9a377b170659 100644 --- a/crates/biome_js_analyze/src/lint/suspicious/no_misleading_character_class.rs +++ b/crates/biome_js_analyze/src/lint/suspicious/no_misleading_character_class.rs @@ -425,30 +425,41 @@ fn make_suggestion( }) } -fn is_emoji_modifier(code: u32) -> bool { - (0x1F3FB..=0x1F3FF).contains(&code) +fn is_emoji_modifier(c: char) -> bool { + (0x1F3FB..=0x1F3FF).contains(&(c as u32)) } fn has_emoji_modifier(chars: &str) -> bool { - let char_vec: Vec = chars.chars().collect(); - - char_vec.iter().enumerate().any(|(i, &c)| { - i != 0 && is_emoji_modifier(c as u32) && !is_emoji_modifier(char_vec[i - 1] as u32) - }) + let mut prev_is_emoji_modifier = true; + for c in chars.chars() { + if is_emoji_modifier(c) { + if !prev_is_emoji_modifier { + return true; + } + prev_is_emoji_modifier = true; + } else { + prev_is_emoji_modifier = false; + } + } + false } -fn is_regional_indicator_symbol(code: u32) -> bool { - (0x1F1E6..=0x1F1FF).contains(&code) +fn is_regional_indicator_symbol(c: char) -> bool { + (0x1F1E6..=0x1F1FF).contains(&(c as u32)) } fn has_regional_indicator_symbol(chars: &str) -> bool { - let char_vec: Vec = chars.chars().collect(); - - char_vec.iter().enumerate().any(|(i, &c)| { - i != 0 - && is_regional_indicator_symbol(c as u32) - && is_regional_indicator_symbol(char_vec[i - 1] as u32) - }) + let mut iter = chars.chars(); + while let Some(c) = iter.next() { + if is_regional_indicator_symbol(c) { + if let Some(c) = iter.next() { + if is_regional_indicator_symbol(c) { + return true; + } + } + } + } + false } fn is_combining_character(ch: char) -> bool { diff --git a/crates/biome_js_analyze/src/suppression_action.rs b/crates/biome_js_analyze/src/suppression_action.rs index b86f20ee5532..c040b423bdb6 100644 --- a/crates/biome_js_analyze/src/suppression_action.rs +++ b/crates/biome_js_analyze/src/suppression_action.rs @@ -13,12 +13,12 @@ use biome_rowan::{AstNode, BatchMutation, TriviaPieceKind}; /// This new element will serve as trailing "newline" for the suppression comment. fn make_indentation_from_jsx_element(current_element: &JsxText) -> JsxText { if let Ok(text) = current_element.value_token() { - let chars = text.text().chars(); + let bytes = text.text().bytes(); let mut newlines = 0; let mut spaces = 0; let mut string_found = false; - for char in chars { - if char == '\"' { + for byte in bytes { + if byte == b'\"' { if string_found { string_found = false; } else { @@ -30,10 +30,10 @@ fn make_indentation_from_jsx_element(current_element: &JsxText) -> JsxText { continue; } - if matches!(char, '\r' | '\n') { + if matches!(byte, b'\r' | b'\n') { newlines += 1; } - if matches!(char, ' ') && newlines == 1 && !string_found { + if matches!(byte, b' ') && newlines == 1 && !string_found { spaces += 1; } } diff --git a/crates/biome_js_factory/src/make.rs b/crates/biome_js_factory/src/make.rs index 5d44d73f6fc3..63139e5dfe59 100644 --- a/crates/biome_js_factory/src/make.rs +++ b/crates/biome_js_factory/src/make.rs @@ -60,7 +60,7 @@ pub fn jsx_string_literal_single_quotes(text: &str) -> JsSyntaxToken { pub fn js_template_chunk(text: &str) -> JsSyntaxToken { JsSyntaxToken::new_detached( JsSyntaxKind::TEMPLATE_CHUNK, - &utils::escape(text, &["${", "`"], '\\'), + &utils::escape(text, &["${", "`"], b'\\'), [], [], ) diff --git a/crates/biome_js_factory/src/utils.rs b/crates/biome_js_factory/src/utils.rs index 1fa07e1fcff9..e47e71cabd0b 100644 --- a/crates/biome_js_factory/src/utils.rs +++ b/crates/biome_js_factory/src/utils.rs @@ -15,15 +15,19 @@ pub fn escape<'a>( unescaped_string: &'a str, needs_escaping: &[&str], - escaping_char: char, + escaping_char: u8, ) -> std::borrow::Cow<'a, str> { debug_assert!(!needs_escaping.is_empty()); + debug_assert!( + escaping_char.is_ascii(), + "escaping_char must be a valid ASCII character." + ); let mut escaped = String::new(); - let mut iter = unescaped_string.char_indices(); + let mut iter = unescaped_string.bytes().enumerate(); let mut last_copied_idx = 0; - while let Some((idx, chr)) = iter.next() { - if chr == escaping_char { - // The next character is esacaped + while let Some((idx, byte)) = iter.next() { + if byte == escaping_char { + // The next character is escaped iter.next(); } else { for candidate in needs_escaping { @@ -32,9 +36,9 @@ pub fn escape<'a>( escaped = String::with_capacity(unescaped_string.len() * 2 - idx); } escaped.push_str(&unescaped_string[last_copied_idx..idx]); - escaped.push(escaping_char); + escaped.push(escaping_char as char); escaped.push_str(candidate); - for _ in candidate.chars().skip(1) { + for _ in candidate.bytes().skip(1) { iter.next(); } last_copied_idx = idx + candidate.len(); @@ -57,32 +61,32 @@ mod tests { #[test] fn ok_escape_dollar_signs_and_backticks() { - assert_eq!(escape("abc", &["${"], '\\'), "abc"); - assert_eq!(escape("abc", &["`"], '\\'), "abc"); - assert_eq!(escape(r"abc\", &["`"], '\\'), r"abc\"); - assert_eq!(escape("abc $ bca", &["${"], '\\'), "abc $ bca"); - assert_eq!(escape("abc ${a} bca", &["${"], '\\'), r"abc \${a} bca"); + assert_eq!(escape("abc", &["${"], b'\\'), "abc"); + assert_eq!(escape("abc", &["`"], b'\\'), "abc"); + assert_eq!(escape(r"abc\", &["`"], b'\\'), r"abc\"); + assert_eq!(escape("abc $ bca", &["${"], b'\\'), "abc $ bca"); + assert_eq!(escape("abc ${a} bca", &["${"], b'\\'), r"abc \${a} bca"); assert_eq!( - escape("abc ${} ${} bca", &["${"], '\\'), + escape("abc ${} ${} bca", &["${"], b'\\'), r"abc \${} \${} bca" ); - assert_eq!(escape(r"\`", &["`"], '\\'), r"\`"); - assert_eq!(escape(r"\${}", &["${"], '\\'), r"\${}"); - assert_eq!(escape(r"\\`", &["`"], '\\'), r"\\\`"); - assert_eq!(escape(r"\\${}", &["${"], '\\'), r"\\\${}"); - assert_eq!(escape(r"\\\`", &["`"], '\\'), r"\\\`"); - assert_eq!(escape(r"\\\${}", &["${"], '\\'), r"\\\${}"); + assert_eq!(escape(r"\`", &["`"], b'\\'), r"\`"); + assert_eq!(escape(r"\${}", &["${"], b'\\'), r"\${}"); + assert_eq!(escape(r"\\`", &["`"], b'\\'), r"\\\`"); + assert_eq!(escape(r"\\${}", &["${"], b'\\'), r"\\\${}"); + assert_eq!(escape(r"\\\`", &["`"], b'\\'), r"\\\`"); + assert_eq!(escape(r"\\\${}", &["${"], b'\\'), r"\\\${}"); - assert_eq!(escape("abc", &["${", "`"], '\\'), "abc"); - assert_eq!(escape("${} `", &["${", "`"], '\\'), r"\${} \`"); + assert_eq!(escape("abc", &["${", "`"], b'\\'), "abc"); + assert_eq!(escape("${} `", &["${", "`"], b'\\'), r"\${} \`"); assert_eq!( - escape(r"abc \${a} \`bca", &["${", "`"], '\\'), + escape(r"abc \${a} \`bca", &["${", "`"], b'\\'), r"abc \${a} \`bca" ); - assert_eq!(escape(r"abc \${bca}", &["${", "`"], '\\'), r"abc \${bca}"); - assert_eq!(escape(r"abc \`bca", &["${", "`"], '\\'), r"abc \`bca"); + assert_eq!(escape(r"abc \${bca}", &["${", "`"], b'\\'), r"abc \${bca}"); + assert_eq!(escape(r"abc \`bca", &["${", "`"], b'\\'), r"abc \`bca"); - assert_eq!(escape(r"\n`", &["`"], '\\'), r"\n\`"); + assert_eq!(escape(r"\n`", &["`"], b'\\'), r"\n\`"); } } diff --git a/crates/biome_js_formatter/src/js/expressions/regex_literal_expression.rs b/crates/biome_js_formatter/src/js/expressions/regex_literal_expression.rs index 2b17d146be1b..890bf0796c38 100644 --- a/crates/biome_js_formatter/src/js/expressions/regex_literal_expression.rs +++ b/crates/biome_js_formatter/src/js/expressions/regex_literal_expression.rs @@ -25,7 +25,7 @@ impl FormatNodeRule for FormatJsRegexLiteralExpression let end_slash_pos = trimmed_raw_string.rfind('/').unwrap(); let mut flag_char_vec = trimmed_raw_string[end_slash_pos + 1..] .chars() - .collect::>(); + .collect::>(); flag_char_vec.sort_unstable(); let sorted_flag_string = flag_char_vec.iter().collect::(); diff --git a/crates/biome_js_formatter/src/js/lists/template_element_list.rs b/crates/biome_js_formatter/src/js/lists/template_element_list.rs index 185c14c145ee..8bc18293d2e7 100644 --- a/crates/biome_js_formatter/src/js/lists/template_element_list.rs +++ b/crates/biome_js_formatter/src/js/lists/template_element_list.rs @@ -190,9 +190,9 @@ impl TemplateElementIndention { let tab_width: u32 = u8::from(tab_width).into(); let mut size: u32 = 0; - for c in after_new_line.chars() { - match c { - '\t' => { + for byte in after_new_line.bytes() { + match byte { + b'\t' => { // Tabs behave in a way that they are aligned to the nearest // multiple of tab_width: // number of spaces -> added size @@ -201,7 +201,7 @@ impl TemplateElementIndention { // Or in other words, it clips the size to the next multiple of tab width. size = size + tab_width - (size % tab_width); } - ' ' => { + b' ' => { size += 1; } _ => break, diff --git a/crates/biome_js_formatter/src/utils/jsx.rs b/crates/biome_js_formatter/src/utils/jsx.rs index 5bb7636e499b..a76a896fe149 100644 --- a/crates/biome_js_formatter/src/utils/jsx.rs +++ b/crates/biome_js_formatter/src/utils/jsx.rs @@ -10,7 +10,7 @@ use biome_rowan::{Direction, SyntaxResult, TextRange, TextSize, TokenText}; use std::iter::{FusedIterator, Peekable}; use std::str::Chars; -pub(crate) static JSX_WHITESPACE_CHARS: [char; 4] = [' ', '\n', '\t', '\r']; +pub(crate) static JSX_WHITESPACE_CHARS: [u8; 4] = [b' ', b'\n', b'\t', b'\r']; /// Meaningful JSX text is defined to be text that has either non-whitespace /// characters, or does not contain a newline. Whitespace is defined as ASCII @@ -28,11 +28,11 @@ pub(crate) static JSX_WHITESPACE_CHARS: [char; 4] = [' ', '\n', '\t', '\r']; /// ``` pub fn is_meaningful_jsx_text(text: &str) -> bool { let mut has_newline = false; - for c in text.chars() { + for byte in text.bytes() { // If there is a non-whitespace character - if !JSX_WHITESPACE_CHARS.contains(&c) { + if !JSX_WHITESPACE_CHARS.contains(&byte) { return true; - } else if c == '\n' { + } else if byte == b'\n' { has_newline = true; } } @@ -242,7 +242,7 @@ where // A text only consisting of whitespace that also contains a new line isn't considered meaningful text. // It can be entirely removed from the content without changing the semantics. let newlines = - whitespace.chars().filter(|c| *c == '\n').count(); + whitespace.bytes().filter(|b| *b == b'\n').count(); // Keep up to one blank line between tags/expressions and text. // ```javascript diff --git a/crates/biome_js_formatter/src/utils/member_chain/mod.rs b/crates/biome_js_formatter/src/utils/member_chain/mod.rs index 8efc3a869497..27857df27fb4 100644 --- a/crates/biome_js_formatter/src/utils/member_chain/mod.rs +++ b/crates/biome_js_formatter/src/utils/member_chain/mod.rs @@ -586,11 +586,11 @@ fn has_simple_arguments(call: &JsCallExpression) -> bool { fn is_factory(token: &JsSyntaxToken) -> bool { let text = token.text_trimmed(); - let mut chars = text.chars(); + let mut bytes = text.bytes(); match text.chars().next() { // Any sequence of '$' or '_' characters - Some('_' | '$') => chars.all(|c| matches!(c, '_' | '$')), + Some('_' | '$') => bytes.all(|b| matches!(b, b'_' | b'$')), Some(c) => c.is_uppercase(), _ => false, } diff --git a/crates/biome_js_formatter/src/utils/string_utils.rs b/crates/biome_js_formatter/src/utils/string_utils.rs index aabf0762e1fd..0e3ef1a5eaf3 100644 --- a/crates/biome_js_formatter/src/utils/string_utils.rs +++ b/crates/biome_js_formatter/src/utils/string_utils.rs @@ -281,9 +281,9 @@ impl<'token> LiteralStringNormaliser<'token> { let text_to_check = self.raw_content(); if text_to_check - .chars() + .bytes() .next() - .map_or(false, |c| c.is_ascii_digit()) + .map_or(false, |b| b.is_ascii_digit()) { if let Ok(parsed) = text_to_check.parse::() { // In TypeScript, numbers like members have different meaning from numbers. diff --git a/crates/biome_js_parser/src/syntax/expr.rs b/crates/biome_js_parser/src/syntax/expr.rs index 49ff6937292b..cb4c31498f8d 100644 --- a/crates/biome_js_parser/src/syntax/expr.rs +++ b/crates/biome_js_parser/src/syntax/expr.rs @@ -254,10 +254,9 @@ pub(crate) fn parse_number_literal_expression(p: &mut JsParser) -> ParsedSyntax if p.state().strict().is_some() && cur_src.starts_with('0') && cur_src - .chars() - .nth(1) - .filter(|c| c.is_ascii_digit()) - .is_some() + .as_bytes() + .get(1) + .is_some_and(|b| b.is_ascii_digit()) { let err_msg = if cur_src.contains(['8', '9']) { "Decimals with leading zeros are not allowed in strict mode." diff --git a/crates/biome_js_parser/src/syntax/jsx/mod.rs b/crates/biome_js_parser/src/syntax/jsx/mod.rs index 19ae4e6964cc..690756b52276 100644 --- a/crates/biome_js_parser/src/syntax/jsx/mod.rs +++ b/crates/biome_js_parser/src/syntax/jsx/mod.rs @@ -434,7 +434,7 @@ fn parse_jsx_any_element_name(p: &mut JsParser) -> ParsedSyntax { /// Tests if this is an intrinsic element name. Intrinsic elements are such elements /// that are built in, for example HTML elements. This implementation uses React's semantic /// and assumes that anything starting with a lower case character is an intrinsic element, and -/// that custom components start with an uper case character. +/// that custom components start with an upper case character. /// /// Resources: [TypeScript's documentation on intrinsic elements](https://www.typescriptlang.org/docs/handbook/jsx.html#intrinsic-elements) fn is_intrinsic_element(element_name: &str) -> bool { diff --git a/crates/biome_js_semantic/src/tests/assertions.rs b/crates/biome_js_semantic/src/tests/assertions.rs index 0a39654ded5a..736e64a7195e 100644 --- a/crates/biome_js_semantic/src/tests/assertions.rs +++ b/crates/biome_js_semantic/src/tests/assertions.rs @@ -793,25 +793,18 @@ fn show_unmatched_assertion( ) { let assertion_code = &code[assertion_range]; - // eat all trivia at the start + // eat all trivia at the start and at the end let mut start: usize = assertion_range.start().into(); - for chr in assertion_code.chars() { - if chr.is_ascii_whitespace() { - start += chr.len_utf8(); - } else { - break; - } - } - - // eat all trivia at the end + start += assertion_code + .bytes() + .take_while(u8::is_ascii_whitespace) + .count(); let mut end: usize = assertion_range.end().into(); - for chr in assertion_code.chars().rev() { - if chr.is_ascii_whitespace() { - end -= chr.len_utf8(); - } else { - break; - } - } + end -= assertion_code + .bytes() + .rev() + .take_while(u8::is_ascii_whitespace) + .count(); let diagnostic = TestSemanticDiagnostic::new( format!("This assertion was not matched: {assertion:?}"), @@ -850,27 +843,19 @@ fn show_all_events( for e in all_events { let diagnostic = match e { SemanticEvent::ScopeStarted { range, .. } => { - let mut start: usize = range.start().into(); let code = &code[range]; - for chr in code.chars() { - if chr.is_ascii_whitespace() { - start += chr.len_utf8(); - } else { - break; - } - } + let mut start: usize = range.start().into(); + start += code.bytes().take_while(|b| b.is_ascii_whitespace()).count(); TestSemanticDiagnostic::new(format!("{e:?}"), start..start + 1) } SemanticEvent::ScopeEnded { range, .. } => { - let mut start: usize = range.end().into(); let code = &code[range]; - for chr in code.chars().rev() { - if chr.is_ascii_whitespace() { - start -= chr.len_utf8(); - } else { - break; - } - } + let mut start: usize = range.end().into(); + start -= code + .bytes() + .rev() + .take_while(|b| b.is_ascii_whitespace()) + .count(); TestSemanticDiagnostic::new(format!("{e:?}"), start - 1..start) } _ => TestSemanticDiagnostic::new(format!("{e:?}"), e.range()), diff --git a/crates/biome_js_syntax/src/numbers.rs b/crates/biome_js_syntax/src/numbers.rs index 6506d28fd928..934430ff7e34 100644 --- a/crates/biome_js_syntax/src/numbers.rs +++ b/crates/biome_js_syntax/src/numbers.rs @@ -16,17 +16,16 @@ pub fn split_into_radix_and_number(num: &str) -> (u8, Cow) { } fn parse_js_number_prefix(num: &str) -> Option<(u8, &str)> { - let mut chars = num.chars(); - let c = chars.next()?; - if c != '0' { + let mut bytes = num.bytes(); + if bytes.next()? != b'0' { return None; } - Some(match chars.next()? { - 'x' | 'X' => (16, chars.as_str()), - 'o' | 'O' => (8, chars.as_str()), - 'b' | 'B' => (2, chars.as_str()), + Some(match bytes.next()? { + b'x' | b'X' => (16, &num[2..]), + b'o' | b'O' => (8, &num[2..]), + b'b' | b'B' => (2, &num[2..]), // Legacy octal literals - '0'..='7' if !chars.as_str().contains(['8', '9']) => (8, &num[1..]), + b'0'..=b'7' if bytes.all(|b| !matches!(b, b'8' | b'9')) => (8, &num[1..]), _ => return None, }) } diff --git a/crates/biome_lsp/src/converters/mod.rs b/crates/biome_lsp/src/converters/mod.rs index 8400a5f7a050..01ebfb6bd482 100644 --- a/crates/biome_lsp/src/converters/mod.rs +++ b/crates/biome_lsp/src/converters/mod.rs @@ -159,7 +159,7 @@ mod tests { let mut lin_col = LineCol { line: 0, col: 0 }; let mut col_utf16 = 0; let mut col_utf32 = 0; - for (offset, c) in text.char_indices() { + for (offset, char) in text.char_indices() { let got_offset = line_index.offset(lin_col).unwrap(); assert_eq!(usize::from(got_offset), offset); @@ -178,14 +178,14 @@ mod tests { assert_eq!(wide_lin_col.col, want_col) } - if c == '\n' { + if char == '\n' { lin_col.line += 1; lin_col.col = 0; col_utf16 = 0; col_utf32 = 0; } else { - lin_col.col += c.len_utf8() as u32; - col_utf16 += c.len_utf16() as u32; + lin_col.col += char.len_utf8() as u32; + col_utf16 += char.len_utf16() as u32; col_utf32 += 1; } } diff --git a/crates/biome_parser/src/diagnostic.rs b/crates/biome_parser/src/diagnostic.rs index 9e95835e82ce..59e7ae6b75a6 100644 --- a/crates/biome_parser/src/diagnostic.rs +++ b/crates/biome_parser/src/diagnostic.rs @@ -440,8 +440,8 @@ pub fn expect_one_of(names: &[&str], range: TextRange) -> ParseDiagnostic { } fn article_for(name: &str) -> &'static str { - match name.chars().next() { - Some('a' | 'e' | 'i' | 'o' | 'u') => "an", + match name.bytes().next() { + Some(b'a' | b'e' | b'i' | b'o' | b'u') => "an", _ => "a", } } diff --git a/crates/biome_ungrammar/src/lexer.rs b/crates/biome_ungrammar/src/lexer.rs index 0d3e78fb7ba4..a3829842327c 100644 --- a/crates/biome_ungrammar/src/lexer.rs +++ b/crates/biome_ungrammar/src/lexer.rs @@ -53,7 +53,7 @@ impl Location { fn advance(&mut self, text: &str) { match text.rfind('\n') { Some(idx) => { - self.line += text.chars().filter(|&it| it == '\n').count(); + self.line += text.bytes().filter(|byte| *byte == b'\n').count(); self.column = text[idx + 1..].chars().count(); } None => self.column += text.chars().count(),