Skip to content

Commit

Permalink
fix #1322: avoid "</script>" in generated code
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed May 27, 2021
1 parent 9b21e79 commit 5e749a2
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `</script>` ([#1322](https://github.com/evanw/esbuild/issues/1322))

If the output of esbuild is inlined into a `<script>...</script>` tag inside an HTML file, the character sequence `</script>` 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('</script>')
console.log(1</script>/.exec(x).length)
// @license </script>
```

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))
Expand Down
17 changes: 14 additions & 3 deletions internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, '\\')
Expand Down Expand Up @@ -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 "</script>" sequence
if last := buffer[n-1]; last == '/' || (last == '<' && strings.HasPrefix(e.Value, "/script>")) {
p.print(" ")
}
}
p.print(e.Value)

Expand Down Expand Up @@ -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 "</script>"
text = strings.ReplaceAll(text, "</script>", "<\u2215script>")

if strings.HasPrefix(text, "/*") {
// Re-indent multi-line comments
for {
Expand Down
27 changes: 25 additions & 2 deletions internal/js_printer/js_printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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)
})
}

Expand Down Expand Up @@ -905,3 +905,26 @@ func TestJSX(t *testing.T) {
expectPrintedJSXMinify(t, "<a> x <b/> y </a>", "<a> x <b/> y </a>;")
expectPrintedJSXMinify(t, "<a>{' x '}{'<b/>'}{' y '}</a>", "<a> x {\"<b/>\"} y </a>;")
}

func TestAvoidSlashScript(t *testing.T) {
// Positive cases
expectPrinted(t, "x = '</script>'", "x = \"<\\/script>\";\n")
expectPrinted(t, "x = `</script>`", "x = `<\\/script>`;\n")
expectPrinted(t, "x = `</script>${y}`", "x = `<\\/script>${y}`;\n")
expectPrinted(t, "x = `${y}</script>`", "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, "//! </script>", "//! <\u2215script>\n")

// Negative cases
expectPrinted(t, "x = '</'", "x = \"</\";\n")
expectPrinted(t, "x = '</script'", "x = \"</script\";\n")
expectPrinted(t, "x = '/script>'", "x = \"/script>\";\n")
expectPrinted(t, "x = '<script>'", "x = \"<script>\";\n")
expectPrinted(t, "x = '</script<'", "x = \"</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;")
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;")
expectPrintedMinify(t, "x = 1 <= /script>/.exec(y).length", "x=1<=/script>/.exec(y).length;")
}

0 comments on commit 5e749a2

Please sign in to comment.