diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index 9a719ee7e..abc466a14 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [Python] Remove `$` sign when reporting an error from `assert_equal` and `assert_not_equal` (#3878) (by @joprice) +### Fixed + +* [JS/TS] Fix escaping of `{` and `}` in FormattableString (#3890) (by @roboz0r) + ## 4.20.0 - 2024-09-04 ### Added diff --git a/src/Fable.Transforms/BabelPrinter.fs b/src/Fable.Transforms/BabelPrinter.fs index e4ba357eb..71749afc9 100644 --- a/src/Fable.Transforms/BabelPrinter.fs +++ b/src/Fable.Transforms/BabelPrinter.fs @@ -573,7 +573,12 @@ module PrinterExtensions = | Literal.DirectiveLiteral(literal) -> printer.Print(literal) | StringTemplate(tag, parts, values, loc) -> let escape str = - Regex.Replace(str, @"(? value.replace(/{/g, '{{').replace(/}/g, '}}')); + return s.fmts - ? s.strs.reduce((acc, newPart, index) => acc + `{${String(index - 1) + s.fmts![index - 1]}}` + newPart) - : s.strs.reduce((acc, newPart, index) => acc + `{${index - 1}}` + newPart); + ? strs + .reduce((acc, newPart, index) => acc + `{${String(index - 1) + s.fmts![index - 1]}}` + newPart) + : strs + .reduce((acc, newPart, index) => acc + `{${index - 1}}` + newPart); } diff --git a/tests/Js/Main/StringTests.fs b/tests/Js/Main/StringTests.fs index 3707d3a48..bda0f73e7 100644 --- a/tests/Js/Main/StringTests.fs +++ b/tests/Js/Main/StringTests.fs @@ -1166,6 +1166,8 @@ let tests = testList "Strings" [ s4.Format |> equal "I have `backticks`" let s5: FormattableString = $"I have {{escaped braces}} and %%percentage%%" s5.Format |> equal "I have {{escaped braces}} and %percentage%" + let s6: FormattableString = $$$$"""I have {{escaped braces}} and %%percentage%%""" + s6.Format |> equal "I have {{{{escaped braces}}}} and %%percentage%%" () #if FABLE_COMPILER @@ -1184,5 +1186,43 @@ let tests = testList "Strings" [ s2.GetStrings() |> toArray |> equal [|""; "This is \""; "\" awesome!"|] let s3: FormattableString = $"""I have no holes""" s3.GetStrings() |> toArray |> equal [|"I have no holes"|] + + testCase "FormattableString fragments handle { and }" <| fun () -> +// TypeScript will complain because TemplateStringsArray is not equivalent to string[] +#if FABLE_COMPILER_TYPESCRIPT + let toArray = Seq.toArray +#else + let toArray = id +#endif + let classAttr = "item-panel" + let cssNew :FormattableString = $$""".{{classAttr}}:hover {background-color: #eee;}""" + let strs = cssNew.GetStrings() |> toArray + strs |> equal [|"."; ":hover {background-color: #eee;}"|] + let args = cssNew.GetArguments() + args |> equal [|classAttr|] + + let cssNew :FormattableString = $""".{classAttr}:hover {{background-color: #eee;}}""" + let strs = cssNew.GetStrings() |> toArray + strs |> equal [|"."; ":hover {background-color: #eee;}"|] + let args = cssNew.GetArguments() + args |> equal [|classAttr|] + + let cssNew :FormattableString = $".{classAttr}:hover {{background-color: #eee;}}" + let strs = cssNew.GetStrings() |> toArray + strs |> equal [|"."; ":hover {background-color: #eee;}"|] + let args = cssNew.GetArguments() + args |> equal [|classAttr|] + + let another :FormattableString = $$"""{ { } {{classAttr}} } } }""" + let strs = another.GetStrings() |> toArray + strs |> equal [|"{ { } "; " } } }"|] + let args = another.GetArguments() + args |> equal [|classAttr|] + + let another :FormattableString = $"""{{ {{{{ }}}}}} {classAttr} }}}} }} }}}}""" + let strs = another.GetStrings() |> toArray + strs |> equal [|"{ {{ }}} "; " }} } }}"|] + let args = another.GetArguments() + args |> equal [|classAttr|] #endif ]