diff --git a/CHANGELOG.md b/CHANGELOG.md index f70bbb2d78ce..d75e4e1d3d2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,10 +78,13 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b #### New features - Add [noTemplateCurlyInString](https://biomejs.dev/linter/rules/no-template-curly-in-string/). Contributed by @fireairforce + - Add [NoOctalEscape](https://biomejs.dev/linter/rules/no-octal-escape/). Contributed by @fireairforce #### Bug fixes +- [noControlCharactersInRegex](https://www.biomejs.dev/linter/rules/no-control-characters-in-regex) no longer panics on regexes with incomplete escape sequences. Contributed by @Conaclos + - [noMisleadingCharacterClass](https://biomejs.dev/linter/rules/no-misleading-character-class/) no longer reports issues outside of character classes. The following code is no longer reported: diff --git a/crates/biome_js_analyze/src/lint/suspicious/no_control_characters_in_regex.rs b/crates/biome_js_analyze/src/lint/suspicious/no_control_characters_in_regex.rs index dd80bb7f51e4..00403108a429 100644 --- a/crates/biome_js_analyze/src/lint/suspicious/no_control_characters_in_regex.rs +++ b/crates/biome_js_analyze/src/lint/suspicious/no_control_characters_in_regex.rs @@ -3,8 +3,8 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_syntax::{ - AnyJsExpression, JsCallArguments, JsCallExpression, JsNewExpression, JsRegexLiteralExpression, - JsStringLiteralExpression, + static_value::StaticValue, AnyJsExpression, JsCallArguments, JsCallExpression, JsNewExpression, + JsRegexLiteralExpression, }; use biome_rowan::{declare_node_union, AstNode, AstSeparatedList, TextRange, TextSize}; use core::str; @@ -69,7 +69,7 @@ declare_lint_rule! { } declare_node_union! { - pub RegexExpressionLike = JsNewExpression | JsCallExpression | JsRegexLiteralExpression + pub AnyRegexExpression = JsNewExpression | JsCallExpression | JsRegexLiteralExpression } fn decode_hex(digits: &[u8]) -> Option { @@ -111,7 +111,7 @@ fn collect_control_characters( }; let hex_index = escaped_index + 1; match c { - b'x' => ( + b'x' if (hex_index + 2) <= bytes.len() => ( decode_hex(&bytes[hex_index..(hex_index + 2)]), hex_index + 2, ), @@ -129,7 +129,7 @@ fn collect_control_characters( ) } } - b'u' => ( + b'u' if (hex_index + 4) <= bytes.len() => ( decode_hex(&bytes[hex_index..(hex_index + 4)]), hex_index + 4, ), @@ -169,31 +169,32 @@ fn collect_control_characters_from_expression( .is_some_and(|name| name.has_name("RegExp")) { let mut args = js_call_arguments.args().iter(); - let Some(js_string_literal) = args + let Some(static_value) = args .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) + .and_then(|arg| arg.ok()?.as_any_js_expression()?.as_static_value()) else { return Default::default(); }; - let Ok(pattern) = js_string_literal.inner_string_text() else { + let Some(pattern) = static_value.as_string_constant() else { return Default::default(); }; - let pattern_start = js_string_literal.range().start() + TextSize::from(1); + let pattern_start = static_value.range().start() + TextSize::from(1); let flags = args .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .map(|js_string_literal| js_string_literal.text()) - .unwrap_or_default(); - collect_control_characters(pattern_start, &pattern, &flags, true).unwrap_or_default() + .and_then(|arg| arg.ok()?.as_any_js_expression()?.as_static_value()); + let flags = if let Some(StaticValue::String(flags)) = &flags { + flags.text() + } else { + "" + }; + collect_control_characters(pattern_start, pattern, flags, true).unwrap_or_default() } else { Vec::new() } } impl Rule for NoControlCharactersInRegex { - type Query = Ast; + type Query = Ast; type State = TextRange; type Signals = Vec; type Options = (); @@ -201,19 +202,19 @@ impl Rule for NoControlCharactersInRegex { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); match node { - RegexExpressionLike::JsNewExpression(new_expr) => { + AnyRegexExpression::JsNewExpression(new_expr) => { let (Ok(callee), Some(args)) = (new_expr.callee(), new_expr.arguments()) else { return Default::default(); }; collect_control_characters_from_expression(&callee, &args) } - RegexExpressionLike::JsCallExpression(call_expr) => { + AnyRegexExpression::JsCallExpression(call_expr) => { let (Ok(callee), Ok(args)) = (call_expr.callee(), call_expr.arguments()) else { return Default::default(); }; collect_control_characters_from_expression(&callee, &args) } - RegexExpressionLike::JsRegexLiteralExpression(regex_literal_expr) => { + AnyRegexExpression::JsRegexLiteralExpression(regex_literal_expr) => { let Ok((pattern, flags)) = regex_literal_expr.decompose() else { return Default::default(); }; 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 e0960fe8533e..0f047eea0324 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 @@ -6,14 +6,15 @@ use biome_analyze::{ use biome_console::markup; use biome_js_factory::make; use biome_js_syntax::{ - global_identifier, AnyJsCallArgument, AnyJsExpression, AnyJsLiteralExpression, - AnyJsTemplateElement, JsCallArguments, JsCallExpression, JsNewExpression, - JsRegexLiteralExpression, JsStringLiteralExpression, JsSyntaxKind, JsSyntaxToken, T, + global_identifier, static_value::StaticValue, AnyJsCallArgument, AnyJsExpression, + AnyJsLiteralExpression, AnyJsTemplateElement, JsCallArguments, JsSyntaxKind, JsSyntaxToken, T, }; use biome_rowan::{ - declare_node_union, AstNode, AstNodeList, AstSeparatedList, BatchMutationExt, TextRange, - TriviaPieceKind, + AstNode, AstNodeList, AstSeparatedList, BatchMutationExt, TextRange, TriviaPieceKind, }; + +use super::no_control_characters_in_regex::AnyRegexExpression; + declare_lint_rule! { /// Disallow characters made with multiple code points in character class syntax. /// @@ -68,10 +69,6 @@ declare_lint_rule! { } } -declare_node_union! { - pub AnyRegexExpression = JsNewExpression | JsCallExpression | JsRegexLiteralExpression -} - pub enum Message { SurrogatePairWithoutUFlag, EmojiModifier, @@ -81,17 +78,31 @@ pub enum Message { } impl Message { - fn as_str(&self) -> &str { + fn diagnostic(&self) -> &str { match self { - Self::CombiningClassOrVs16 => "Unexpected combined character in the character class.", + Self::CombiningClassOrVs16 => "A character class cannot match a character and a combining character.", Self::SurrogatePairWithoutUFlag => { - "Unexpected surrogate pair in character class. Use the 'u' flag." + "A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them." } - Self::EmojiModifier => "Unexpected modified Emoji in the character class. ", + Self::EmojiModifier => "A character class cannot match an emoji with a skin tone modifier.", Self::RegionalIndicatorSymbol => { - "Regional indicator symbol characters should not be used in the character class." + "A character class cannot match a pair of regional indicator symbols." } - Self::JoinedCharSequence => "Unexpected joined character sequence in character class.", + Self::JoinedCharSequence => "A character class cannot match a joined character sequence.", + } + } + + fn note(&self) -> &str { + match self { + Self::CombiningClassOrVs16 => "A character and a combining character forms a new character. Replace the character class with an alternation.", + Self::SurrogatePairWithoutUFlag => { + "A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character." + } + Self::EmojiModifier => "Replace the character class with an alternation.", + Self::RegionalIndicatorSymbol => { + "A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation." + } + Self::JoinedCharSequence => "A zero width joiner composes several emojis into a new one. Replace the character class with an alternation.", } } } @@ -109,67 +120,52 @@ impl Rule for NoMisleadingCharacterClass { fn run(ctx: &RuleContext) -> Self::Signals { let regex = ctx.query(); - match regex { + let (callee, arguments) = match regex { AnyRegexExpression::JsRegexLiteralExpression(expr) => { - let Ok((pattern, flags)) = expr.decompose() else { - return None; - }; - let regex_pattern = pattern.text(); - let range = expr.syntax().text_trimmed_range(); - return diagnostic_regex_pattern(®ex_pattern, flags.text(), range); - } - - AnyRegexExpression::JsNewExpression(expr) => { - if is_regex_expr(expr.callee().ok()?)? { - let mut args = expr.arguments()?.args().iter(); - let regex_pattern = args - .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .and_then(|js_string_literal| js_string_literal.inner_string_text().ok())? - .to_string(); - - let regexp_flags = args - .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .map(|js_string_literal| js_string_literal.text()) - .unwrap_or_default(); - - let range = expr.syntax().text_trimmed_range(); - return diagnostic_regex_pattern(®ex_pattern, ®exp_flags, range); - } + let (pattern, flags) = expr.decompose().ok()?; + let RuleState { range, message } = + diagnostic_regex_pattern(pattern.text(), flags.text(), false)?; + return Some(RuleState { + range: range.checked_add(expr.range().start().checked_add(1.into())?)?, + message, + }); } + AnyRegexExpression::JsNewExpression(expr) => (expr.callee().ok()?, expr.arguments()?), AnyRegexExpression::JsCallExpression(expr) => { - if is_regex_expr(expr.callee().ok()?)? { - let mut args = expr.arguments().ok()?.args().iter(); - let regex_pattern = args - .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .and_then(|js_string_literal| js_string_literal.inner_string_text().ok())? - .to_string(); - - let regexp_flags = args - .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .map(|js_string_literal| js_string_literal.text()) - .unwrap_or_default(); - let range = expr.syntax().text_trimmed_range(); - return diagnostic_regex_pattern(®ex_pattern, ®exp_flags, range); - } + (expr.callee().ok()?, expr.arguments().ok()?) } + }; + if is_regex_expr(callee)? { + let mut args = arguments.args().iter(); + let pattern = args + .next()? + .ok()? + .as_any_js_expression()? + .as_static_value()?; + let pattern_range = pattern.range(); + let pattern = pattern.as_string_constant()?; + let flags = args + .next() + .and_then(|arg| arg.ok()?.as_any_js_expression()?.as_static_value()); + let flags = if let Some(StaticValue::String(flags)) = &flags { + flags.text() + } else { + "" + }; + let RuleState { range, message } = diagnostic_regex_pattern(pattern, flags, true)?; + return Some(RuleState { + range: range.checked_add(pattern_range.start().checked_add(1.into())?)?, + message, + }); } None } fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { - Some(RuleDiagnostic::new( - rule_category!(), - state.range, - state.message.as_str(), - )) + Some( + RuleDiagnostic::new(rule_category!(), state.range, state.message.diagnostic()) + .note(state.message.note()), + ) } fn action(ctx: &RuleContext, state: &Self::State) -> Option { @@ -186,10 +182,8 @@ impl Rule for NoMisleadingCharacterClass { [], [], ); - let mut mutation = ctx.root().begin(); mutation.replace_token(prev_token, next_token); - Some(JsRuleAction::new( ActionCategory::QuickFix, ctx.metadata().applicability(), @@ -198,14 +192,11 @@ impl Rule for NoMisleadingCharacterClass { mutation, )) } - AnyRegexExpression::JsNewExpression(expr) => { let prev_node = expr.arguments()?; let mut prev_args = prev_node.args().iter(); - let regex_pattern = prev_args.next().and_then(|a| a.ok())?; let flag = prev_args.next().and_then(|a| a.ok()); - match make_suggestion(regex_pattern, flag) { Some(suggest) => { let mut mutation = ctx.root().begin(); @@ -221,14 +212,11 @@ impl Rule for NoMisleadingCharacterClass { None => None, } } - AnyRegexExpression::JsCallExpression(expr) => { let prev_node = expr.arguments().ok()?; let mut prev_args = expr.arguments().ok()?.args().iter(); - let regex_pattern = prev_args.next().and_then(|a| a.ok())?; let flag = prev_args.next().and_then(|a| a.ok()); - match make_suggestion(regex_pattern, flag) { Some(suggest) => { let mut mutation = ctx.root().begin(); @@ -271,7 +259,7 @@ fn is_regex_expr(expr: AnyJsExpression) -> Option { fn diagnostic_regex_pattern( regex_pattern: &str, flags: &str, - range: TextRange, + is_in_string: bool, ) -> Option { if flags.contains('v') { return None; @@ -290,12 +278,14 @@ fn diagnostic_regex_pattern( bytes_iter.next(); } b']' => { - let rqw_char_class = ®ex_pattern[i + 1..j]; - let char_class = replace_escaped_unicode(rqw_char_class); - if let Some(diag) = - diagnostic_regex_class(&char_class, has_u_flag, range) + let char_class = ®ex_pattern[i + 1..j]; + if let Some(RuleState { range, message }) = + diagnostic_regex_class(char_class, has_u_flag, is_in_string) { - return Some(diag); + return Some(RuleState { + range: range.checked_add(((i + 1) as u32).into())?, + message, + }); } break; } @@ -309,57 +299,229 @@ fn diagnostic_regex_pattern( None } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum CharType { + CombiningOrVariationSelectorS16, + EmojiModifier, + None, + RegionalIndicator, + Regular, + ZeroWidthJoiner, +} + fn diagnostic_regex_class( char_class: &str, has_u_flag: bool, - range: TextRange, + is_in_string: bool, ) -> Option { - // FIXME: escape handling should be done in th entire class, not only at the start. - let char_class = if char_class.as_bytes().first() == Some(&b'\\') { - let start = 1 + char_class[1..].chars().next()?.len_utf8(); - if start >= char_class.len() { - return None; + let mut prev_char_index = 0; + let mut prev_char_type = CharType::None; + let mut iter = char_class.char_indices(); + while let Some((i, c)) = iter.next() { + let (codepoint, end) = if c == '\\' { + // Maybe unicode esccapes \u{XXX} \uXXXX + let Some((codepoint, len)) = decode_next_codepoint(&char_class[i..], is_in_string) + else { + prev_char_index = i; + continue; + }; + for _ in c.len_utf8()..len { + iter.next(); + } + (codepoint, i + len) + } else { + (c as u32, i + c.len_utf8()) + }; + match codepoint { + // Non-BMP characters are encoded as surrogate pairs in UTF-16 / UCS-2 + 0x10000.. if !has_u_flag => { + return Some(RuleState { + range: TextRange::new((i as u32).into(), (end as u32).into()), + message: Message::SurrogatePairWithoutUFlag, + }); + } + // Combining Diacritical Marks + 0x0300..=0x036F + // Mongolian Free Variation Selector (FVS1 to FVS4) + | 0x180B..=0x180D | 0x180F + // Combining Diacritical Marks Extended + | 0x1AB0..=0x1AFF + // Combining Diacritical Marks Supplement + | 0x1DC0..=0x1DFF + // Combining Diacritical Marks for Symbols + | 0x20D0..=0x20FF + // Variation Selector (VS1 to VS16) + | 0xFE00..=0xFE0F + // Combining Half Marks + | 0xFE20..=0xFE2F + // Variation Selectors Supplement (VS17 to VS256) + | 0xE0100..=0xE01EF => { + if prev_char_type == CharType::Regular { + return Some(RuleState { + range: TextRange::new((prev_char_index as u32).into(), (end as u32).into()), + message: Message::CombiningClassOrVs16, + }); + } + prev_char_type = CharType::CombiningOrVariationSelectorS16; + } + // Regional indicator + 0x1F1E6..=0x1F1FF => { + if matches!(prev_char_type, CharType::RegionalIndicator) { + return Some(RuleState { + range: TextRange::new((prev_char_index as u32).into(), (end as u32).into()), + message: Message::RegionalIndicatorSymbol, + }); + } + prev_char_type = CharType::RegionalIndicator; + } + // Emoji skin modifier + 0x1F3FB..=0x1F3FF => { + if prev_char_type == CharType::Regular { + return Some(RuleState { + range: TextRange::new((prev_char_index as u32).into(), (end as u32).into()), + message: Message::EmojiModifier, + }); + } + prev_char_type = CharType::EmojiModifier; + } + // Zero Width Joiner (used to combine emoji) + 0x200D => { + if + !matches!(prev_char_type, CharType::None | CharType::ZeroWidthJoiner) + && end < char_class.len() + { + if let Some((c, len)) = decode_next_codepoint(&char_class[end..], is_in_string) { + if c != 0x200D { + return Some(RuleState { + range: TextRange::new((prev_char_index as u32).into(), ((end + len) as u32).into()), + message: Message::JoinedCharSequence, + }); + } + } + } + prev_char_type = CharType::ZeroWidthJoiner; + } + _ => { + prev_char_type = CharType::Regular; + } } - &char_class[start..] - } else { - char_class - }; - - if !has_u_flag && has_surrogate_pair(char_class) { - return Some(RuleState { - range, - message: Message::SurrogatePairWithoutUFlag, - }); + prev_char_index = i; } + None +} - if has_combining_class_or_vs16(char_class) { - return Some(RuleState { - range, - message: Message::CombiningClassOrVs16, - }); - } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum UnicodeEscapeKind { + // "\u{XXX}" + // "\uXXXX" + String, + // /\u{XXX}/ and "\\u{XXX}" + RegexBraced, + // /\uXXX/ and "\\uXXX" + RegexPlain, +} - if has_regional_indicator_symbol(char_class) { - return Some(RuleState { - range, - message: Message::RegionalIndicatorSymbol, - }); - } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct UnicodeEscape { + codepoint: u32, + kind: UnicodeEscapeKind, + len: usize, +} - if has_emoji_modifier(char_class) { - return Some(RuleState { - range, - message: Message::EmojiModifier, - }); +/// Convert unicode escape sequence string to unicode character +/// - unicode escape sequences: \u{XXXX} +/// - unicode escape sequences without parenthesis: \uXXXX +/// - surrogate pair: \uXXXX\uXXXX +/// If the unicode escape sequence is not valid, it will be treated as a simple string. +/// +/// ```example +/// \uD83D\uDC4D -> πŸ‘ +/// \u0041\u0301 -> Á +/// \uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66 -> πŸ‘¨β€πŸ‘©β€πŸ‘¦ +/// \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466} -> πŸ‘¨β€πŸ‘©β€πŸ‘¦ +/// \u899\uD83D\uDC4D -> \u899πŸ‘ +/// ```` +fn decode_next_codepoint(char_class: &str, is_in_string: bool) -> Option<(u32, usize)> { + let c = char_class.chars().next()?; + // `\u{XXX}` + // `\uXXXX` + let Some(UnicodeEscape { + kind, + codepoint, + len, + }) = decode_unicode_escape_sequence(char_class, is_in_string) + else { + // Ignore the escape sequence + return Some((c as u32, c.len_utf8())); + }; + if kind != UnicodeEscapeKind::RegexBraced + && matches!(codepoint, 0xD800..=0xDBFF) + && len <= char_class.len() + { + if let Some(UnicodeEscape { + kind: low_kind, + codepoint: low_codepoint, + len: low_len, + }) = decode_unicode_escape_sequence(&char_class[len..], is_in_string) + { + let (final_codepoint, final_len) = if kind == low_kind + && matches!(low_codepoint, 0xDC00..=0xDFFF) + { + let surrogate = ((codepoint - 0xD800) << 10) + (low_codepoint - 0xDC00) + 0x10000; + (surrogate, len + low_len) + } else { + (codepoint, len) + }; + Some((final_codepoint, final_len)) + } else { + Some((codepoint, len)) + } + } else { + Some((codepoint, len)) } +} - if zwj(char_class) { - return Some(RuleState { - range, - message: Message::JoinedCharSequence, - }); +fn decode_unicode_escape_sequence(s: &str, is_in_string: bool) -> Option { + let bytes = s.as_bytes(); + if bytes.len() < 5 || bytes[0] != b'\\' { + return None; + } + let (offset, is_regex_escape) = if is_in_string && bytes[1] == b'\\' { + (1, true) + } else { + (0, !is_in_string) + }; + if bytes[offset + 1] != b'u' { + return None; + } + if bytes[offset + 2] == b'{' { + let (end, _) = bytes + .iter() + .enumerate() + .skip(offset + 3) + .find(|(_, &c)| c == b'}')?; + Some(UnicodeEscape { + codepoint: u32::from_str_radix(&s[offset + 3..end], 16).ok()?, + kind: if is_regex_escape { + UnicodeEscapeKind::RegexBraced + } else { + UnicodeEscapeKind::String + }, + len: end + 1, + }) + } else if (offset + 6) <= bytes.len() { + Some(UnicodeEscape { + codepoint: u32::from_str_radix(&s[offset + 2..offset + 6], 16).ok()?, + kind: if is_regex_escape { + UnicodeEscapeKind::RegexPlain + } else { + UnicodeEscapeKind::String + }, + len: offset + 6, + }) + } else { + None } - None } fn make_suggestion( @@ -427,7 +589,6 @@ fn make_suggestion( AnyJsCallArgument::JsSpread(_) => None, }, }; - suggestion.map(|s| { make::js_call_arguments( make::token(T!['(']), @@ -440,238 +601,180 @@ fn make_suggestion( }) } -fn is_emoji_modifier(c: char) -> bool { - (0x1F3FB..=0x1F3FF).contains(&(c as u32)) -} - -fn has_emoji_modifier(chars: &str) -> bool { - 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(c: char) -> bool { - (0x1F1E6..=0x1F1FF).contains(&(c as u32)) -} - -fn has_regional_indicator_symbol(chars: &str) -> bool { - 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 { - match ch { - '\u{0300}'..='\u{036F}' | // Combining Diacritical Marks - '\u{1AB0}'..='\u{1AFF}' | // Combining Diacritical Marks Extended - '\u{1DC0}'..='\u{1DFF}' | // Combining Diacritical Marks Supplement - '\u{20D0}'..='\u{20FF}' | // Combining Diacritical Marks for Symbols - '\u{FE20}'..='\u{FE2F}' // Combining Half Marks - => true, - _ => false - } -} - -fn is_variation_selector_16(ch: char) -> bool { - ('\u{FE00}'..='\u{FE0F}').contains(&ch) -} - -fn has_combining_class_or_vs16(chars: &str) -> bool { - chars.chars().enumerate().any(|(i, c)| { - i != 0 - && (is_combining_character(c) || is_variation_selector_16(c)) - // SAFETY: index `i - 1` is not equal to zero. - && !(is_combining_character(chars.chars().nth(i - 1).unwrap()) - // SAFETY: index `i - 1` is not equal to zero. - || is_variation_selector_16(chars.chars().nth(i - 1).unwrap())) - }) -} - -fn zwj(chars: &str) -> bool { - let char_vec: Vec = chars.chars().collect(); - let last_index = char_vec.len() - 1; - char_vec.iter().enumerate().any(|(i, &c)| { - i != 0 - && i != last_index - && c as u32 == 0x200D - && char_vec[i - 1] as u32 != 0x200D - && char_vec[i + 1] as u32 != 0x200D - }) -} - -fn has_surrogate_pair(s: &str) -> bool { - s.chars().any(is_not_bmp_char) -} - -/// Codepoints that cannot be encoded on 4 bytes in UTF-16 / UCS-2 -fn is_not_bmp_char(c: char) -> bool { - c as u32 > 0xFFFF -} +#[cfg(test)] +mod tests { + use super::*; + // #[test] + // fn test_replace_escaped_unicode() { + // assert_eq!(replace_escaped_unicode(r#"/[\uD83D\uDC4D]/"#), "/[πŸ‘]/"); + // assert_eq!(replace_escaped_unicode(r#"/[\u0041\u0301]/"#), "/[Á]/"); + // assert_eq!( + // replace_escaped_unicode("/[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u"), + // "/[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u" + // ); + // assert_eq!( + // replace_escaped_unicode(r#"/[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u"#), + // "/[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u" + // ); + // assert_eq!( + // replace_escaped_unicode(r#"/[\u899\uD83D\uDC4D]/"#), + // r#"/[\u899πŸ‘]/"# + // ); + // assert_eq!( + // replace_escaped_unicode(r#"/[\u899\uD83D\u899\uDC4D]/"#), + // r#"/[\u899\uD83D\u899\uDC4D]/"# + // ); + // } -/// Convert unicode escape sequence string to unicode character -/// - unicode escape sequences: \u{XXXX} -/// - unicode escape sequences without parenthesis: \uXXXX -/// - surrogate pair: \uXXXX\uXXXX -/// If the unicode escape sequence is not valid, it will be treated as a simple string. -/// -/// ```example -/// \uD83D\uDC4D -> πŸ‘ -/// \u0041\u0301 -> Á -/// \uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66 -> πŸ‘¨β€πŸ‘©β€πŸ‘¦ -/// \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466} -> πŸ‘¨β€πŸ‘©β€πŸ‘¦ -/// \u899\uD83D\uDC4D -> \u899πŸ‘ -/// ```` -fn replace_escaped_unicode(input: &str) -> String { - let mut result = String::new(); - let mut chars_iter = input.chars().peekable(); + #[test] + fn test_decode_unicode_escape_sequence() { + assert_eq!(decode_unicode_escape_sequence(r"", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\\", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\\", true), None); + assert_eq!(decode_unicode_escape_sequence(r"\n", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\u", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\uZ", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\u{", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\u{}", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\u{Z}", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\\u{31}", false), None); - while let Some(ch) = chars_iter.next() { - if ch == '\\' { - match handle_escape_sequence(&mut chars_iter) { - Some(unicode_char) => result.push_str(&unicode_char), - None => result.push(ch), - } - } else { - result.push(ch); - } - } - result -} + assert_eq!( + decode_unicode_escape_sequence(r"\u0031 test", false), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::RegexPlain, + len: 6 + }) + ); + assert_eq!( + decode_unicode_escape_sequence(r"\u0031 test", true), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::String, + len: 6 + }) + ); + assert_eq!( + decode_unicode_escape_sequence(r"\\u0031 test", true), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::RegexPlain, + len: 7 + }) + ); -fn handle_escape_sequence(chars_iter: &mut std::iter::Peekable) -> Option { - if chars_iter.peek() != Some(&'u') { - return None; - } - chars_iter.next(); + assert_eq!( + decode_unicode_escape_sequence(r"\u{31} test", false), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::RegexBraced, + len: 6 + }) + ); + assert_eq!( + decode_unicode_escape_sequence(r"\u{31} test", true), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::String, + len: 6 + }) + ); + assert_eq!( + decode_unicode_escape_sequence(r"\\u{31} test", true), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::RegexBraced, + len: 7 + }) + ); - if chars_iter.peek() == Some(&'{') { - handle_braced_escape_sequence(chars_iter) - } else { - handle_simple_or_surrogate_escape_sequence(chars_iter) + assert_eq!( + decode_unicode_escape_sequence(r"\u{1} test", false), + Some(UnicodeEscape { + codepoint: 1, + kind: UnicodeEscapeKind::RegexBraced, + len: 5 + }) + ); } -} -fn handle_braced_escape_sequence( - chars_iter: &mut std::iter::Peekable, -) -> Option { - chars_iter.next(); - let mut codepoint_str = String::new(); - while let Some(&next_char) = chars_iter.peek() { - if next_char == '}' { - chars_iter.next(); - break; - } else { - codepoint_str.push(next_char); - chars_iter.next(); - } - } - u32::from_str_radix(&codepoint_str, 16) - .ok() - .and_then(char::from_u32) - .map(|c| c.to_string()) -} + #[test] + fn test_decode_next_codepoint() { + assert_eq!(decode_next_codepoint(r"", false), None); + assert_eq!(decode_next_codepoint(r"1 test", false), Some((0x31, 1))); -fn handle_simple_or_surrogate_escape_sequence( - chars_iter: &mut std::iter::Peekable, -) -> Option { - let mut invalid_pair = String::new(); - let mut high_surrogate_str = String::new(); + assert_eq!( + decode_next_codepoint(r"\u0031\u0031", false), + Some((0x31, 6)) + ); + assert_eq!(decode_next_codepoint(r"\u0031 test", true), Some((0x31, 6))); + assert_eq!( + decode_next_codepoint(r"\\u0031 test", true), + Some((0x31, 7)) + ); - for _ in 0..4 { - if let Some(&next_char) = chars_iter.peek() { - if next_char.is_ascii_hexdigit() { - high_surrogate_str.push(next_char); - chars_iter.next(); - } else { - // If the character is not a valid Unicode char, return as simple string. - return Some(format!("\\u{high_surrogate_str}")); - } - } else { - // If not enough characters, return as if it were a simple string. - return Some(format!("\\u{high_surrogate_str}")); - } - } + assert_eq!( + decode_next_codepoint(r"\u{31}\u{31}", false), + Some((0x31, 6)) + ); + assert_eq!(decode_next_codepoint(r"\u{31} test", true), Some((0x31, 6))); + assert_eq!( + decode_next_codepoint(r"\\u{31} test", true), + Some((0x31, 7)) + ); - if let Ok(high_surrogate) = u32::from_str_radix(&high_surrogate_str, 16) { - // Check if it is in the high surrogate range(0xD800-0xDBFF) in UTF-16. - if (0xD800..=0xDBFF).contains(&high_surrogate) { - // If we have a high surrogate, expect a low surrogate next - if chars_iter.next() == Some('\\') && chars_iter.next() == Some('u') { - let mut low_surrogate_str = String::new(); - for _ in 0..4 { - if let Some(next_char) = chars_iter.peek() { - if !next_char.is_ascii_hexdigit() { - // Return as a simple string - // - high surrogate on its own doesn't make sense - // - low surrogate is not a valid unicode codepoint - // e.g \uD83D\u333 - invalid_pair.push_str(&format!("\\u{high_surrogate_str}")); - invalid_pair.push_str(&format!("\\u{low_surrogate_str}")); - return Some(invalid_pair); - } - low_surrogate_str.push(*next_char); - chars_iter.next(); - } - } - if let Ok(low_surrogate) = u32::from_str_radix(&low_surrogate_str, 16) { - // Check if it is in the low surrogate range(0xDC00-0xDFFF) in UTF-16. - if (0xDC00..=0xDFFF).contains(&low_surrogate) { - // Calculate the codepoint from the surrogate pair - let codepoint = - ((high_surrogate - 0xD800) << 10) + (low_surrogate - 0xDC00) + 0x10000; - return char::from_u32(codepoint).map(|c| c.to_string()); - }; - } - } - } else { - match char::from_u32(high_surrogate) { - Some(c) => return Some(c.to_string()), - None => invalid_pair.push_str(&format!("\\u{high_surrogate_str}")), - } - } - } - Some(invalid_pair) -} + // Surrogate pairs + assert_eq!( + decode_next_codepoint(r"\uD83D\uDC4D", false), + Some(('πŸ‘' as u32, 12)) + ); + assert_eq!( + decode_next_codepoint(r"\uD83D\uDC4D", true), + Some(('πŸ‘' as u32, 12)) + ); + assert_eq!( + decode_next_codepoint(r"\\uD83D\\uDC4D", true), + Some(('πŸ‘' as u32, 14)) + ); + assert_eq!( + decode_next_codepoint(r"\u{D83D}\u{DC4D}", true), + Some(('πŸ‘' as u32, 16)) + ); + assert_eq!( + decode_next_codepoint(r"\uD83D\u{DC4D}", true), + Some(('πŸ‘' as u32, 14)) + ); + assert_eq!( + decode_next_codepoint(r"\u{D83D}\uDC4D", true), + Some(('πŸ‘' as u32, 14)) + ); -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_replace_escaped_unicode() { - assert_eq!(replace_escaped_unicode(r#"/[\uD83D\uDC4D]/"#), "/[πŸ‘]/"); - assert_eq!(replace_escaped_unicode(r#"/[\u0041\u0301]/"#), "/[Á]/"); + // Lone high surrogate + assert_eq!( + decode_next_codepoint(r"\u{D83D}\u{DC4D}", false), + Some((0xD83D, 8)) + ); + assert_eq!( + decode_next_codepoint(r"\\u{D83D}\\u{DC4D}", true), + Some((0xD83D, 9)) + ); assert_eq!( - replace_escaped_unicode("/[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u"), - "/[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u" + decode_next_codepoint(r"\\uD83D\\u{DC4D}", true), + Some((0xD83D, 7)) ); assert_eq!( - replace_escaped_unicode(r#"/[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u"#), - "/[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u" + decode_next_codepoint(r"\\u{D83D}\\uDC4D", true), + Some((0xD83D, 9)) ); assert_eq!( - replace_escaped_unicode(r#"/[\u899\uD83D\uDC4D]/"#), - r#"/[\u899πŸ‘]/"# + decode_next_codepoint(r"\u{D83D}\\uDC4D", true), + Some((0xD83D, 8)) ); assert_eq!( - replace_escaped_unicode(r#"/[\u899\uD83D\u899\uDC4D]/"#), - r#"/[\u899\uD83D\u899\uDC4D]/"# + decode_next_codepoint(r"\uD83D\\uDC4D", true), + Some((0xD83D, 6)) ); } } diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js b/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js index f24d34e5928c..042352d27bb7 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js +++ b/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js @@ -17,5 +17,7 @@ new RegExp("\n"); /\u{1F}/g; /\t/; /\n/; +/\x/; +/\u/; new (function foo() {})("\\x1f"); /[\u200E\u2066-\u2069]/gu; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js.snap index b6f2aad25d69..31c832935b63 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js.snap @@ -23,6 +23,8 @@ new RegExp("\n"); /\u{1F}/g; /\t/; /\n/; +/\x/; +/\u/; new (function foo() {})("\\x1f"); /[\u200E\u2066-\u2069]/gu; ``` diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/invalid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/invalid.js.snap index 353af434dc4a..aa59ade4826d 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/invalid.js.snap @@ -83,15 +83,17 @@ var r = globalThis.RegExp("[πŸ‘]", ""); # Diagnostics ``` -invalid.js:1:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:1:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. > 1 β”‚ var r = /[πŸ‘]/; - β”‚ ^^^^^^ + β”‚ ^^ 2 β”‚ var r = /[\uD83D\uDC4D]/; 3 β”‚ var r = /[πŸ‘]\\a/; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 1 β”‚ varΒ·rΒ·=Β·/[πŸ‘]/u; @@ -100,16 +102,18 @@ invalid.js:1:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━ ``` ``` -invalid.js:2:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:2:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 1 β”‚ var r = /[πŸ‘]/; > 2 β”‚ var r = /[\uD83D\uDC4D]/; - β”‚ ^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 3 β”‚ var r = /[πŸ‘]\\a/; 4 β”‚ var r = /(?<=[πŸ‘])/; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 2 β”‚ varΒ·rΒ·=Β·/[\uD83D\uDC4D]/u; @@ -118,17 +122,19 @@ invalid.js:2:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━ ``` ``` -invalid.js:3:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:3:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 1 β”‚ var r = /[πŸ‘]/; 2 β”‚ var r = /[\uD83D\uDC4D]/; > 3 β”‚ var r = /[πŸ‘]\\a/; - β”‚ ^^^^^^^^^ + β”‚ ^^ 4 β”‚ var r = /(?<=[πŸ‘])/; 5 β”‚ var r = /[AοΏ½]/; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 3 β”‚ varΒ·rΒ·=Β·/[πŸ‘]\\a/u; @@ -137,17 +143,19 @@ invalid.js:3:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━ ``` ``` -invalid.js:4:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:4:15 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 2 β”‚ var r = /[\uD83D\uDC4D]/; 3 β”‚ var r = /[πŸ‘]\\a/; > 4 β”‚ var r = /(?<=[πŸ‘])/; - β”‚ ^^^^^^^^^^^ + β”‚ ^^ 5 β”‚ var r = /[AοΏ½]/; 6 β”‚ var r = /[AοΏ½]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 4 β”‚ varΒ·rΒ·=Β·/(?<=[πŸ‘])/u; @@ -156,167 +164,189 @@ invalid.js:4:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━ ``` ``` -invalid.js:5:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:5:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 3 β”‚ var r = /[πŸ‘]\\a/; 4 β”‚ var r = /(?<=[πŸ‘])/; > 5 β”‚ var r = /[AοΏ½]/; - β”‚ ^^^^^ + β”‚ ^ 6 β”‚ var r = /[AοΏ½]/u; 7 β”‚ var r = /[\u0041\u0301]/; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:6:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:6:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 4 β”‚ var r = /(?<=[πŸ‘])/; 5 β”‚ var r = /[AοΏ½]/; > 6 β”‚ var r = /[AοΏ½]/u; - β”‚ ^^^^^^ + β”‚ ^ 7 β”‚ var r = /[\u0041\u0301]/; 8 β”‚ var r = /[\u0041\u0301]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:7:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:7:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 5 β”‚ var r = /[AοΏ½]/; 6 β”‚ var r = /[AοΏ½]/u; > 7 β”‚ var r = /[\u0041\u0301]/; - β”‚ ^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 8 β”‚ var r = /[\u0041\u0301]/u; 9 β”‚ var r = /[\u{41}\u{301}]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:8:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:8:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 6 β”‚ var r = /[AοΏ½]/u; 7 β”‚ var r = /[\u0041\u0301]/; > 8 β”‚ var r = /[\u0041\u0301]/u; - β”‚ ^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 9 β”‚ var r = /[\u{41}\u{301}]/u; 10 β”‚ var r = /[❇�]/; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:9:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:9:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 7 β”‚ var r = /[\u0041\u0301]/; 8 β”‚ var r = /[\u0041\u0301]/u; > 9 β”‚ var r = /[\u{41}\u{301}]/u; - β”‚ ^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^ 10 β”‚ var r = /[❇�]/; 11 β”‚ var r = /[❇�]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:10:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:10:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 8 β”‚ var r = /[\u0041\u0301]/u; 9 β”‚ var r = /[\u{41}\u{301}]/u; > 10 β”‚ var r = /[❇�]/; - β”‚ ^^^^^ + β”‚ ^ 11 β”‚ var r = /[❇�]/u; 12 β”‚ var r = /[\u2747\uFE0F]/; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:11:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:11:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 9 β”‚ var r = /[\u{41}\u{301}]/u; 10 β”‚ var r = /[❇�]/; > 11 β”‚ var r = /[❇�]/u; - β”‚ ^^^^^^ + β”‚ ^ 12 β”‚ var r = /[\u2747\uFE0F]/; 13 β”‚ var r = /[\u2747\uFE0F]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:12:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:12:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 10 β”‚ var r = /[❇�]/; 11 β”‚ var r = /[❇�]/u; > 12 β”‚ var r = /[\u2747\uFE0F]/; - β”‚ ^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 13 β”‚ var r = /[\u2747\uFE0F]/u; 14 β”‚ var r = /[\u{2747}\u{FE0F}]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:13:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:13:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 11 β”‚ var r = /[❇�]/u; 12 β”‚ var r = /[\u2747\uFE0F]/; > 13 β”‚ var r = /[\u2747\uFE0F]/u; - β”‚ ^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 14 β”‚ var r = /[\u{2747}\u{FE0F}]/u; 15 β”‚ var r = /[πŸ‘ΆπŸ»]/; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:14:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:14:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 12 β”‚ var r = /[\u2747\uFE0F]/; 13 β”‚ var r = /[\u2747\uFE0F]/u; > 14 β”‚ var r = /[\u{2747}\u{FE0F}]/u; - β”‚ ^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^ 15 β”‚ var r = /[πŸ‘ΆπŸ»]/; 16 β”‚ var r = /[πŸ‘ΆπŸ»]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:15:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:15:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 13 β”‚ var r = /[\u2747\uFE0F]/u; 14 β”‚ var r = /[\u{2747}\u{FE0F}]/u; > 15 β”‚ var r = /[πŸ‘ΆπŸ»]/; - β”‚ ^^^^^^^^ + β”‚ ^^ 16 β”‚ var r = /[πŸ‘ΆπŸ»]/u; 17 β”‚ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 15 β”‚ varΒ·rΒ·=Β·/[πŸ‘ΆπŸ»]/u; @@ -325,62 +355,70 @@ invalid.js:15:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:16:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:16:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 14 β”‚ var r = /[\u{2747}\u{FE0F}]/u; 15 β”‚ var r = /[πŸ‘ΆπŸ»]/; > 16 β”‚ var r = /[πŸ‘ΆπŸ»]/u; - β”‚ ^^^^^^^^^ + β”‚ ^^^^ 17 β”‚ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; 18 β”‚ var r = /[\u{1F476}\u{1F3FB}]/u; + i Replace the character class with an alternation. + ``` ``` -invalid.js:17:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:17:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 15 β”‚ var r = /[πŸ‘ΆπŸ»]/; 16 β”‚ var r = /[πŸ‘ΆπŸ»]/u; > 17 β”‚ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^ 18 β”‚ var r = /[\u{1F476}\u{1F3FB}]/u; 19 β”‚ var r = /[πŸ‡―πŸ‡΅]/; + i Replace the character class with an alternation. + ``` ``` -invalid.js:18:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:18:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 16 β”‚ var r = /[πŸ‘ΆπŸ»]/u; 17 β”‚ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; > 18 β”‚ var r = /[\u{1F476}\u{1F3FB}]/u; - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^ 19 β”‚ var r = /[πŸ‡―πŸ‡΅]/; 20 β”‚ var r = /[πŸ‡―πŸ‡΅]/i; + i Replace the character class with an alternation. + ``` ``` -invalid.js:19:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:19:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 17 β”‚ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; 18 β”‚ var r = /[\u{1F476}\u{1F3FB}]/u; > 19 β”‚ var r = /[πŸ‡―πŸ‡΅]/; - β”‚ ^^^^^^ + β”‚ ^ 20 β”‚ var r = /[πŸ‡―πŸ‡΅]/i; 21 β”‚ var r = /[πŸ‡―πŸ‡΅]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 19 β”‚ varΒ·rΒ·=Β·/[πŸ‡―πŸ‡΅]/u; @@ -389,17 +427,19 @@ invalid.js:19:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:20:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:20:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 18 β”‚ var r = /[\u{1F476}\u{1F3FB}]/u; 19 β”‚ var r = /[πŸ‡―πŸ‡΅]/; > 20 β”‚ var r = /[πŸ‡―πŸ‡΅]/i; - β”‚ ^^^^^^^ + β”‚ ^ 21 β”‚ var r = /[πŸ‡―πŸ‡΅]/u; 22 β”‚ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 18 18 β”‚ var r = /[\u{1F476}\u{1F3FB}]/u; @@ -413,62 +453,70 @@ invalid.js:20:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:21:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:21:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 19 β”‚ var r = /[πŸ‡―πŸ‡΅]/; 20 β”‚ var r = /[πŸ‡―πŸ‡΅]/i; > 21 β”‚ var r = /[πŸ‡―πŸ‡΅]/u; - β”‚ ^^^^^^^ + β”‚ ^^ 22 β”‚ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; 23 β”‚ var r = /[\u{1F1EF}\u{1F1F5}]/u; + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:22:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:22:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 20 β”‚ var r = /[πŸ‡―πŸ‡΅]/i; 21 β”‚ var r = /[πŸ‡―πŸ‡΅]/u; > 22 β”‚ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^ 23 β”‚ var r = /[\u{1F1EF}\u{1F1F5}]/u; 24 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/; + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:23:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:23:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 21 β”‚ var r = /[πŸ‡―πŸ‡΅]/u; 22 β”‚ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; > 23 β”‚ var r = /[\u{1F1EF}\u{1F1F5}]/u; - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^ 24 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/; 25 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/u; + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:24:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:24:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 22 β”‚ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; 23 β”‚ var r = /[\u{1F1EF}\u{1F1F5}]/u; > 24 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/; - β”‚ ^^^^^^^^^^ + β”‚ ^^ 25 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/u; 26 β”‚ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 24 β”‚ varΒ·rΒ·=Β·/[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/u; @@ -477,62 +525,70 @@ invalid.js:24:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:25:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:25:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 23 β”‚ var r = /[\u{1F1EF}\u{1F1F5}]/u; 24 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/; > 25 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/u; - β”‚ ^^^^^^^^^^^ + β”‚ ^^^^ 26 β”‚ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; 27 β”‚ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:26:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:26:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 24 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/; 25 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/u; > 26 β”‚ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 27 β”‚ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; 28 β”‚ var r = new RegExp("[πŸ‘]", ""); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:27:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:27:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 25 β”‚ var r = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/u; 26 β”‚ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; > 27 β”‚ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^ 28 β”‚ var r = new RegExp("[πŸ‘]", ""); 29 β”‚ var r = new RegExp('[πŸ‘]', ``); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:28:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:28:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 26 β”‚ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; 27 β”‚ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; > 28 β”‚ var r = new RegExp("[πŸ‘]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 29 β”‚ var r = new RegExp('[πŸ‘]', ``); 30 β”‚ var r = new RegExp("[πŸ‘]", flags); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 28 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‘]",Β·"u"); @@ -541,17 +597,19 @@ invalid.js:28:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:29:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:29:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 27 β”‚ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; 28 β”‚ var r = new RegExp("[πŸ‘]", ""); > 29 β”‚ var r = new RegExp('[πŸ‘]', ``); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 30 β”‚ var r = new RegExp("[πŸ‘]", flags); 31 β”‚ var r = new RegExp("[\uD83D\uDC4D]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 29 β”‚ varΒ·rΒ·=Β·newΒ·RegExp('[πŸ‘]',Β·`u`); @@ -560,32 +618,36 @@ invalid.js:29:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:30:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:30:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 28 β”‚ var r = new RegExp("[πŸ‘]", ""); 29 β”‚ var r = new RegExp('[πŸ‘]', ``); > 30 β”‚ var r = new RegExp("[πŸ‘]", flags); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 31 β”‚ var r = new RegExp("[\uD83D\uDC4D]", ""); 32 β”‚ var r = new RegExp("/(?<=[πŸ‘])", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + ``` ``` -invalid.js:31:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:31:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 29 β”‚ var r = new RegExp('[πŸ‘]', ``); 30 β”‚ var r = new RegExp("[πŸ‘]", flags); > 31 β”‚ var r = new RegExp("[\uD83D\uDC4D]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 32 β”‚ var r = new RegExp("/(?<=[πŸ‘])", ""); 33 β”‚ var r = new RegExp("[AοΏ½]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 31 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("[\uD83D\uDC4D]",Β·"u"); @@ -594,17 +656,19 @@ invalid.js:31:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:32:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:32:27 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 30 β”‚ var r = new RegExp("[πŸ‘]", flags); 31 β”‚ var r = new RegExp("[\uD83D\uDC4D]", ""); > 32 β”‚ var r = new RegExp("/(?<=[πŸ‘])", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 33 β”‚ var r = new RegExp("[AοΏ½]", ""); 34 β”‚ var r = new RegExp("[AοΏ½]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 32 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("/(?<=[πŸ‘])",Β·"u"); @@ -613,167 +677,189 @@ invalid.js:32:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:33:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:33:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 31 β”‚ var r = new RegExp("[\uD83D\uDC4D]", ""); 32 β”‚ var r = new RegExp("/(?<=[πŸ‘])", ""); > 33 β”‚ var r = new RegExp("[AοΏ½]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 34 β”‚ var r = new RegExp("[AοΏ½]", "u"); 35 β”‚ var r = new RegExp("[\u0041\u0301]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:34:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:34:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 32 β”‚ var r = new RegExp("/(?<=[πŸ‘])", ""); 33 β”‚ var r = new RegExp("[AοΏ½]", ""); > 34 β”‚ var r = new RegExp("[AοΏ½]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 35 β”‚ var r = new RegExp("[\u0041\u0301]", ""); 36 β”‚ var r = new RegExp("[\u0041\u0301]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:35:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:35:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 33 β”‚ var r = new RegExp("[AοΏ½]", ""); 34 β”‚ var r = new RegExp("[AοΏ½]", "u"); > 35 β”‚ var r = new RegExp("[\u0041\u0301]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 36 β”‚ var r = new RegExp("[\u0041\u0301]", "u"); 37 β”‚ var r = new RegExp("[\u{41}\u{301}]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:36:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:36:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 34 β”‚ var r = new RegExp("[AοΏ½]", "u"); 35 β”‚ var r = new RegExp("[\u0041\u0301]", ""); > 36 β”‚ var r = new RegExp("[\u0041\u0301]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 37 β”‚ var r = new RegExp("[\u{41}\u{301}]", "u"); 38 β”‚ var r = new RegExp("[❇�]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:37:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:37:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 35 β”‚ var r = new RegExp("[\u0041\u0301]", ""); 36 β”‚ var r = new RegExp("[\u0041\u0301]", "u"); > 37 β”‚ var r = new RegExp("[\u{41}\u{301}]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^ 38 β”‚ var r = new RegExp("[❇�]", ""); 39 β”‚ var r = new RegExp("[❇�]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:38:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:38:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 36 β”‚ var r = new RegExp("[\u0041\u0301]", "u"); 37 β”‚ var r = new RegExp("[\u{41}\u{301}]", "u"); > 38 β”‚ var r = new RegExp("[❇�]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 39 β”‚ var r = new RegExp("[❇�]", "u"); 40 β”‚ var r = new RegExp("[\u2747\uFE0F]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:39:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:39:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 37 β”‚ var r = new RegExp("[\u{41}\u{301}]", "u"); 38 β”‚ var r = new RegExp("[❇�]", ""); > 39 β”‚ var r = new RegExp("[❇�]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 40 β”‚ var r = new RegExp("[\u2747\uFE0F]", ""); 41 β”‚ var r = new RegExp("[\u2747\uFE0F]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:40:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:40:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 38 β”‚ var r = new RegExp("[❇�]", ""); 39 β”‚ var r = new RegExp("[❇�]", "u"); > 40 β”‚ var r = new RegExp("[\u2747\uFE0F]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 41 β”‚ var r = new RegExp("[\u2747\uFE0F]", "u"); 42 β”‚ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:41:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:41:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 39 β”‚ var r = new RegExp("[❇�]", "u"); 40 β”‚ var r = new RegExp("[\u2747\uFE0F]", ""); > 41 β”‚ var r = new RegExp("[\u2747\uFE0F]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^ 42 β”‚ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); 43 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:42:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:42:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 40 β”‚ var r = new RegExp("[\u2747\uFE0F]", ""); 41 β”‚ var r = new RegExp("[\u2747\uFE0F]", "u"); > 42 β”‚ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^ 43 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", ""); 44 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:43:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:43:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 41 β”‚ var r = new RegExp("[\u2747\uFE0F]", "u"); 42 β”‚ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); > 43 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 44 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", "u"); 45 β”‚ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 43 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‘ΆπŸ»]",Β·"u"); @@ -782,62 +868,70 @@ invalid.js:43:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:44:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:44:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 42 β”‚ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); 43 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", ""); > 44 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^ 45 β”‚ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); 46 β”‚ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + i Replace the character class with an alternation. + ``` ``` -invalid.js:45:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:45:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 43 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", ""); 44 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", "u"); > 45 β”‚ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^ 46 β”‚ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); 47 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); + i Replace the character class with an alternation. + ``` ``` -invalid.js:46:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:46:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 44 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", "u"); 45 β”‚ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); > 46 β”‚ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^ 47 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); 48 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); + i Replace the character class with an alternation. + ``` ``` -invalid.js:47:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:47:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 45 β”‚ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); 46 β”‚ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); > 47 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 48 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); 49 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 47 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‡―πŸ‡΅]",Β·"u"); @@ -846,17 +940,19 @@ invalid.js:47:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:48:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:48:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 46 β”‚ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); 47 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); > 48 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 49 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); 50 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 46 46 β”‚ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); @@ -870,17 +966,19 @@ invalid.js:48:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:49:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:49:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 47 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); 48 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); > 49 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 50 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`); 51 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 47 47 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); @@ -894,17 +992,19 @@ invalid.js:49:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:50:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:50:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 48 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); 49 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); > 50 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 51 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]"); 52 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]",); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 50 β”‚ varΒ·rΒ·=Β·newΒ·RegExp('[πŸ‡―πŸ‡΅]',Β·`${foo}u`); @@ -913,17 +1013,19 @@ invalid.js:50:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:51:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:51:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 49 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); 50 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`); > 51 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]"); - β”‚ ^^^^^^^^^^^^^^^^^^ + β”‚ ^ 52 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]",); 53 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 51 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‡―πŸ‡΅]",Β·"u"); @@ -932,17 +1034,19 @@ invalid.js:51:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:52:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:52:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 50 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`); 51 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]"); > 52 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]",); - β”‚ ^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 53 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "u"); 54 β”‚ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 52 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‡―πŸ‡΅]",Β·"u"); @@ -951,62 +1055,70 @@ invalid.js:52:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:53:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:53:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 51 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]"); 52 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]",); > 53 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 54 β”‚ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); 55 β”‚ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:54:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:54:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 52 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]",); 53 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "u"); > 54 β”‚ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^ 55 β”‚ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); 56 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", ""); + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:55:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:55:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 53 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "u"); 54 β”‚ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); > 55 β”‚ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^ 56 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", ""); 57 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", "u"); + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:56:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:56:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 54 β”‚ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); 55 β”‚ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); > 56 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 57 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", "u"); 58 β”‚ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 56 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]",Β·"u"); @@ -1015,92 +1127,104 @@ invalid.js:56:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:57:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:57:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 55 β”‚ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); 56 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", ""); > 57 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^ 58 β”‚ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); 59 β”‚ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:58:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:58:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 56 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", ""); 57 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", "u"); > 58 β”‚ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 59 β”‚ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); 60 β”‚ var r = new globalThis.RegExp("[❇�]", ""); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:59:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:59:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 57 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", "u"); 58 β”‚ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); > 59 β”‚ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^ 60 β”‚ var r = new globalThis.RegExp("[❇�]", ""); 61 β”‚ var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u"); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:60:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:60:33 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 58 β”‚ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); 59 β”‚ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); > 60 β”‚ var r = new globalThis.RegExp("[❇�]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 61 β”‚ var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u"); 62 β”‚ var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:61:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:61:33 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 59 β”‚ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); 60 β”‚ var r = new globalThis.RegExp("[❇�]", ""); > 61 β”‚ var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^ 62 β”‚ var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", ""); 63 β”‚ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + i Replace the character class with an alternation. + ``` ``` -invalid.js:62:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:62:33 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 60 β”‚ var r = new globalThis.RegExp("[❇�]", ""); 61 β”‚ var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u"); > 62 β”‚ var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 63 β”‚ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); 64 β”‚ var r = new window.RegExp("[❇�]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 62 β”‚ varΒ·rΒ·=Β·newΒ·globalThis.RegExp("[πŸ‡―πŸ‡΅]",Β·"u"); @@ -1109,47 +1233,53 @@ invalid.js:62:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:63:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:63:33 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 61 β”‚ var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u"); 62 β”‚ var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", ""); > 63 β”‚ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^ 64 β”‚ var r = new window.RegExp("[❇�]", ""); 65 β”‚ var r = new window.RegExp("[πŸ‘]", ""); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:64:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:64:29 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 62 β”‚ var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", ""); 63 β”‚ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); > 64 β”‚ var r = new window.RegExp("[❇�]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 65 β”‚ var r = new window.RegExp("[πŸ‘]", ""); 66 β”‚ var r = new global.RegExp("[❇�]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:65:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:65:29 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 63 β”‚ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); 64 β”‚ var r = new window.RegExp("[❇�]", ""); > 65 β”‚ var r = new window.RegExp("[πŸ‘]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 66 β”‚ var r = new global.RegExp("[❇�]", ""); 67 β”‚ var r = new global.RegExp("[πŸ‘]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 65 β”‚ varΒ·rΒ·=Β·newΒ·window.RegExp("[πŸ‘]",Β·"u"); @@ -1158,32 +1288,36 @@ invalid.js:65:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:66:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:66:29 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 64 β”‚ var r = new window.RegExp("[❇�]", ""); 65 β”‚ var r = new window.RegExp("[πŸ‘]", ""); > 66 β”‚ var r = new global.RegExp("[❇�]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 67 β”‚ var r = new global.RegExp("[πŸ‘]", ""); 68 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:67:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:67:29 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 65 β”‚ var r = new window.RegExp("[πŸ‘]", ""); 66 β”‚ var r = new global.RegExp("[❇�]", ""); > 67 β”‚ var r = new global.RegExp("[πŸ‘]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 68 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); 69 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[πŸ‘]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 67 β”‚ varΒ·rΒ·=Β·newΒ·global.RegExp("[πŸ‘]",Β·"u"); @@ -1192,32 +1326,36 @@ invalid.js:67:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:68:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:68:55 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 66 β”‚ var r = new global.RegExp("[❇�]", ""); 67 β”‚ var r = new global.RegExp("[πŸ‘]", ""); > 68 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^ 69 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[πŸ‘]", ""); 70 β”‚ var r = RegExp("[πŸ‘]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:69:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:69:55 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 67 β”‚ var r = new global.RegExp("[πŸ‘]", ""); 68 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); > 69 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[πŸ‘]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 70 β”‚ var r = RegExp("[πŸ‘]", ""); 71 β”‚ var r = window.RegExp("[πŸ‘]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 69 β”‚ varΒ·rΒ·=Β·newΒ·globalThis.globalThis.globalThis.RegExp("[πŸ‘]",Β·"u"); @@ -1226,17 +1364,19 @@ invalid.js:69:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:70:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:70:18 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 68 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); 69 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[πŸ‘]", ""); > 70 β”‚ var r = RegExp("[πŸ‘]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 71 β”‚ var r = window.RegExp("[πŸ‘]", ""); 72 β”‚ var r = global.RegExp("[πŸ‘]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 70 β”‚ varΒ·rΒ·=Β·RegExp("[πŸ‘]",Β·"u"); @@ -1245,17 +1385,19 @@ invalid.js:70:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:71:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:71:25 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 69 β”‚ var r = new globalThis.globalThis.globalThis.RegExp("[πŸ‘]", ""); 70 β”‚ var r = RegExp("[πŸ‘]", ""); > 71 β”‚ var r = window.RegExp("[πŸ‘]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 72 β”‚ var r = global.RegExp("[πŸ‘]", ""); 73 β”‚ var r = globalThis.RegExp("[πŸ‘]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 71 β”‚ varΒ·rΒ·=Β·window.RegExp("[πŸ‘]",Β·"u"); @@ -1264,17 +1406,19 @@ invalid.js:71:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:72:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:72:25 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 70 β”‚ var r = RegExp("[πŸ‘]", ""); 71 β”‚ var r = window.RegExp("[πŸ‘]", ""); > 72 β”‚ var r = global.RegExp("[πŸ‘]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 73 β”‚ var r = globalThis.RegExp("[πŸ‘]", ""); 74 β”‚ + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 72 β”‚ varΒ·rΒ·=Β·global.RegExp("[πŸ‘]",Β·"u"); @@ -1283,17 +1427,19 @@ invalid.js:72:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:73:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:73:29 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 71 β”‚ var r = window.RegExp("[πŸ‘]", ""); 72 β”‚ var r = global.RegExp("[πŸ‘]", ""); > 73 β”‚ var r = globalThis.RegExp("[πŸ‘]", ""); - β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + β”‚ ^^ 74 β”‚ 75 β”‚ /[\]πŸ‘]/; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 73 β”‚ varΒ·rΒ·=Β·globalThis.RegExp("[πŸ‘]",Β·"u"); @@ -1302,14 +1448,16 @@ invalid.js:73:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:75:1 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:75:5 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 73 β”‚ var r = globalThis.RegExp("[πŸ‘]", ""); 74 β”‚ > 75 β”‚ /[\]πŸ‘]/; - β”‚ ^^^^^^^^ + β”‚ ^^ + + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. i Safe fix: Add unicode u flag to regex diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js index a7f00f0ba6e5..9b07d4bfaa1f 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js +++ b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js @@ -1,6 +1,6 @@ var r = /[πŸ‘]/u; -var r = /[\\uD83D\\uDC4D]/u; -var r = /[\\u{1F44D}]/u; +var r = /[\uD83D\uDC4D]/u; +var r = /[\u{1F44D}]/u; var r = /❇️/; var r = /Á/; var r = /[❇]/; @@ -11,19 +11,19 @@ var r = /[JP]/; var r = /πŸ‘¨β€πŸ‘©β€πŸ‘¦/; // Ignore solo lead/tail surrogate. -var r = /[\\uD83D]/; -var r = /[\\uDC4D]/; -var r = /[\\uD83D]/u; -var r = /[\\uDC4D]/u; +var r = /[\uD83D]/; +var r = /[\uDC4D]/; +var r = /[\uD83D]/u; +var r = /[\uDC4D]/u; // Ignore solo combining char. -var r = /[\\u0301]/; -var r = /[\\uFE0F]/; -var r = /[\\u0301]/u; -var r = /[\\uFE0F]/u; +var r = /[\u0301]/; +var r = /[\uFE0F]/; +var r = /[\u0301]/u; +var r = /[\uFE0F]/u; // Ignore solo emoji modifier. -var r = /[\\u{1F3FB}]/u; +var r = /[\u{1F3FB}]/u; var r = /[\u{1F3FB}]/u; // Ignore solo regional indicator symbol. @@ -31,8 +31,8 @@ var r = /[πŸ‡―]/u; var r = /[πŸ‡΅]/u; // Ignore solo ZWJ. -var r = /[\\u200D]/; -var r = /[\\u200D]/u; +var r = /[\u200D]/; +var r = /[\u200D]/u; // don't report and don't crash on invalid regex // FIXME: need to ecma regex parser to handle this case diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js.snap index 5e3bf1d09cf5..7818bf658a01 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js.snap @@ -5,8 +5,8 @@ expression: valid.js # Input ```jsx var r = /[πŸ‘]/u; -var r = /[\\uD83D\\uDC4D]/u; -var r = /[\\u{1F44D}]/u; +var r = /[\uD83D\uDC4D]/u; +var r = /[\u{1F44D}]/u; var r = /❇️/; var r = /Á/; var r = /[❇]/; @@ -17,19 +17,19 @@ var r = /[JP]/; var r = /πŸ‘¨β€πŸ‘©β€πŸ‘¦/; // Ignore solo lead/tail surrogate. -var r = /[\\uD83D]/; -var r = /[\\uDC4D]/; -var r = /[\\uD83D]/u; -var r = /[\\uDC4D]/u; +var r = /[\uD83D]/; +var r = /[\uDC4D]/; +var r = /[\uD83D]/u; +var r = /[\uDC4D]/u; // Ignore solo combining char. -var r = /[\\u0301]/; -var r = /[\\uFE0F]/; -var r = /[\\u0301]/u; -var r = /[\\uFE0F]/u; +var r = /[\u0301]/; +var r = /[\uFE0F]/; +var r = /[\u0301]/u; +var r = /[\uFE0F]/u; // Ignore solo emoji modifier. -var r = /[\\u{1F3FB}]/u; +var r = /[\u{1F3FB}]/u; var r = /[\u{1F3FB}]/u; // Ignore solo regional indicator symbol. @@ -37,8 +37,8 @@ var r = /[πŸ‡―]/u; var r = /[πŸ‡΅]/u; // Ignore solo ZWJ. -var r = /[\\u200D]/; -var r = /[\\u200D]/u; +var r = /[\u200D]/; +var r = /[\u200D]/u; // don't report and don't crash on invalid regex // FIXME: need to ecma regex parser to handle this case diff --git a/crates/biome_text_size/src/range.rs b/crates/biome_text_size/src/range.rs index 9c65f8d4da56..d71303946c6a 100644 --- a/crates/biome_text_size/src/range.rs +++ b/crates/biome_text_size/src/range.rs @@ -62,8 +62,8 @@ impl TextRange { /// assert_eq!(range.len(), end - start); /// ``` #[inline] - pub fn new(start: TextSize, end: TextSize) -> TextRange { - assert!(start <= end); + pub const fn new(start: TextSize, end: TextSize) -> TextRange { + assert!(start.raw <= end.raw); TextRange { start, end } } @@ -83,8 +83,13 @@ impl TextRange { /// assert_eq!(&text[range], "23456") /// ``` #[inline] - pub fn at(offset: TextSize, len: TextSize) -> TextRange { - TextRange::new(offset, offset + len) + pub const fn at(offset: TextSize, len: TextSize) -> TextRange { + TextRange { + start: offset, + end: TextSize { + raw: offset.raw + len.raw, + }, + } } /// Create a zero-length range at the specified offset (`offset..offset`). @@ -100,7 +105,7 @@ impl TextRange { /// assert_eq!(range, TextRange::new(point, point)); /// ``` #[inline] - pub fn empty(offset: TextSize) -> TextRange { + pub const fn empty(offset: TextSize) -> TextRange { TextRange { start: offset, end: offset, @@ -122,9 +127,9 @@ impl TextRange { /// assert_eq!(range, TextRange::at(0.into(), point)); /// ``` #[inline] - pub fn up_to(end: TextSize) -> TextRange { + pub const fn up_to(end: TextSize) -> TextRange { TextRange { - start: 0.into(), + start: TextSize { raw: 0 }, end, } }