diff --git a/CHANGELOG.md b/CHANGELOG.md index e44ca70b9c5a..e933bf81c503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,14 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b Contributed by @Conaclos +- [useFilenamingConvention](https://biomejs.dev/linter/rules/use-filenaming-convention) and [useNamingConvention](https://biomejs.dev/linter/rules/use-naming-convention) `match` options now accept case-insensitive and case-sensitive groups. + + By default, the regular expression in `match` is case-sensitive. + You can now make it case-insensitive by using a case-insensitive group `(?i:)`. + For example, the regular expression `(?i:a)` matches `a` and `A`. + + Contributed by @Conaclos + ### Parser #### New features diff --git a/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs b/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs index 6f1e1821a492..49cd4525bce1 100644 --- a/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs +++ b/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs @@ -121,6 +121,7 @@ declare_lint_rule! { /// - Alternations `|` /// - Capturing groups `()` /// - Non-capturing groups `(?:)` + /// - Case-insensitive groups `(?i:)` and case-sensitive groups `(?-i:)` /// - A limited set of escaped characters including all special characters /// and regular string escape characters `\f`, `\n`, `\r`, `\t`, `\v` /// diff --git a/crates/biome_js_analyze/src/lint/style/use_naming_convention.rs b/crates/biome_js_analyze/src/lint/style/use_naming_convention.rs index ceddb1dc0af4..d75fce7ce30c 100644 --- a/crates/biome_js_analyze/src/lint/style/use_naming_convention.rs +++ b/crates/biome_js_analyze/src/lint/style/use_naming_convention.rs @@ -603,6 +603,7 @@ declare_lint_rule! { /// - Alternations `|` /// - Capturing groups `()` /// - Non-capturing groups `(?:)` + /// - Case-insensitive groups `(?i:)` and case-sensitive groups `(?-i:)` /// - A limited set of escaped characters including all special characters /// and regular string escape characters `\f`, `\n`, `\r`, `\t`, `\v` /// diff --git a/crates/biome_js_analyze/src/utils/restricted_regex.rs b/crates/biome_js_analyze/src/utils/restricted_regex.rs index 3cf83bbab552..775c3805cc75 100644 --- a/crates/biome_js_analyze/src/utils/restricted_regex.rs +++ b/crates/biome_js_analyze/src/utils/restricted_regex.rs @@ -11,6 +11,11 @@ use biome_deserialize_macros::Deserializable; /// - Alternations `|` /// - Capturing groups `()` /// - Non-capturing groups `(?:)` +/// - Non-capturing groups with flags `(?flags:)` and negated flags `(?-flags:)` +/// Supported flags: +/// - `i`: ignore case +/// - `m`: multiline mode +/// - `s`: single line mode (`.` matches also `\n`) /// - A limited set of escaped characters including all regex special characters /// and regular string escape characters `\f`, `\n`, `\r`, `\t`, `\v` /// @@ -173,10 +178,31 @@ fn is_restricted_regex(pattern: &str) -> Result<(), regex::Error> { }; } Some(b':') => {} - _ => { - return Err(regex::Error::Syntax( - "Group flags `(?flags:)` are not supported.".to_string(), - )); + c => { + let mut current = c; + while matches!(current, Some(b'i' | b'm' | b's' | b'-')) { + current = it.next() + } + match current { + Some(b':') => {} + Some(b')') => { + return Err(regex::Error::Syntax( + "Group modifiers `(?flags)` are not supported.".to_string(), + )); + } + Some(c) if c.is_ascii() => { + // SAFETY: `c` is ASCII according to the guard + let c = c as char; + return Err(regex::Error::Syntax(format!( + "Group flags `(?{c}:)` are not supported." + ))); + } + _ => { + return Err(regex::Error::Syntax( + "Unterminated non-capturing group.".to_string(), + )); + } + } } }, _ => {} @@ -197,7 +223,6 @@ mod tests { assert!(is_restricted_regex("a$").is_err()); assert!(is_restricted_regex(r"\").is_err()); assert!(is_restricted_regex(r"\p{L}").is_err()); - assert!(is_restricted_regex(r"(?i:)").is_err()); assert!(is_restricted_regex(r"(?=a)").is_err()); assert!(is_restricted_regex(r"(?!a)").is_err()); assert!(is_restricted_regex(r"(?:a)").is_err()); @@ -210,6 +235,9 @@ mod tests { assert!(is_restricted_regex("").is_ok()); assert!(is_restricted_regex("abc").is_ok()); assert!(is_restricted_regex("(?:a)(.+)z").is_ok()); + assert!(is_restricted_regex("(?ims:a)(.+)z").is_ok()); + assert!(is_restricted_regex("(?-ims:a)(.+)z").is_ok()); + assert!(is_restricted_regex("(?i-ms:a)(.+)z").is_ok()); assert!(is_restricted_regex("[A-Z][^a-z]").is_ok()); assert!(is_restricted_regex(r"\n\t\v\f").is_ok()); assert!(is_restricted_regex("([^_])").is_ok());