diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 316798245e69..c73edc9f25b5 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -14,3 +14,5 @@ ### Bug Fixes +- [codegen/node] - Fix an issue with escaping deprecation messages. + [#9371](https://github.com/pulumi/pulumi/pull/9371) \ No newline at end of file diff --git a/pkg/codegen/nodejs/gen.go b/pkg/codegen/nodejs/gen.go index c682e44847bb..395a2202b8ff 100644 --- a/pkg/codegen/nodejs/gen.go +++ b/pkg/codegen/nodejs/gen.go @@ -648,7 +648,7 @@ func (mod *modContext) genResource(w io.Writer, r *schema.Resource) error { fmt.Fprintf(w, " public static get(name: string, id: pulumi.Input, %sopts?: pulumi.%s): %s {\n", stateParam, optionsType, name) if r.DeprecationMessage != "" && mod.compatibility != kubernetes20 { - fmt.Fprintf(w, " pulumi.log.warn(\"%s is deprecated: %s\")\n", name, r.DeprecationMessage) + fmt.Fprintf(w, " pulumi.log.warn(\"%s is deprecated: %s\")\n", name, escape(r.DeprecationMessage)) } fmt.Fprintf(w, " return new %s(name, %s{ ...opts, id: id });\n", name, stateRef) fmt.Fprintf(w, " }\n") @@ -815,7 +815,7 @@ func (mod *modContext) genResource(w io.Writer, r *schema.Resource) error { argsType, stateType, optionsType) } if r.DeprecationMessage != "" && mod.compatibility != kubernetes20 { - fmt.Fprintf(w, " pulumi.log.warn(\"%s is deprecated: %s\")\n", name, r.DeprecationMessage) + fmt.Fprintf(w, " pulumi.log.warn(\"%s is deprecated: %s\")\n", name, escape(r.DeprecationMessage)) } fmt.Fprintf(w, " let resourceInputs: pulumi.Inputs = {};\n") fmt.Fprintf(w, " opts = opts || {};\n") @@ -954,7 +954,7 @@ func (mod *modContext) genResource(w io.Writer, r *schema.Resource) error { fmt.Fprintf(w, " %s(%s): %s {\n", methodName, argsig, retty) if fun.DeprecationMessage != "" { fmt.Fprintf(w, " pulumi.log.warn(\"%s.%s is deprecated: %s\")\n", name, methodName, - fun.DeprecationMessage) + escape(fun.DeprecationMessage)) } // Zero initialize the args if empty and necessary. @@ -1084,7 +1084,7 @@ func (mod *modContext) genFunction(w io.Writer, fun *schema.Function) error { fmt.Fprintf(w, "export function %s(%sopts?: pulumi.InvokeOptions): Promise<%s> {\n", name, argsig, functionReturnType(fun)) if fun.DeprecationMessage != "" && mod.compatibility != kubernetes20 { - fmt.Fprintf(w, " pulumi.log.warn(\"%s is deprecated: %s\")\n", name, fun.DeprecationMessage) + fmt.Fprintf(w, " pulumi.log.warn(\"%s is deprecated: %s\")\n", name, escape(fun.DeprecationMessage)) } // Zero initialize the args if empty and necessary. diff --git a/pkg/codegen/nodejs/utilities.go b/pkg/codegen/nodejs/utilities.go index b9bad2719ab5..ab2594f4bf75 100644 --- a/pkg/codegen/nodejs/utilities.go +++ b/pkg/codegen/nodejs/utilities.go @@ -15,6 +15,7 @@ package nodejs import ( + "encoding/json" "fmt" "io" "regexp" @@ -22,6 +23,7 @@ import ( "unicode" "github.com/pulumi/pulumi/pkg/v3/codegen" + "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" ) // isReservedWord returns true if s is a reserved word as per ECMA-262. @@ -126,3 +128,16 @@ func makeSafeEnumName(name, typeName string) (string, error) { return safeName, nil } + +// escape returns the string escaped for a JS string literal +func escape(s string) string { + // Seems the most fool-proof way of doing this is by using the JSON marshaler and then stripping the surrounding quotes + escaped, err := json.Marshal(s) + contract.AssertNoError(err) + contract.Assertf(len(escaped) >= 2, "JSON(%s) expected a quoted string but returned %s", s, escaped) + contract.Assertf( + escaped[0] == byte('"') && escaped[len(escaped)-1] == byte('"'), + "JSON(%s) expected a quoted string but returned %s", s, escaped) + + return string(escaped)[1:(len(escaped) - 1)] +} diff --git a/pkg/codegen/nodejs/utilities_test.go b/pkg/codegen/nodejs/utilities_test.go index 6fc9c33e0b28..b0749ebe7a3e 100644 --- a/pkg/codegen/nodejs/utilities_test.go +++ b/pkg/codegen/nodejs/utilities_test.go @@ -43,3 +43,27 @@ func TestMakeSafeEnumName(t *testing.T) { }) } } + +func TestEscape(t *testing.T) { + t.Parallel() + tests := []struct { + input string + expected string + }{ + {"test", "test"}, + {"sub\"string\"", "sub\\\"string\\\""}, + {"slash\\s", "slash\\\\s"}, + {"N\\A \"bad data\"", "N\\\\A \\\"bad data\\\""}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.input, func(t *testing.T) { + t.Parallel() + + got := escape(tt.input) + if tt.expected != got { + t.Errorf("escape(%s) was %s want %s", tt.input, got, tt.expected) + } + }) + } +}