diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 3462ca8e2bf0..e0bcdc2abfc9 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -104,6 +104,7 @@ define_categories! { "lint/nursery/noDuplicateJsonKeys": "https://biomejs.dev/linter/rules/no-duplicate-json-keys", "lint/nursery/noEmptyBlockStatements": "https://biomejs.dev/linter/rules/no-empty-block-statements", "lint/nursery/noImplicitAnyLet": "https://biomejs.dev/lint/rules/no-implicit-any-let", + "lint/nursery/noMisleadingCharacterClass": "https://biomejs.dev/linter/rules/no-misleading-character-class", "lint/nursery/noUnusedImports": "https://biomejs.dev/linter/rules/no-unused-imports", "lint/nursery/noUnusedPrivateClassMembers": "https://biomejs.dev/linter/rules/no-unused-private-class-members", "lint/nursery/noUselessLoneBlockStatements": "https://biomejs.dev/linter/rules/no-useless-lone-block-statements", diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs index 037aba6b8976..6d44f0a37463 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs @@ -2,6 +2,7 @@ use biome_analyze::declare_group; +pub(crate) mod no_misleading_character_class; pub(crate) mod no_unused_imports; pub(crate) mod use_for_of; @@ -9,6 +10,7 @@ declare_group! { pub (crate) Nursery { name : "nursery" , rules : [ + self :: no_misleading_character_class :: NoMisleadingCharacterClass , self :: no_unused_imports :: NoUnusedImports , self :: use_for_of :: UseForOf , ] diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_misleading_character_class.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_misleading_character_class.rs new file mode 100644 index 000000000000..d0e8cffba819 --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_misleading_character_class.rs @@ -0,0 +1,646 @@ +use crate::{semantic_services::Semantic, JsRuleAction}; +use biome_analyze::{ + context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, +}; +use biome_console::markup; +use biome_diagnostics::Applicability; +use biome_js_factory::make; +use biome_js_syntax::{ + AnyJsCallArgument, AnyJsExpression, AnyJsLiteralExpression, AnyJsTemplateElement, + JsCallArguments, JsCallExpression, JsNewExpression, JsRegexLiteralExpression, + JsStringLiteralExpression, JsSyntaxKind, JsSyntaxToken, T, +}; +use biome_rowan::{ + declare_node_union, AstNode, AstNodeList, AstSeparatedList, BatchMutationExt, TextRange, +}; +declare_rule! { + /// Disallow characters made with multiple code points in character class syntax. + /// + /// Unicode includes the characters which are made with multiple code points. e.g. Á, πŸ‡―πŸ‡΅, πŸ‘¨β€πŸ‘©β€πŸ‘¦. + /// A RegExp character class `/[abc]/` cannot handle characters with multiple code points. + /// For example, the character `❇️` consists of two code points: `❇` (U+2747) and `VARIATION SELECTOR-16` (U+FE0F). + /// If this character is in a RegExp character class, it will match to either `❇` or `VARIATION SELECTOR-16` rather than `❇️`. + /// This rule reports the regular expressions which include multiple code point characters in character class syntax. + /// + /// Source: https://eslint.org/docs/latest/rules/no-misleading-character-class + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// /^[Á]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[❇️]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[πŸ‘ΆπŸ»]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[πŸ‡―πŸ‡΅]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[πŸ‘¨β€πŸ‘©β€πŸ‘¦]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[πŸ‘]$/; // surrogate pair without u flag + /// ``` + /// + /// ## Valid + /// + /// ```js + /// /^[abc]$/; + /// /^[πŸ‘]$/u; + /// /^[\q{πŸ‘ΆπŸ»}]$/v; + /// ``` + /// + + pub(crate) NoMisleadingCharacterClass { + version: "next", + name: "noMisleadingCharacterClass", + recommended: false, + fix_kind: FixKind::Safe, + } +} + +declare_node_union! { + pub(crate) AnyRegexExpression = JsNewExpression | JsCallExpression | JsRegexLiteralExpression +} + +pub enum Message { + SurrogatePairWithoutUFlag, + EmojiModifier, + RegionalIndicatorSymbol, + CombiningClassOrVs16, + JoinedCharSequence, +} + +impl Message { + fn as_str(&self) -> &str { + match self { + Self::CombiningClassOrVs16 => "Unexpected combined character in the character class.", + Self::SurrogatePairWithoutUFlag => { + "Unexpected surrogate pair in character class. Use the 'u' flag." + } + Self::EmojiModifier => "Unexpected modified Emoji in the character class. ", + Self::RegionalIndicatorSymbol => { + "Regional indicator symbol characters should not be used in the character class." + } + Self::JoinedCharSequence => "Unexpected joined character sequence in character class.", + } + } +} + +pub struct RuleState { + range: TextRange, + message: Message, +} + +impl Rule for NoMisleadingCharacterClass { + type Query = Semantic; + type State = RuleState; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let regex = ctx.query(); + + match regex { + AnyRegexExpression::JsRegexLiteralExpression(expr) => { + let Ok((pattern, flags)) = expr.decompose() else { + return None; + }; + + if flags.text().contains('v') { + return None; + } + let regex_pattern = replace_escaped_unicode(pattern.text()); + let has_u_flag = flags.text().contains('u'); + let range = expr.syntax().text_range(); + return diagnostic_regex_pattern(®ex_pattern, has_u_flag, range); + } + + AnyRegexExpression::JsNewExpression(expr) => { + let is_reg_exp = match expr.callee().ok()? { + AnyJsExpression::JsIdentifierExpression(callee) => { + callee.name().ok()?.has_name("RegExp") + } + AnyJsExpression::JsStaticMemberExpression(callee) => { + let is_global_this = match callee.object().ok()? { + AnyJsExpression::JsIdentifierExpression(e) => { + e.name().ok()?.has_name("globalThis") + } + _ => false, + }; + + is_global_this + && callee.member().ok()?.value_token().ok()?.text() == "RegExp" + } + _ => false, + }; + + if is_reg_exp { + let mut args = expr.arguments()?.args().iter(); + let raw_regex_pattern = args + .next() + .and_then(|arg| arg.ok()) + .and_then(|arg| JsStringLiteralExpression::cast_ref(arg.syntax())) + .and_then(|js_string_literal| js_string_literal.inner_string_text().ok())? + .to_string(); + + let regex_pattern = replace_escaped_unicode(raw_regex_pattern.as_str()); + let regexp_flags = args + .next() + .and_then(|arg| arg.ok()) + .and_then(|arg| JsStringLiteralExpression::cast_ref(arg.syntax())) + .map(|js_string_literal| js_string_literal.text()) + .unwrap_or_default(); + + if regexp_flags.contains('v') { + return None; + } + let has_u_flag = regexp_flags.contains('u'); + let range = expr.syntax().text_range(); + return diagnostic_regex_pattern(®ex_pattern, has_u_flag, range); + } + } + AnyRegexExpression::JsCallExpression(expr) => { + let callee = match expr.callee().ok()? { + AnyJsExpression::JsIdentifierExpression(callee) => callee, + _ => return None, + }; + if callee.name().ok()?.has_name("RegExp") { + let mut args = expr.arguments().ok()?.args().iter(); + let raw_regex_pattern = args + .next() + .and_then(|arg| arg.ok()) + .and_then(|arg| JsStringLiteralExpression::cast_ref(arg.syntax())) + .and_then(|js_string_literal| js_string_literal.inner_string_text().ok())? + .to_string(); + + let regex_pattern = replace_escaped_unicode(raw_regex_pattern.as_str()); + + let regexp_flags = args + .next() + .and_then(|arg| arg.ok()) + .and_then(|arg| JsStringLiteralExpression::cast_ref(arg.syntax())) + .map(|js_string_literal| js_string_literal.text()) + .unwrap_or_default(); + + if regexp_flags.contains('v') { + return None; + } + + let has_u_flag = regexp_flags.contains('u'); + let range = expr.syntax().text_range(); + return diagnostic_regex_pattern(®ex_pattern, has_u_flag, range); + } + } + } + None + } + + fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { + Some(RuleDiagnostic::new( + rule_category!(), + state.range, + state.message.as_str(), + )) + } + + fn action(ctx: &RuleContext, state: &Self::State) -> Option { + let node = ctx.query(); + let is_fixable = matches!(state.message, Message::SurrogatePairWithoutUFlag); + if is_fixable { + match node { + AnyRegexExpression::JsRegexLiteralExpression(expr) => { + let prev_token = expr.value_token().ok()?; + let text = prev_token.text(); + let next_token = JsSyntaxToken::new_detached( + JsSyntaxKind::JS_REGEX_LITERAL, + &format!("{}u", text), + [], + [], + ); + + let mut mutation = ctx.root().begin(); + mutation.replace_token(prev_token, next_token); + + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { "Add unicode ""u"" flag to regex" } + .to_owned(), + 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(); + mutation.replace_node(prev_node, suggest); + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { "Add unicode ""u"" flag to regex" } + .to_owned(), + mutation, + }) + } + 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(); + mutation.replace_node(prev_node, suggest); + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { "Add unicode ""u"" flag to regex" } + .to_owned(), + mutation, + }) + } + None => None, + } + } + } + } else { + None + } + } +} + +fn diagnostic_regex_pattern( + regex_pattern: &str, + has_u_flag: bool, + range: TextRange, +) -> Option { + let mut is_in_character_class = false; + let mut escape_next = false; + let char_iter = regex_pattern.chars().peekable(); + for (i, ch) in char_iter.enumerate() { + if escape_next { + escape_next = false; + continue; + } + match ch { + '\\' => escape_next = true, + '[' => is_in_character_class = true, + ']' => is_in_character_class = false, + _ if is_in_character_class && i < regex_pattern.len() => { + if !has_u_flag && has_surrogate_pair(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::SurrogatePairWithoutUFlag, + }); + } + + if has_combining_class_or_vs16(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::CombiningClassOrVs16, + }); + } + + if has_regional_indicator_symbol(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::RegionalIndicatorSymbol, + }); + } + + if has_emoji_modifier(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::EmojiModifier, + }); + } + + if zwj(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::JoinedCharSequence, + }); + } + } + _ => {} + } + } + None +} + +fn make_suggestion( + literal: AnyJsCallArgument, + flag: Option, +) -> Option { + let suggestion = match flag { + None => Some(AnyJsCallArgument::AnyJsExpression( + AnyJsExpression::AnyJsLiteralExpression( + AnyJsLiteralExpression::JsStringLiteralExpression( + make::js_string_literal_expression(make::js_string_literal("u")), + ), + ), + )), + Some(f) => match f { + AnyJsCallArgument::AnyJsExpression(expr) => match expr { + AnyJsExpression::AnyJsLiteralExpression(e) => { + let text = e.text(); + if text.starts_with('\'') { + Some(AnyJsCallArgument::AnyJsExpression( + AnyJsExpression::AnyJsLiteralExpression( + AnyJsLiteralExpression::JsStringLiteralExpression( + make::js_string_literal_expression(make::js_string_literal( + &format!("'{}u'", text), + )), + ), + ), + )) + } else { + Some(AnyJsCallArgument::AnyJsExpression( + AnyJsExpression::AnyJsLiteralExpression( + AnyJsLiteralExpression::JsStringLiteralExpression( + make::js_string_literal_expression(make::js_string_literal( + &format!("{}u", text.replace('"', "")), + )), + ), + ), + )) + } + } + AnyJsExpression::JsTemplateExpression(expr) => { + let mut elements = expr + .elements() + .iter() + .collect::>(); + + let uflag = AnyJsTemplateElement::from(make::js_template_chunk_element( + make::js_template_chunk("u"), + )); + elements.push(uflag); + Some(AnyJsCallArgument::AnyJsExpression( + AnyJsExpression::JsTemplateExpression( + make::js_template_expression( + make::token(T!['`']), + make::js_template_element_list(elements), + make::token(T!['`']), + ) + .build(), + ), + )) + } + AnyJsExpression::JsIdentifierExpression(_) => None, + _ => None, + }, + AnyJsCallArgument::JsSpread(_) => None, + }, + }; + + suggestion.map(|s| { + make::js_call_arguments( + make::token(T!['(']), + make::js_call_argument_list([literal, s], [make::token(T![,])]), + make::token(T![')']), + ) + }) +} + +fn is_emoji_modifier(code: u32) -> bool { + (0x1F3FB..=0x1F3FF).contains(&code) +} + +fn has_emoji_modifier(chars: &str) -> bool { + let char_vec: Vec = chars.chars().collect(); + + char_vec.iter().enumerate().any(|(i, &c)| { + i != 0 && is_emoji_modifier(c as u32) && !is_emoji_modifier(char_vec[i - 1] as u32) + }) +} + +fn is_regional_indicator_symbol(code: u32) -> bool { + (0x1F1E6..=0x1F1FF).contains(&code) +} + +fn has_regional_indicator_symbol(chars: &str) -> bool { + let char_vec: Vec = chars.chars().collect(); + + char_vec.iter().enumerate().any(|(i, &c)| { + i != 0 + && is_regional_indicator_symbol(c as u32) + && is_regional_indicator_symbol(char_vec[i - 1] as u32) + }) +} + +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)) + && !(is_combining_character(chars.chars().nth(i - 1).unwrap()) + || 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(|c| c as u32 > 0xFFFF) +} + +/// 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(); + + 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 +} + +fn handle_escape_sequence(chars_iter: &mut std::iter::Peekable) -> Option { + if chars_iter.peek() != Some(&'u') { + return None; + } + chars_iter.next(); + + if chars_iter.peek() == Some(&'{') { + handle_braced_escape_sequence(chars_iter) + } else { + handle_simple_or_surrogate_escape_sequence(chars_iter) + } +} + +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()) +} + +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(); + + 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)); + } + } + + 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) +} + +#[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]/"# + ); + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js new file mode 100644 index 000000000000..9cce41c6e7fd --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js @@ -0,0 +1,63 @@ +var r = /[πŸ‘]/; +var r = /[\uD83D\uDC4D]/; +var r = /[πŸ‘]\\a/; +var r = /(?<=[πŸ‘])/; +var r = /[Á]/; +var r = /[Á]/u; +var r = /[\u0041\u0301]/; +var r = /[\u0041\u0301]/u; +var r = /[\u{41}\u{301}]/u; +var r = /[❇️]/; +var r = /[❇️]/u; +var r = /[\u2747\uFE0F]/; +var r = /[\u2747\uFE0F]/u; +var r = /[\u{2747}\u{FE0F}]/u; +var r = /[πŸ‘ΆπŸ»]/; +var r = /[πŸ‘ΆπŸ»]/u; +var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; +var r = /[\u{1F476}\u{1F3FB}]/u; +var r = /[πŸ‡―πŸ‡΅]/; +var r = /[πŸ‡―πŸ‡΅]/i; +var r = /[πŸ‡―πŸ‡΅]/u; +var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; +var r = /[\u{1F1EF}\u{1F1F5}]/u; +var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/; +var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u; +var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; +var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; +var r = new RegExp("[πŸ‘]", ""); +var r = new RegExp('[πŸ‘]', ``); +var r = new RegExp("[πŸ‘]", flags); +var r = new RegExp("[\uD83D\uDC4D]", ""); +var r = new RegExp("/(?<=[πŸ‘])", ""); +var r = new RegExp("[Á]", ""); +var r = new RegExp("[Á]", "u"); +var r = new RegExp("[\u0041\u0301]", ""); +var r = new RegExp("[\u0041\u0301]", "u"); +var r = new RegExp("[\u{41}\u{301}]", "u"); +var r = new RegExp("[❇️]", ""); +var r = new RegExp("[❇️]", "u"); +var r = new RegExp("[\u2747\uFE0F]", ""); +var r = new RegExp("[\u2747\uFE0F]", "u"); +var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); +var r = new RegExp("[πŸ‘ΆπŸ»]", ""); +var r = new RegExp("[πŸ‘ΆπŸ»]", "u"); +var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); +var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); +var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); +var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); +var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); +var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`); +var r = new RegExp("[πŸ‡―πŸ‡΅]"); +var r = new RegExp("[πŸ‡―πŸ‡΅]",); +var r = new RegExp("[πŸ‡―πŸ‡΅]", "u"); +var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); +var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); +var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", ""); +var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "u"); +var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); +var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); +var r = new globalThis.RegExp("[❇️]", ""); +var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u"); +var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", ""); +var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js.snap new file mode 100644 index 000000000000..a6f24a38253e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js.snap @@ -0,0 +1,1155 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +var r = /[πŸ‘]/; +var r = /[\uD83D\uDC4D]/; +var r = /[πŸ‘]\\a/; +var r = /(?<=[πŸ‘])/; +var r = /[Á]/; +var r = /[Á]/u; +var r = /[\u0041\u0301]/; +var r = /[\u0041\u0301]/u; +var r = /[\u{41}\u{301}]/u; +var r = /[❇️]/; +var r = /[❇️]/u; +var r = /[\u2747\uFE0F]/; +var r = /[\u2747\uFE0F]/u; +var r = /[\u{2747}\u{FE0F}]/u; +var r = /[πŸ‘ΆπŸ»]/; +var r = /[πŸ‘ΆπŸ»]/u; +var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; +var r = /[\u{1F476}\u{1F3FB}]/u; +var r = /[πŸ‡―πŸ‡΅]/; +var r = /[πŸ‡―πŸ‡΅]/i; +var r = /[πŸ‡―πŸ‡΅]/u; +var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; +var r = /[\u{1F1EF}\u{1F1F5}]/u; +var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/; +var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u; +var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; +var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; +var r = new RegExp("[πŸ‘]", ""); +var r = new RegExp('[πŸ‘]', ``); +var r = new RegExp("[πŸ‘]", flags); +var r = new RegExp("[\uD83D\uDC4D]", ""); +var r = new RegExp("/(?<=[πŸ‘])", ""); +var r = new RegExp("[Á]", ""); +var r = new RegExp("[Á]", "u"); +var r = new RegExp("[\u0041\u0301]", ""); +var r = new RegExp("[\u0041\u0301]", "u"); +var r = new RegExp("[\u{41}\u{301}]", "u"); +var r = new RegExp("[❇️]", ""); +var r = new RegExp("[❇️]", "u"); +var r = new RegExp("[\u2747\uFE0F]", ""); +var r = new RegExp("[\u2747\uFE0F]", "u"); +var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); +var r = new RegExp("[πŸ‘ΆπŸ»]", ""); +var r = new RegExp("[πŸ‘ΆπŸ»]", "u"); +var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); +var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); +var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); +var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); +var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); +var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`); +var r = new RegExp("[πŸ‡―πŸ‡΅]"); +var r = new RegExp("[πŸ‡―πŸ‡΅]",); +var r = new RegExp("[πŸ‡―πŸ‡΅]", "u"); +var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); +var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); +var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", ""); +var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "u"); +var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); +var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); +var r = new globalThis.RegExp("[❇️]", ""); +var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u"); +var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", ""); +var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); +``` + +# Diagnostics +``` +invalid.js:1:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + > 1 β”‚ var r = /[πŸ‘]/; + β”‚ ^^^^^^ + 2 β”‚ var r = /[\uD83D\uDC4D]/; + 3 β”‚ var r = /[πŸ‘]\\a/; + + i Safe fix: Add unicode u flag to regex + + 1 β”‚ varΒ·rΒ·=Β·/[πŸ‘]/u; + β”‚ + + +``` + +``` +invalid.js:2:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 1 β”‚ var r = /[πŸ‘]/; + > 2 β”‚ var r = /[\uD83D\uDC4D]/; + β”‚ ^^^^^^^^^^^^^^^^ + 3 β”‚ var r = /[πŸ‘]\\a/; + 4 β”‚ var r = /(?<=[πŸ‘])/; + + i Safe fix: Add unicode u flag to regex + + 2 β”‚ varΒ·rΒ·=Β·/[\uD83D\uDC4D]/u; + β”‚ + + +``` + +``` +invalid.js:3:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 1 β”‚ var r = /[πŸ‘]/; + 2 β”‚ var r = /[\uD83D\uDC4D]/; + > 3 β”‚ var r = /[πŸ‘]\\a/; + β”‚ ^^^^^^^^^ + 4 β”‚ var r = /(?<=[πŸ‘])/; + 5 β”‚ var r = /[AοΏ½]/; + + i Safe fix: Add unicode u flag to regex + + 3 β”‚ varΒ·rΒ·=Β·/[πŸ‘]\\a/u; + β”‚ + + +``` + +``` +invalid.js:4:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 2 β”‚ var r = /[\uD83D\uDC4D]/; + 3 β”‚ var r = /[πŸ‘]\\a/; + > 4 β”‚ var r = /(?<=[πŸ‘])/; + β”‚ ^^^^^^^^^^^ + 5 β”‚ var r = /[AοΏ½]/; + 6 β”‚ var r = /[AοΏ½]/u; + + i Safe fix: Add unicode u flag to regex + + 4 β”‚ varΒ·rΒ·=Β·/(?<=[πŸ‘])/u; + β”‚ + + +``` + +``` +invalid.js:5:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 3 β”‚ var r = /[πŸ‘]\\a/; + 4 β”‚ var r = /(?<=[πŸ‘])/; + > 5 β”‚ var r = /[AοΏ½]/; + β”‚ ^^^^^ + 6 β”‚ var r = /[AοΏ½]/u; + 7 β”‚ var r = /[\u0041\u0301]/; + + +``` + +``` +invalid.js:6:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 4 β”‚ var r = /(?<=[πŸ‘])/; + 5 β”‚ var r = /[AοΏ½]/; + > 6 β”‚ var r = /[AοΏ½]/u; + β”‚ ^^^^^^ + 7 β”‚ var r = /[\u0041\u0301]/; + 8 β”‚ var r = /[\u0041\u0301]/u; + + +``` + +``` +invalid.js:7:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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; + + +``` + +``` +invalid.js:8:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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 = /[❇�]/; + + +``` + +``` +invalid.js:9:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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; + + +``` + +``` +invalid.js:10:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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]/; + + +``` + +``` +invalid.js:11:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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; + + +``` + +``` +invalid.js:12:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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; + + +``` + +``` +invalid.js:13:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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 = /[πŸ‘ΆπŸ»]/; + + +``` + +``` +invalid.js:14:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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; + + +``` + +``` +invalid.js:15:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 15 β”‚ varΒ·rΒ·=Β·/[πŸ‘ΆπŸ»]/u; + β”‚ + + +``` + +``` +invalid.js:16:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 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; + + +``` + +``` +invalid.js:17:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 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 = /[πŸ‡―πŸ‡΅]/; + + +``` + +``` +invalid.js:18:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 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; + + +``` + +``` +invalid.js:19:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 19 β”‚ varΒ·rΒ·=Β·/[πŸ‡―πŸ‡΅]/u; + β”‚ + + +``` + +``` +invalid.js:20:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 18 18 β”‚ var r = /[\u{1F476}\u{1F3FB}]/u; + 19 19 β”‚ var r = /[πŸ‡―πŸ‡΅]/; + 20 β”‚ - varΒ·rΒ·=Β·/[πŸ‡―πŸ‡΅]/i; + 20 β”‚ + varΒ·rΒ·=Β·/[πŸ‡―πŸ‡΅]/iu; + 21 21 β”‚ var r = /[πŸ‡―πŸ‡΅]/u; + 22 22 β”‚ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; + + +``` + +``` +invalid.js:21:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 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; + + +``` + +``` +invalid.js:22:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 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 = /[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/; + + +``` + +``` +invalid.js:23:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 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; + + +``` + +``` +invalid.js:24:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 24 β”‚ varΒ·rΒ·=Β·/[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]/u; + β”‚ + + +``` + +``` +invalid.js:25:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 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; + + +``` + +``` +invalid.js:26:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 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("[πŸ‘]", ""); + + +``` + +``` +invalid.js:27:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 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('[πŸ‘]', ``); + + +``` + +``` +invalid.js:28:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 26 26 β”‚ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; + 27 27 β”‚ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + 28 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‘]",Β·""); + 28 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‘]","u"); + 29 29 β”‚ var r = new RegExp('[πŸ‘]', ``); + 30 30 β”‚ var r = new RegExp("[πŸ‘]", flags); + + +``` + +``` +invalid.js:29:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 27 27 β”‚ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + 28 28 β”‚ var r = new RegExp("[πŸ‘]", ""); + 29 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp('[πŸ‘]',Β·``); + 29 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp('[πŸ‘]',`u`); + 30 30 β”‚ var r = new RegExp("[πŸ‘]", flags); + 31 31 β”‚ var r = new RegExp("[\uD83D\uDC4D]", ""); + + +``` + +``` +invalid.js:30:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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("/(?<=[πŸ‘])", ""); + + +``` + +``` +invalid.js:31:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 29 29 β”‚ var r = new RegExp('[πŸ‘]', ``); + 30 30 β”‚ var r = new RegExp("[πŸ‘]", flags); + 31 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp("[\uD83D\uDC4D]",Β·""); + 31 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp("[\uD83D\uDC4D]","u"); + 32 32 β”‚ var r = new RegExp("/(?<=[πŸ‘])", ""); + 33 33 β”‚ var r = new RegExp("[AοΏ½]", ""); + + +``` + +``` +invalid.js:32:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 30 30 β”‚ var r = new RegExp("[πŸ‘]", flags); + 31 31 β”‚ var r = new RegExp("[\uD83D\uDC4D]", ""); + 32 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp("/(?<=[πŸ‘])",Β·""); + 32 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp("/(?<=[πŸ‘])","u"); + 33 33 β”‚ var r = new RegExp("[AοΏ½]", ""); + 34 34 β”‚ var r = new RegExp("[AοΏ½]", "u"); + + +``` + +``` +invalid.js:33:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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]", ""); + + +``` + +``` +invalid.js:34:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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"); + + +``` + +``` +invalid.js:35:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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"); + + +``` + +``` +invalid.js:36:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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("[❇�]", ""); + + +``` + +``` +invalid.js:37:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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"); + + +``` + +``` +invalid.js:38:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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]", ""); + + +``` + +``` +invalid.js:39:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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"); + + +``` + +``` +invalid.js:40:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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"); + + +``` + +``` +invalid.js:41:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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("[πŸ‘ΆπŸ»]", ""); + + +``` + +``` +invalid.js:42:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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"); + + +``` + +``` +invalid.js:43:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 41 41 β”‚ var r = new RegExp("[\u2747\uFE0F]", "u"); + 42 42 β”‚ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); + 43 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‘ΆπŸ»]",Β·""); + 43 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‘ΆπŸ»]","u"); + 44 44 β”‚ var r = new RegExp("[πŸ‘ΆπŸ»]", "u"); + 45 45 β”‚ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + + +``` + +``` +invalid.js:44:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 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"); + + +``` + +``` +invalid.js:45:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 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("[πŸ‡―πŸ‡΅]", ""); + + +``` + +``` +invalid.js:46:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 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"); + + +``` + +``` +invalid.js:47:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 45 45 β”‚ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + 46 46 β”‚ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + 47 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‡―πŸ‡΅]",Β·""); + 47 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‡―πŸ‡΅]","u"); + 48 48 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); + 49 49 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); + + +``` + +``` +invalid.js:48:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 46 46 β”‚ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + 47 47 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); + 48 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‡―πŸ‡΅]",Β·"i"); + 48 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‡―πŸ‡΅]","iu"); + 49 49 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); + 50 50 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`); + + +``` + +``` +invalid.js:49:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 47 47 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", ""); + 48 48 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); + 49 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp('[πŸ‡―πŸ‡΅]',Β·`i`); + 49 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp('[πŸ‡―πŸ‡΅]',`iu`); + 50 50 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`); + 51 51 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]"); + + +``` + +``` +invalid.js:50:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 48 48 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]", "i"); + 49 49 β”‚ var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`); + 50 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp('[πŸ‡―πŸ‡΅]',Β·`${foo}`); + 50 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp('[πŸ‡―πŸ‡΅]',`${foo}u`); + 51 51 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]"); + 52 52 β”‚ var r = new RegExp("[πŸ‡―πŸ‡΅]",); + + +``` + +``` +invalid.js:51:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 51 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‡―πŸ‡΅]","u"); + β”‚ ++++ + +``` + +``` +invalid.js:52:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 52 β”‚ varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‡―πŸ‡΅]","u"); + β”‚ +++ + +``` + +``` +invalid.js:53:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 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"); + + +``` + +``` +invalid.js:54:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 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("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", ""); + + +``` + +``` +invalid.js:55:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 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"); + + +``` + +``` +invalid.js:56:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 54 54 β”‚ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); + 55 55 β”‚ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); + 56 β”‚ - varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]",Β·""); + 56 β”‚ + varΒ·rΒ·=Β·newΒ·RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]","u"); + 57 57 β”‚ var r = new RegExp("[πŸ‘¨οΏ½πŸ‘©οΏ½πŸ‘¦]", "u"); + 58 58 β”‚ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); + + +``` + +``` +invalid.js:57:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 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"); + + +``` + +``` +invalid.js:58:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 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("[❇�]", ""); + + +``` + +``` +invalid.js:59:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 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"); + + +``` + +``` +invalid.js:60:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 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("[πŸ‡―πŸ‡΅]", ""); + + +``` + +``` +invalid.js:61:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 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"); + + +``` + +``` +invalid.js:62:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 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 Safe fix: Add unicode u flag to regex + + 60 60 β”‚ var r = new globalThis.RegExp("[❇�]", ""); + 61 61 β”‚ var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u"); + 62 β”‚ - varΒ·rΒ·=Β·newΒ·globalThis.RegExp("[πŸ‡―πŸ‡΅]",Β·""); + 62 β”‚ + varΒ·rΒ·=Β·newΒ·globalThis.RegExp("[πŸ‡―πŸ‡΅]","u"); + 63 63 β”‚ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + + +``` + +``` +invalid.js:63:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 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"); + β”‚ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js new file mode 100644 index 000000000000..4854e27c2b07 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js @@ -0,0 +1,49 @@ +var r = /[πŸ‘]/u; +var r = /[\\uD83D\\uDC4D]/u; +var r = /[\\u{1F44D}]/u; +var r = /❇️/; +var r = /Á/; +var r = /[❇]/; +var r = /πŸ‘ΆπŸ»/; +var r = /[πŸ‘Ά]/u; +var r = /πŸ‡―πŸ‡΅/; +var r = /[JP]/; +var r = /πŸ‘¨β€πŸ‘©β€πŸ‘¦/; + +// Ignore solo lead/tail surrogate. +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; + +// Ignore solo emoji modifier. +var r = /[\\u{1F3FB}]/u; +var r = /[\u{1F3FB}]/u; + +// Ignore solo regional indicator symbol. +var r = /[πŸ‡―]/u; +var r = /[πŸ‡΅]/u; + +// Ignore solo ZWJ. +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 +// var r = new RegExp('[Á] [ '); +// var r = RegExp('{ [Á]', 'u'); +// var r = new globalThis.RegExp('[Á] [ '); +// var r = globalThis.RegExp('{ [Á]', 'u'); + +// v flag +var r = /[πŸ‘]/v; +var r = /^[\q{πŸ‘ΆπŸ»}]$/v; +var r = /[πŸ‡―\q{abc}πŸ‡΅]/v; +var r = /[πŸ‡―[A]πŸ‡΅]/v; +var r = /[πŸ‡―[A--B]πŸ‡΅]/v; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js.snap new file mode 100644 index 000000000000..575f07754a4b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js.snap @@ -0,0 +1,58 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +var r = /[πŸ‘]/u; +var r = /[\\uD83D\\uDC4D]/u; +var r = /[\\u{1F44D}]/u; +var r = /❇️/; +var r = /Á/; +var r = /[❇]/; +var r = /πŸ‘ΆπŸ»/; +var r = /[πŸ‘Ά]/u; +var r = /πŸ‡―πŸ‡΅/; +var r = /[JP]/; +var r = /πŸ‘¨β€πŸ‘©β€πŸ‘¦/; + +// Ignore solo lead/tail surrogate. +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; + +// Ignore solo emoji modifier. +var r = /[\\u{1F3FB}]/u; +var r = /[\u{1F3FB}]/u; + +// Ignore solo regional indicator symbol. +var r = /[πŸ‡―]/u; +var r = /[πŸ‡΅]/u; + +// Ignore solo ZWJ. +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 +// var r = new RegExp('[Á] [ '); +// var r = RegExp('{ [Á]', 'u'); +// var r = new globalThis.RegExp('[Á] [ '); +// var r = globalThis.RegExp('{ [Á]', 'u'); + +// v flag +var r = /[πŸ‘]/v; +var r = /^[\q{πŸ‘ΆπŸ»}]$/v; +var r = /[πŸ‡―\q{abc}πŸ‡΅]/v; +var r = /[πŸ‡―[A]πŸ‡΅]/v; +var r = /[πŸ‡―[A--B]πŸ‡΅]/v; +``` + + diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index 347990076c9f..7568fb6ed104 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2786,6 +2786,15 @@ pub struct Nursery { #[bpaf(long("no-implicit-any-let"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] pub no_implicit_any_let: Option, + #[doc = "Disallow characters made with multiple code points in character class syntax."] + #[bpaf( + long("no-misleading-character-class"), + argument("on|off|warn"), + optional, + hide + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_misleading_character_class: Option, #[doc = "Disallow unused imports."] #[bpaf(long("no-unused-imports"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] @@ -2860,6 +2869,9 @@ impl MergeWith for Nursery { if let Some(no_implicit_any_let) = other.no_implicit_any_let { self.no_implicit_any_let = Some(no_implicit_any_let); } + if let Some(no_misleading_character_class) = other.no_misleading_character_class { + self.no_misleading_character_class = Some(no_misleading_character_class); + } if let Some(no_unused_imports) = other.no_unused_imports { self.no_unused_imports = Some(no_unused_imports); } @@ -2899,12 +2911,13 @@ impl MergeWith for Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 14] = [ + pub(crate) const GROUP_RULES: [&'static str; 15] = [ "noAriaHiddenOnFocusable", "noDefaultExport", "noDuplicateJsonKeys", "noEmptyBlockStatements", "noImplicitAnyLet", + "noMisleadingCharacterClass", "noUnusedImports", "noUnusedPrivateClassMembers", "noUselessLoneBlockStatements", @@ -2927,11 +2940,11 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 14] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 15] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -2946,6 +2959,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended(&self) -> bool { @@ -2987,51 +3001,56 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_unused_imports.as_ref() { + if let Some(rule) = self.no_misleading_character_class.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_unused_private_class_members.as_ref() { + if let Some(rule) = self.no_unused_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { + if let Some(rule) = self.no_unused_private_class_members.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.use_await.as_ref() { + if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.use_for_of.as_ref() { + if let Some(rule) = self.use_await.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_for_of.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.use_regex_literals.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.use_valid_aria_role.as_ref() { + if let Some(rule) = self.use_regex_literals.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } + if let Some(rule) = self.use_valid_aria_role.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -3061,51 +3080,56 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_unused_imports.as_ref() { + if let Some(rule) = self.no_misleading_character_class.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_unused_private_class_members.as_ref() { + if let Some(rule) = self.no_unused_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { + if let Some(rule) = self.no_unused_private_class_members.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.use_await.as_ref() { + if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.use_for_of.as_ref() { + if let Some(rule) = self.use_await.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_for_of.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.use_regex_literals.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.use_valid_aria_role.as_ref() { + if let Some(rule) = self.use_regex_literals.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } + if let Some(rule) = self.use_valid_aria_role.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3119,7 +3143,7 @@ impl Nursery { pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 6] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 14] { + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 15] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] @@ -3147,6 +3171,7 @@ impl Nursery { "noDuplicateJsonKeys" => self.no_duplicate_json_keys.as_ref(), "noEmptyBlockStatements" => self.no_empty_block_statements.as_ref(), "noImplicitAnyLet" => self.no_implicit_any_let.as_ref(), + "noMisleadingCharacterClass" => self.no_misleading_character_class.as_ref(), "noUnusedImports" => self.no_unused_imports.as_ref(), "noUnusedPrivateClassMembers" => self.no_unused_private_class_members.as_ref(), "noUselessLoneBlockStatements" => self.no_useless_lone_block_statements.as_ref(), diff --git a/crates/biome_service/src/configuration/parse/json/rules.rs b/crates/biome_service/src/configuration/parse/json/rules.rs index a3840d0fb923..86cc668cdd1a 100644 --- a/crates/biome_service/src/configuration/parse/json/rules.rs +++ b/crates/biome_service/src/configuration/parse/json/rules.rs @@ -937,6 +937,13 @@ impl Deserializable for Nursery { diagnostics, ); } + "noMisleadingCharacterClass" => { + result.no_misleading_character_class = Deserializable::deserialize( + &value, + "noMisleadingCharacterClass", + diagnostics, + ); + } "noUnusedImports" => { result.no_unused_imports = Deserializable::deserialize(&value, "noUnusedImports", diagnostics); @@ -1003,6 +1010,7 @@ impl Deserializable for Nursery { "noDuplicateJsonKeys", "noEmptyBlockStatements", "noImplicitAnyLet", + "noMisleadingCharacterClass", "noUnusedImports", "noUnusedPrivateClassMembers", "noUselessLoneBlockStatements", diff --git a/crates/biome_service/tests/invalid/hooks_incorrect_options.json.snap b/crates/biome_service/tests/invalid/hooks_incorrect_options.json.snap index b2ae35370b28..b26aafc7dd78 100644 --- a/crates/biome_service/tests/invalid/hooks_incorrect_options.json.snap +++ b/crates/biome_service/tests/invalid/hooks_incorrect_options.json.snap @@ -22,6 +22,7 @@ hooks_incorrect_options.json:6:5 deserialize ━━━━━━━━━━━ - noDuplicateJsonKeys - noEmptyBlockStatements - noImplicitAnyLet + - noMisleadingCharacterClass - noUnusedImports - noUnusedPrivateClassMembers - noUselessLoneBlockStatements diff --git a/crates/biome_service/tests/invalid/hooks_missing_name.json.snap b/crates/biome_service/tests/invalid/hooks_missing_name.json.snap index 479320a69bf3..695c5e1d6229 100644 --- a/crates/biome_service/tests/invalid/hooks_missing_name.json.snap +++ b/crates/biome_service/tests/invalid/hooks_missing_name.json.snap @@ -22,6 +22,7 @@ hooks_missing_name.json:6:5 deserialize ━━━━━━━━━━━━━ - noDuplicateJsonKeys - noEmptyBlockStatements - noImplicitAnyLet + - noMisleadingCharacterClass - noUnusedImports - noUnusedPrivateClassMembers - noUselessLoneBlockStatements diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 650c77a60b4c..e4834ae8af8d 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -797,6 +797,10 @@ export interface Nursery { * Disallow use of implicit any type on variable declarations. */ noImplicitAnyLet?: RuleConfiguration; + /** + * Disallow characters made with multiple code points in character class syntax. + */ + noMisleadingCharacterClass?: RuleConfiguration; /** * Disallow unused imports. */ @@ -1506,6 +1510,7 @@ export type Category = | "lint/nursery/noDuplicateJsonKeys" | "lint/nursery/noEmptyBlockStatements" | "lint/nursery/noImplicitAnyLet" + | "lint/nursery/noMisleadingCharacterClass" | "lint/nursery/noUnusedImports" | "lint/nursery/noUnusedPrivateClassMembers" | "lint/nursery/noUselessLoneBlockStatements" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 11133c005b1e..fcdc498a74f4 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1147,6 +1147,13 @@ { "type": "null" } ] }, + "noMisleadingCharacterClass": { + "description": "Disallow characters made with multiple code points in character class syntax.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noUnusedImports": { "description": "Disallow unused imports.", "anyOf": [ diff --git a/website/src/components/generated/NumberOfRules.astro b/website/src/components/generated/NumberOfRules.astro index 59aabab17f0b..7131a8124a29 100644 --- a/website/src/components/generated/NumberOfRules.astro +++ b/website/src/components/generated/NumberOfRules.astro @@ -1,2 +1,2 @@ -

Biome's linter has a total of 179 rules

\ No newline at end of file +

Biome's linter has a total of 180 rules

\ No newline at end of file diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 1a4359c5b100..40033ff85811 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -232,6 +232,7 @@ Rules that belong to this group are not subject to semantic versionany type on variable declarations. | | +| [noMisleadingCharacterClass](/linter/rules/no-misleading-character-class) | Disallow characters made with multiple code points in character class syntax. | πŸ”§ | | [noUnusedImports](/linter/rules/no-unused-imports) | Disallow unused imports. | πŸ”§ | | [noUnusedPrivateClassMembers](/linter/rules/no-unused-private-class-members) | Disallow unused private class members | ⚠️ | | [noUselessLoneBlockStatements](/linter/rules/no-useless-lone-block-statements) | Disallow unnecessary nested block statements. | ⚠️ | diff --git a/website/src/content/docs/linter/rules/no-misleading-character-class.md b/website/src/content/docs/linter/rules/no-misleading-character-class.md new file mode 100644 index 000000000000..9d932840d116 --- /dev/null +++ b/website/src/content/docs/linter/rules/no-misleading-character-class.md @@ -0,0 +1,124 @@ +--- +title: noMisleadingCharacterClass (since vnext) +--- + +**Diagnostic Category: `lint/nursery/noMisleadingCharacterClass`** + +:::caution +This rule is part of the [nursery](/linter/rules/#nursery) group. +::: + +Disallow characters made with multiple code points in character class syntax. + +Unicode includes the characters which are made with multiple code points. e.g. Á, πŸ‡―πŸ‡΅, πŸ‘¨β€πŸ‘©β€πŸ‘¦. +A RegExp character class `/[abc]/` cannot handle characters with multiple code points. +For example, the character `❇️` consists of two code points: `❇` (U+2747) and `VARIATION SELECTOR-16` (U+FE0F). +If this character is in a RegExp character class, it will match to either `❇` or `VARIATION SELECTOR-16` rather than `❇️`. +This rule reports the regular expressions which include multiple code point characters in character class syntax. + +Source: https://eslint.org/docs/latest/rules/no-misleading-character-class + +## Examples + +### Invalid + +```jsx +/^[Á]$/u; +``` + +

nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
+
+  ⚠ Unexpected combined character in the character class.
+  
+  > 1 β”‚ /^[Á]$/u;
+      β”‚ ^^^^^^^^
+    2 β”‚ 
+  
+
+ +```jsx +/^[❇️]$/u; +``` + +
nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
+
+  ⚠ Unexpected combined character in the character class.
+  
+  > 1 β”‚ /^[❇️]$/u;
+      β”‚ ^^^^^^^^
+    2 β”‚ 
+  
+
+ +```jsx +/^[πŸ‘ΆπŸ»]$/u; +``` + +
nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
+
+  ⚠ Unexpected modified Emoji in the character class. 
+  
+  > 1 β”‚ /^[πŸ‘ΆπŸ»]$/u;
+      β”‚ ^^^^^^^^^^^
+    2 β”‚ 
+  
+
+ +```jsx +/^[πŸ‡―πŸ‡΅]$/u; +``` + +
nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
+
+  ⚠ Regional indicator symbol characters should not be used in the character class.
+  
+  > 1 β”‚ /^[πŸ‡―πŸ‡΅]$/u;
+      β”‚ ^^^^^^^^^
+    2 β”‚ 
+  
+
+ +```jsx +/^[πŸ‘¨β€πŸ‘©β€πŸ‘¦]$/u; +``` + +
nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
+
+  ⚠ Unexpected joined character sequence in character class.
+  
+  > 1 β”‚ /^[πŸ‘¨β€πŸ‘©β€πŸ‘¦]$/u;
+      β”‚ ^^^^^^^^^^^^^
+    2 β”‚ 
+  
+
+ +```jsx +/^[πŸ‘]$/; // surrogate pair without u flag +``` + +
nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass  FIXABLE  ━━━━━━━━━━
+
+  ⚠ Unexpected surrogate pair in character class. Use the 'u' flag.
+  
+  > 1 β”‚ /^[πŸ‘]$/; // surrogate pair without u flag
+      β”‚ ^^^^^^^^
+    2 β”‚ 
+  
+  β„Ή Safe fix: Add unicode u flag to regex
+  
+    1 β”‚ /^[πŸ‘]$/u;Β·//Β·surrogateΒ·pairΒ·withoutΒ·uΒ·flag
+      β”‚         +                                  
+
+ +## Valid + +```jsx +/^[abc]$/; +/^[πŸ‘]$/u; +/^[\q{πŸ‘ΆπŸ»}]$/v; +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)