From 2c669a0e5b815ac3732a8c140169934d1c6b15dc Mon Sep 17 00:00:00 2001 From: Rob Lenders Date: Tue, 17 Sep 2024 23:09:40 -0500 Subject: [PATCH 1/5] Handle escaping of { and } in FormattableString --- src/Fable.Transforms/BabelPrinter.fs | 7 +++++- tests/Js/Main/StringTests.fs | 32 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/Fable.Transforms/BabelPrinter.fs b/src/Fable.Transforms/BabelPrinter.fs index e4ba357eb2..71749afc9c 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, @"(? 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 () -> + let classAttr = "item-panel" + let cssNew :FormattableString = $$""".{{classAttr}}:hover {background-color: #eee;}""" + let strs = cssNew.GetStrings() + 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() + 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() + strs |> equal [|"."; ":hover {background-color: #eee;}"|] + let args = cssNew.GetArguments() + args |> equal [|classAttr|] + + let another :FormattableString = $$"""{ { } {{classAttr}} } } }""" + let strs = another.GetStrings() + strs |> equal [|"{ { } "; " } } }"|] + let args = another.GetArguments() + args |> equal [|classAttr|] + + let another :FormattableString = $"""{{ {{{{ }}}}}} {classAttr} }}}} }} }}}}""" + let strs = another.GetStrings() + strs |> equal [|"{ {{ }}} "; " }} } }}"|] + let args = another.GetArguments() + args |> equal [|classAttr|] #endif ] From d0474657cb6644163dea7d7591a2c45377a8ddee Mon Sep 17 00:00:00 2001 From: Rob Lenders Date: Wed, 18 Sep 2024 10:15:42 -0500 Subject: [PATCH 2/5] Make getFormat escape braces --- src/fable-library-ts/String.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/fable-library-ts/String.ts b/src/fable-library-ts/String.ts index 925ca8a32a..d1f5d243ae 100644 --- a/src/fable-library-ts/String.ts +++ b/src/fable-library-ts/String.ts @@ -87,7 +87,7 @@ export function indexOfAny(str: string, anyOf: string[], ...args: number[]) { } const endIndex = startIndex + length const anyOfAsStr = "".concat.apply("", anyOf); - for (let i=startIndex; i -1) { return i; } @@ -588,6 +588,8 @@ export function fmtWith(fmts: string[]) { export function getFormat(s: FormattableString) { 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); + ? s.strs.map((s, _) => s.replace('{', '{{').replace('}', '}}')) + .reduce((acc, newPart, index) => acc + `{${String(index - 1) + s.fmts![index - 1]}}` + newPart) + : s.strs.map((s, _) => s.replace('{', '{{').replace('}', '}}')) + .reduce((acc, newPart, index) => acc + `{${index - 1}}` + newPart); } From 74be9e7bebebf64396cbc773e5e248eff19555ca Mon Sep 17 00:00:00 2001 From: Maxime Mangel Date: Thu, 19 Sep 2024 17:20:15 +0200 Subject: [PATCH 3/5] fix: make tests pass TypeScript + fix escaping of braces --- src/fable-library-ts/String.ts | 6 ++++-- tests/Js/Main/StringTests.fs | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/fable-library-ts/String.ts b/src/fable-library-ts/String.ts index d1f5d243ae..955cf06971 100644 --- a/src/fable-library-ts/String.ts +++ b/src/fable-library-ts/String.ts @@ -587,9 +587,11 @@ export function fmtWith(fmts: string[]) { } export function getFormat(s: FormattableString) { + const strs = s.strs.map((value) => value.replace(/{/g, '{{').replace(/}/g, '}}')); + return s.fmts - ? s.strs.map((s, _) => s.replace('{', '{{').replace('}', '}}')) + ? strs .reduce((acc, newPart, index) => acc + `{${String(index - 1) + s.fmts![index - 1]}}` + newPart) - : s.strs.map((s, _) => s.replace('{', '{{').replace('}', '}}')) + : strs .reduce((acc, newPart, index) => acc + `{${index - 1}}` + newPart); } diff --git a/tests/Js/Main/StringTests.fs b/tests/Js/Main/StringTests.fs index 5d65415207..4b9ae3bb5d 100644 --- a/tests/Js/Main/StringTests.fs +++ b/tests/Js/Main/StringTests.fs @@ -1161,6 +1161,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 @@ -1183,31 +1185,31 @@ let tests = testList "Strings" [ testCase "FormattableString fragments handle { and }" <| fun () -> let classAttr = "item-panel" let cssNew :FormattableString = $$""".{{classAttr}}:hover {background-color: #eee;}""" - let strs = cssNew.GetStrings() + 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() + 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() + 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() + let strs = another.GetStrings() |> toArray strs |> equal [|"{ { } "; " } } }"|] let args = another.GetArguments() args |> equal [|classAttr|] let another :FormattableString = $"""{{ {{{{ }}}}}} {classAttr} }}}} }} }}}}""" - let strs = another.GetStrings() + let strs = another.GetStrings() |> toArray strs |> equal [|"{ {{ }}} "; " }} } }}"|] let args = another.GetArguments() args |> equal [|classAttr|] From 31fed207606f700a3c7b2d292c5d6186671d4fac Mon Sep 17 00:00:00 2001 From: Maxime Mangel Date: Thu, 19 Sep 2024 17:34:35 +0200 Subject: [PATCH 4/5] fix: add missing function definition --- tests/Js/Main/StringTests.fs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Js/Main/StringTests.fs b/tests/Js/Main/StringTests.fs index 4b9ae3bb5d..31a63960cc 100644 --- a/tests/Js/Main/StringTests.fs +++ b/tests/Js/Main/StringTests.fs @@ -1183,6 +1183,12 @@ let tests = testList "Strings" [ 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 From 709eb74731d268b71593782af20c057a939f1bb3 Mon Sep 17 00:00:00 2001 From: Maxime Mangel Date: Thu, 19 Sep 2024 17:39:09 +0200 Subject: [PATCH 5/5] chore: add changelog entry --- src/Fable.Cli/CHANGELOG.md | 4 ++++ src/fable-library-ts/CHANGELOG.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index bd3b720603..db5040bdd9 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -11,6 +11,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-library-ts/CHANGELOG.md b/src/fable-library-ts/CHANGELOG.md index 33f3b8a637..c9f5ec3298 100644 --- a/src/fable-library-ts/CHANGELOG.md +++ b/src/fable-library-ts/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +* [JS/TS] Fix escaping of `{` and `}` in FormattableString (#3890) (by @roboz0r) + ## 1.4.3 - 2024-09-04 * [JS/TS] Fixed Decimal comparisons (#3884) (by @ncave)