From 7481704ffdbac72f4678836e9a10a89cdb72ef60 Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Mon, 26 Oct 2026 22:17:35 +0100 Subject: [PATCH 1/2] fix(all): accept RegexOptions.NonBacktracking --- src/fable-library-dart/RegExp.dart | 5 +++-- src/fable-library-rust/src/RegExp.rs | 3 ++- src/fable-library-ts/RegExp.ts | 5 +++-- tests/Beam/RegexTests.fs | 8 ++++++++ tests/Dart/src/RegexTests.fs | 7 +++++++ tests/Js/Main/RegexTests.fs | 7 +++++++ tests/Python/TestRegex.fs | 8 ++++++++ tests/Rust/tests/src/RegexTests.fs | 8 ++++++++ 8 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/fable-library-dart/RegExp.dart b/src/fable-library-dart/RegExp.dart index 80e8263eb4..5681dceb9d 100644 --- a/src/fable-library-dart/RegExp.dart +++ b/src/fable-library-dart/RegExp.dart @@ -61,9 +61,10 @@ RegExp create(String pattern, [int options = 0]) { // * Compiled: 0x0008 (ignored) // * Singleline: 0x0010 // * ECMAScript: 0x0100 (ignored) - if ((options & ~(1 ^ 2 ^ 8 ^ 16 ^ 256)) != 0) { + // * NonBacktracking: 0x0400 (ignored) + if ((options & ~(1 ^ 2 ^ 8 ^ 16 ^ 256 ^ 1024)) != 0) { throw Exception( - 'RegexOptions only supports: IgnoreCase, Multiline, Compiled, Singleline and ECMAScript'); + 'RegexOptions only supports: IgnoreCase, Multiline, Compiled, Singleline, ECMAScript and NonBacktracking'); } return RegExp(pattern, unicode: true, diff --git a/src/fable-library-rust/src/RegExp.rs b/src/fable-library-rust/src/RegExp.rs index 57071f7e9b..366728c947 100644 --- a/src/fable-library-rust/src/RegExp.rs +++ b/src/fable-library-rust/src/RegExp.rs @@ -193,7 +193,8 @@ pub mod RegExp_ { // * Singleline: 0x0010 // * IgnorePatternWhitespace: 0x0020 // * ECMAScript: 0x0100 (ignored) - if ((options & !(1 | 2 | 8 | 16 | 32 | 256)) != 0) { + // * NonBacktracking: 0x0400 (ignored) + if ((options & !(1 | 2 | 8 | 16 | 32 | 256 | 1024)) != 0) { panic!("Unsupported Regex Option"); } let regex_ = regex::RegexBuilder::new(pat.as_str()) diff --git a/src/fable-library-ts/RegExp.ts b/src/fable-library-ts/RegExp.ts index de54752f71..e49b56bf51 100644 --- a/src/fable-library-ts/RegExp.ts +++ b/src/fable-library-ts/RegExp.ts @@ -9,8 +9,9 @@ export function create(pattern: string, options = 0) { // * Compiled: 0x0008 (ignored) // * Singleline: 0x0010 // * ECMAScript: 0x0100 (ignored) - if ((options & ~(1 ^ 2 ^ 8 ^ 16 ^ 256)) !== 0) { - throw new Exception("RegexOptions only supports: IgnoreCase, Multiline, Compiled, Singleline and ECMAScript"); + // * NonBacktracking: 0x0400 (ignored) + if ((options & ~(1 ^ 2 ^ 8 ^ 16 ^ 256 ^ 1024)) !== 0) { + throw new Exception("RegexOptions only supports: IgnoreCase, Multiline, Compiled, Singleline, ECMAScript and NonBacktracking"); } // Set always global and unicode flags for compatibility with dotnet, see #2925 let flags = "gu"; diff --git a/tests/Beam/RegexTests.fs b/tests/Beam/RegexTests.fs index 15e8d85953..4b0d15432b 100644 --- a/tests/Beam/RegexTests.fs +++ b/tests/Beam/RegexTests.fs @@ -164,6 +164,14 @@ let ``test Regex.Unescape works`` () = // --- Options --- +[] +let ``test RegexOptions.NonBacktracking is accepted`` () = + let options = RegexOptions.NonBacktracking ||| RegexOptions.IgnoreCase + let r = Regex("ab", options) + r.IsMatch("xABx") |> equal true + Regex.IsMatch("aaab", "(a+)*b", RegexOptions.NonBacktracking) |> equal true + Regex.Replace("a1b2", "\\d", "-", RegexOptions.NonBacktracking) |> equal "a-b-" + [] let ``test Regex.Options works`` () = let r = Regex("\\d+", RegexOptions.IgnoreCase ||| RegexOptions.Multiline) diff --git a/tests/Dart/src/RegexTests.fs b/tests/Dart/src/RegexTests.fs index 6c7020f958..acc8186619 100644 --- a/tests/Dart/src/RegexTests.fs +++ b/tests/Dart/src/RegexTests.fs @@ -41,6 +41,13 @@ let tests() = let r = Regex("[a-z]", options) int r.Options |> equal 257 + testCase "RegexOptions.NonBacktracking is accepted" <| fun _ -> + let options = RegexOptions.NonBacktracking ||| RegexOptions.IgnoreCase + let r = Regex("ab", options) + r.IsMatch("xABx") |> equal true + Regex.IsMatch("aaab", "(a+)*b", RegexOptions.NonBacktracking) |> equal true + Regex.Replace("a1b2", "\\d", "-", RegexOptions.NonBacktracking) |> equal "a-b-" + testCase "Regex.IsMatch with IgnoreCase and Multiline works" <| fun _ -> let str = "ab\ncd" let option1 = RegexOptions.IgnoreCase diff --git a/tests/Js/Main/RegexTests.fs b/tests/Js/Main/RegexTests.fs index 657f48b715..934d388c14 100644 --- a/tests/Js/Main/RegexTests.fs +++ b/tests/Js/Main/RegexTests.fs @@ -37,6 +37,13 @@ let tests = let r = Regex("[a-z]", options) int r.Options |> equal 257 + testCase "RegexOptions.NonBacktracking is accepted" <| fun _ -> + let options = RegexOptions.NonBacktracking ||| RegexOptions.IgnoreCase + let r = Regex("ab", options) + r.IsMatch("xABx") |> equal true + Regex.IsMatch("aaab", "(a+)*b", RegexOptions.NonBacktracking) |> equal true + Regex.Replace("a1b2", "\\d", "-", RegexOptions.NonBacktracking) |> equal "a-b-" + testCase "Regex.IsMatch with IgnoreCase and Multiline works" <| fun _ -> let str = "ab\ncd" let option1 = RegexOptions.IgnoreCase diff --git a/tests/Python/TestRegex.fs b/tests/Python/TestRegex.fs index de19016043..faa854f1e3 100644 --- a/tests/Python/TestRegex.fs +++ b/tests/Python/TestRegex.fs @@ -46,6 +46,14 @@ let ``test Literal regex works with whitespace characters`` () = // See #2635 Regex("\\t").IsMatch("foo\tbar") |> equal true Regex(@"\\t").IsMatch("foo\tbar") |> equal false +[] +let ``test RegexOptions.NonBacktracking is accepted`` () = + let options = RegexOptions.NonBacktracking ||| RegexOptions.IgnoreCase + let r = Regex("ab", options) + r.IsMatch("xABx") |> equal true + Regex.IsMatch("aaab", "(a+)*b", RegexOptions.NonBacktracking) |> equal true + Regex.Replace("a1b2", "\\d", "-", RegexOptions.NonBacktracking) |> equal "a-b-" + [] let ``test Regex.Options works`` () = let option1 = int RegexOptions.IgnoreCase diff --git a/tests/Rust/tests/src/RegexTests.fs b/tests/Rust/tests/src/RegexTests.fs index 8ed732e583..b198b0353e 100644 --- a/tests/Rust/tests/src/RegexTests.fs +++ b/tests/Rust/tests/src/RegexTests.fs @@ -31,6 +31,14 @@ let ``Literal regex works with whitespace characters`` () = // See #2635 Regex("\\t").IsMatch("foo\tbar") |> equal true Regex(@"\\t").IsMatch("foo\tbar") |> equal false +[] +let ``RegexOptions.NonBacktracking is accepted`` () = + let options = RegexOptions.NonBacktracking ||| RegexOptions.IgnoreCase + let r = Regex("ab", options) + r.IsMatch("xABx") |> equal true + Regex.IsMatch("aaab", "(a+)*b", RegexOptions.NonBacktracking) |> equal true + Regex.Replace("a1b2", "\\d", "-", RegexOptions.NonBacktracking) |> equal "a-b-" + [] let ``Regex.Options works`` () = let option1 = RegexOptions.IgnoreCase From ac85e4058752ca215b5d58df3c831091235fd51f Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Mon, 26 Oct 2026 22:42:23 +0100 Subject: [PATCH 2/2] fix(js/ts): fold NonBacktracking into regex literals --- src/Fable.Transforms/Replacements.Util.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Fable.Transforms/Replacements.Util.fs b/src/Fable.Transforms/Replacements.Util.fs index 5f8f6f2f8d..89fbe6143d 100644 --- a/src/Fable.Transforms/Replacements.Util.fs +++ b/src/Fable.Transforms/Replacements.Util.fs @@ -1293,6 +1293,7 @@ let (|RegexFlags|_|) e = | 8 -> Some [] // Compiled flag (ignored) | 16 -> Some [ RegexSingleline ] | 256 -> Some [] // ECMAScript flag (ignored) + | 1024 -> Some [] // NonBacktracking flag (ignored) | _ -> None | Operation(Binary(BinaryOrBitwise, flags1, flags2), _, _, _) -> match getFlags flags1, getFlags flags2 with