diff --git a/CHANGELOG.md b/CHANGELOG.md index 136973fced3..95aa2edba1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,26 @@ In TypeScript 4.2 and below, the TypeScript compiler would generate code that prints `set 123` when `tsconfig.json` contains `"target": "ESNext"` but in TypeScript 4.3, the TypeScript compiler will now generate code that doesn't print anything. This is the difference between "assign" semantics and "define" semantics. With this release, esbuild has been changed to follow the TypeScript 4.3 behavior. +* Avoid generating the character sequence `` ([#1322](https://github.com/evanw/esbuild/issues/1322)) + + If the output of esbuild is inlined into a `` tag inside an HTML file, the character sequence `` inside the JavaScript code will accidentally cause the script tag to be terminated early. There are at least three such cases where this can happen: + + ```js + console.log('') + console.log(1/.exec(x).length) + // @license + ``` + + With this release, esbuild will now handle all of these cases and avoid generating the problematic character sequence: + + ```js + console.log('<\/script>'); + console.log(1< /script>/.exec(x).length); + // @license <∕script> + ``` + + The `@license` comment uses a different slash character. I'm not sure how to handle that one since it's meant for humans but that seemed like the least intrusive method. I expect this case to not ever come up in practice anyway. + ## 0.12.4 * Reorder name preservation before TypeScript decorator evaluation ([#1316](https://github.com/evanw/esbuild/issues/1316)) diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index 5d8e925a18c..a5d14a3a290 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -306,6 +306,12 @@ func (p *printer) printUnquotedUTF16(text []uint16, quote rune) { case '\\': js = append(js, "\\\\"...) + case '/': + if i >= 2 && text[i-2] == '<' && i+7 <= len(text) && js_lexer.UTF16EqualsString(text[i:i+7], "script>") { + js = append(js, '\\') + } + js = append(js, '/') + case '\'': if quote == '\'' { js = append(js, '\\') @@ -2088,9 +2094,11 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla buffer := p.js n := len(buffer) - // Avoid forming a single-line comment - if n > 0 && buffer[n-1] == '/' { - p.print(" ") + if n > 0 { + // Avoid forming a single-line comment or "" sequence + if last := buffer[n-1]; last == '/' || (last == '<' && strings.HasPrefix(e.Value, "/script>")) { + p.print(" ") + } } p.print(e.Value) @@ -2701,6 +2709,9 @@ func (p *printer) printIf(s *js_ast.SIf) { } func (p *printer) printIndentedComment(text string) { + // Avoid generating a comment containing the character sequence "" + text = strings.ReplaceAll(text, "", "<\u2215script>") + if strings.HasPrefix(text, "/*") { // Re-indent multi-line comments for { diff --git a/internal/js_printer/js_printer_test.go b/internal/js_printer/js_printer_test.go index d59bda12fc4..0b39bd8fbb7 100644 --- a/internal/js_printer/js_printer_test.go +++ b/internal/js_printer/js_printer_test.go @@ -30,7 +30,7 @@ func expectPrintedCommon(t *testing.T, name string, contents string, expected st for _, msg := range msgs { text += msg.String(logger.OutputOptions{}, logger.TerminalInfo{}) } - assertEqual(t, text, "") + test.AssertEqualWithDiff(t, text, "") if !ok { t.Fatal("Parse error") } @@ -43,7 +43,7 @@ func expectPrintedCommon(t *testing.T, name string, contents string, expected st RemoveWhitespace: options.RemoveWhitespace, UnsupportedFeatures: options.UnsupportedJSFeatures, }).JS - assertEqual(t, string(js), expected) + test.AssertEqualWithDiff(t, string(js), expected) }) } @@ -905,3 +905,26 @@ func TestJSX(t *testing.T) { expectPrintedJSXMinify(t, " x y ", " x y ;") expectPrintedJSXMinify(t, "{' x '}{''}{' y '}", " x {\"\"} y ;") } + +func TestAvoidSlashScript(t *testing.T) { + // Positive cases + expectPrinted(t, "x = ''", "x = \"<\\/script>\";\n") + expectPrinted(t, "x = ``", "x = `<\\/script>`;\n") + expectPrinted(t, "x = `${y}`", "x = `<\\/script>${y}`;\n") + expectPrinted(t, "x = `${y}`", "x = `${y}<\\/script>`;\n") + expectPrintedMinify(t, "x = 1 < /script>/.exec(y).length", "x=1< /script>/.exec(y).length;") + expectPrintedMinify(t, "x = 1 << /script>/.exec(y).length", "x=1<< /script>/.exec(y).length;") + expectPrinted(t, "//! ", "//! <\u2215script>\n") + + // Negative cases + expectPrinted(t, "x = ''", "x = \"/script>\";\n") + expectPrinted(t, "x = '/.exec(y).length", "x=1<=/script>/.exec(y).length;") +}