Created
March 20, 2019 21:30
-
-
Save hundt/fc27e649a01722a8466987b0f102fe5d to your computer and use it in GitHub Desktop.
Initial implementation of Template Literal support - does not work with braces inside ${}
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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