Skip to content

Instantly share code, notes, and snippets.

@hundt
Created March 20, 2019 21:30
Show Gist options
  • Save hundt/fc27e649a01722a8466987b0f102fe5d to your computer and use it in GitHub Desktop.
Save hundt/fc27e649a01722a8466987b0f102fe5d to your computer and use it in GitHub Desktop.
Initial implementation of Template Literal support - does not work with braces inside ${}
diff --git a/src/html/template/context.go b/src/html/template/context.go
index 7ab3d1fed6..01ee09b5e8 100644
--- a/src/html/template/context.go
+++ b/src/html/template/context.go
@@ -16,17 +16,18 @@ import (
// https://www.w3.org/TR/html5/syntax.html#the-end
// where the context element is null.
type context struct {
- state state
- delim delim
- urlPart urlPart
- jsCtx jsCtx
- attr attr
- element element
- err *Error
+ state state
+ delim delim
+ urlPart urlPart
+ jsCtx jsCtx
+ jsTLDepth uint
+ attr attr
+ element element
+ err *Error
}
func (c context) String() string {
- return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, c.err)
+ return fmt.Sprintf("{%v %v %v %v jsTLDepth=%v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.jsTLDepth, c.attr, c.element, c.err)
}
// eq reports whether two contexts are equal.
@@ -112,6 +113,8 @@ const (
stateJSDqStr
// stateJSSqStr occurs inside a JavaScript single quoted string.
stateJSSqStr
+ // stateJSBtStr occurs inside a JavaScript backtick-quoted template literal.
+ stateJSTLStr
// stateJSRegexp occurs inside a JavaScript regexp literal.
stateJSRegexp
// stateJSBlockCmt occurs inside a JavaScript /* block comment */.
diff --git a/src/html/template/escape.go b/src/html/template/escape.go
index f12dafa870..5829efb4b7 100644
--- a/src/html/template/escape.go
+++ b/src/html/template/escape.go
@@ -9,6 +9,7 @@ import (
"fmt"
"html"
"io"
+ "log"
"text/template"
"text/template/parse"
)
@@ -201,7 +202,7 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
s = append(s, "_html_template_jsvalescaper")
// A slash after a value starts a div operator.
c.jsCtx = jsCtxDivOp
- case stateJSDqStr, stateJSSqStr:
+ case stateJSDqStr, stateJSSqStr, stateJSTLStr:
s = append(s, "_html_template_jsstrescaper")
case stateJSRegexp:
s = append(s, "_html_template_jsregexpescaper")
@@ -647,6 +648,7 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context {
s, written, i, b := n.Text, 0, 0, new(bytes.Buffer)
for i != len(s) {
c1, nread := contextAfterText(c, s[i:])
+ log.Printf("Parse \"%s\": %s", s[i:][:nread], c1)
i1 := i + nread
if c.state == stateText || c.state == stateRCDATA {
end := i1
diff --git a/src/html/template/js.go b/src/html/template/js.go
index 04c7c325db..e280a6a655 100644
--- a/src/html/template/js.go
+++ b/src/html/template/js.go
@@ -293,12 +293,14 @@ var jsStrReplacementTable = []string{
// Encode HTML specials as hex so the output can be embedded
// in HTML attributes without further encoding.
'"': `\x22`,
+ '$': `\x24`,
'&': `\x26`,
'\'': `\x27`,
'+': `\x2b`,
'/': `\/`,
'<': `\x3c`,
'>': `\x3e`,
+ '`': `\x60`,
'\\': `\\`,
}
@@ -314,12 +316,14 @@ var jsStrNormReplacementTable = []string{
// Encode HTML specials as hex so the output can be embedded
// in HTML attributes without further encoding.
'"': `\x22`,
+ '$': `\x24`,
'&': `\x26`,
'\'': `\x27`,
'+': `\x2b`,
'/': `\/`,
'<': `\x3c`,
'>': `\x3e`,
+ '`': `\x60`,
}
var jsRegexpReplacementTable = []string{
diff --git a/src/html/template/transition.go b/src/html/template/transition.go
index 06df679330..5d9710ae59 100644
--- a/src/html/template/transition.go
+++ b/src/html/template/transition.go
@@ -27,6 +27,7 @@ var transitionFunc = [...]func(context, []byte) (context, int){
stateJS: tJS,
stateJSDqStr: tJSDelimited,
stateJSSqStr: tJSDelimited,
+ stateJSTLStr: tJSDelimited,
stateJSRegexp: tJSDelimited,
stateJSBlockCmt: tBlockCmt,
stateJSLineCmt: tLineCmt,
@@ -262,7 +263,7 @@ func tURL(c context, s []byte) (context, int) {
// tJS is the context transition function for the JS state.
func tJS(c context, s []byte) (context, int) {
- i := bytes.IndexAny(s, `"'/`)
+ i := bytes.IndexAny(s, "\"'/`}")
if i == -1 {
// Entire input is non string, comment, regexp tokens.
c.jsCtx = nextJSCtx(s, c.jsCtx)
@@ -274,6 +275,14 @@ func tJS(c context, s []byte) (context, int) {
c.state, c.jsCtx = stateJSDqStr, jsCtxRegexp
case '\'':
c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp
+ case '`':
+ c.state, c.jsCtx, c.jsTLDepth = stateJSTLStr, jsCtxRegexp, c.jsTLDepth+1
+ case '}':
+ if c.jsTLDepth > 0 {
+ // In a placeholder. Return to template-literal-quoted state.
+ c.state, c.jsCtx = stateJSTLStr, jsCtxRegexp
+ return c, i + 1
+ }
case '/':
switch {
case i+1 < len(s) && s[i+1] == '/':
@@ -303,6 +312,8 @@ func tJSDelimited(c context, s []byte) (context, int) {
switch c.state {
case stateJSSqStr:
specials = `\'`
+ case stateJSTLStr:
+ specials = "\\`$"
case stateJSRegexp:
specials = `\/[]`
}
@@ -326,9 +337,17 @@ func tJSDelimited(c context, s []byte) (context, int) {
inCharset = true
case ']':
inCharset = false
+ case '$':
+ if c.state == stateJSTLStr && i+1 < len(s) && s[i+1] == '{' {
+ c.state, c.jsCtx = stateJS, jsCtxDivOp
+ return c, i + 2
+ }
default:
// end delimiter
if !inCharset {
+ if c.state == stateJSTLStr {
+ c.jsTLDepth -= 1
+ }
c.state, c.jsCtx = stateJS, jsCtxDivOp
return c, i + 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment