diff --git a/CHANGELOG.md b/CHANGELOG.md index de4d8c9b6..84f4cfd79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ When adding entries, please treat them as if they could end up in a release any Thank you! +# 0.19.10 + +- Fix codegen bug where string literals containing both `$` and `"` generated code that failed to compile on Scala 2.12 + # 0.19.9 - Support `@structurePattern` targeting unions using `{label}` and `{value}` magic identifiers in [#1978](https://github.com/disneystreaming/smithy4s/pull/1978) diff --git a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala index ad2ee518a..911487eec 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala @@ -28,6 +28,7 @@ import software.amazon.smithy.model.node.Node import software.amazon.smithy.model.node._ import software.amazon.smithy.model.shapes.ShapeId +import scala.annotation.nowarn import scala.jdk.CollectionConverters._ import Line._ @@ -1968,7 +1969,7 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => } private def escapeForStringLiteral(raw: String): String = { - val sb = new StringBuilder("\"") + val sb = new StringBuilder("") raw.foreach { case '\b' => sb.append("\\b") case '\t' => sb.append("\\t") @@ -1980,7 +1981,6 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => case c if c >= ' ' && c <= '~' => sb.append(c) case c => sb.append("\\u%04x".format(c.toInt)) } - sb.append('"') sb.toString() } @@ -1995,7 +1995,14 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => // render any such strings as interpolated strings (even though that would // otherwise be unecessary) so that we can render "$" as "$$", which get // converted back to "$" during interpolation. - val escaped = if (str.contains('$')) s"s${str.replace("$", "$$")}" else str + + // However when using interpolated strings, we cannot use normal escaping + // for double quotes as it is not supported by scala 2.12. + val escaped = if (str.contains('$')) { + @nowarn("msg=possible missing interpolator") + val replaced = str.replace("$", "$$").replace("\\\"", "${'\\\"'}") + s"""s"$replaced"""" + } else s""""$str"""" line"$escaped" } diff --git a/modules/codegen/test/src/smithy4s/codegen/internals/RendererSpec.scala b/modules/codegen/test/src/smithy4s/codegen/internals/RendererSpec.scala index 8b995a6cc..577abdb83 100644 --- a/modules/codegen/test/src/smithy4s/codegen/internals/RendererSpec.scala +++ b/modules/codegen/test/src/smithy4s/codegen/internals/RendererSpec.scala @@ -22,6 +22,8 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.EnumShape import software.amazon.smithy.model.shapes.StructureShape +import scala.annotation.nowarn + final class RendererSpec extends munit.ScalaCheckSuite { import TestUtils._ @@ -456,6 +458,33 @@ final class RendererSpec extends munit.ScalaCheckSuite { ) } + test( + "string literal containing $ and \" is rendered correctly" + ) { + val smithy = """ + |$version: "2.0" + | + |namespace smithy4s + | + |/// foo $ " + |string MyString + |""".stripMargin + + val contents = generateScalaCode(smithy).values + + @nowarn("msg=possible missing interpolator") + val expected = + """Hints.dynamic(ShapeId("smithy.api", "documentation"), smithy4s.Document.fromString(s"foo $$ ${'\"'}"))""" + assert( + contents.exists( + _.contains( + expected + ) + ) + ) + assert(s"foo $$ ${'\"'}" == "foo $ \"") + } + test( "string literal containing /* is rendered as a string with * escaped as *" ) {