From 0253c688d07eb8522641388b58e84d69a40646bb Mon Sep 17 00:00:00 2001 From: Mike Samuel Date: Thu, 1 Sep 2011 12:03:40 +1000 Subject: [PATCH] exp/template/html: Implement grammar for JS. This transitions into a JS state when entering any attribute whose name starts with "on". It does not yet enter a JS on entry into a once that CL + // has been merged. + + i := bytes.IndexAny(s, `"'/`) + if i == -1 { + // Entire input is non string, comment, regexp tokens. + c.jsCtx = nextJSCtx(s, c.jsCtx) + return c, nil + } + c.jsCtx = nextJSCtx(s[:i], c.jsCtx) + switch s[i] { + case '"': + c.state, c.jsCtx = stateJSDqStr, jsCtxRegexp + case '\'': + c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp + case '/': + switch { + case i+1 < len(s) && s[i+1] == '/': + c.state = stateJSLineCmt + case i+1 < len(s) && s[i+1] == '*': + c.state = stateJSBlockCmt + case c.jsCtx == jsCtxRegexp: + c.state = stateJSRegexp + default: + c.jsCtx = jsCtxRegexp + } + default: + panic("unreachable") + } + return c, s[i+1:] +} + +// tJSStr is the context transition function for the JS string states. +func tJSStr(c context, s []byte) (context, []byte) { + // TODO: delegate to tSpecialTagEnd to find any once that CL + // has been merged. + + quoteAndEsc := `\"` + if c.state == stateJSSqStr { + quoteAndEsc = `\'` + } + + b := s + for { + i := bytes.IndexAny(b, quoteAndEsc) + if i == -1 { + return c, nil + } + if b[i] == '\\' { + i++ + if i == len(b) { + return context{ + state: stateError, + errStr: fmt.Sprintf("unfinished escape sequence in JS string: %q", s), + }, nil + } + } else { + c.state, c.jsCtx = stateJS, jsCtxDivOp + return c, b[i+1:] + } + b = b[i+1:] + } + panic("unreachable") +} + +// tJSRegexp is the context transition function for the /RegExp/ literal state. +func tJSRegexp(c context, s []byte) (context, []byte) { + // TODO: delegate to tSpecialTagEnd to find any once that CL + // has been merged. + + b := s + inCharset := false + for { + i := bytes.IndexAny(b, `/[\]`) + if i == -1 { + break + } + switch b[i] { + case '/': + if !inCharset { + c.state, c.jsCtx = stateJS, jsCtxDivOp + return c, b[i+1:] + } + case '\\': + i++ + if i == len(b) { + return context{ + state: stateError, + errStr: fmt.Sprintf("unfinished escape sequence in JS regexp: %q", s), + }, nil + } + case '[': + inCharset = true + case ']': + inCharset = false + default: + panic("unreachable") + } + b = b[i+1:] + } + + if inCharset { + // This can be fixed by making context richer if interpolation + // into charsets is desired. + return context{ + state: stateError, + errStr: fmt.Sprintf("unfinished JS regexp charset: %q", s), + }, nil + } + + return c, nil +} + +var blockCommentEnd = []byte("*/") + +// tJSBlockCmt is the context transition function for the JS /*comment*/ state. +func tJSBlockCmt(c context, s []byte) (context, []byte) { + // TODO: delegate to tSpecialTagEnd to find any once that CL + // has been merged. + + i := bytes.Index(s, blockCommentEnd) + if i == -1 { + return c, nil + } + c.state = stateJS + return c, s[i+2:] +} + +// tJSLineCmt is the context transition function for the JS //comment state. +func tJSLineCmt(c context, s []byte) (context, []byte) { + // TODO: delegate to tSpecialTagEnd to find any once that CL + // has been merged. + + i := bytes.IndexAny(s, "\r\n\u2028\u2029") + if i == -1 { + return c, nil + } + c.state = stateJS + // Per section 7.4 of EcmaScript 5 : http://es5.github.com/#x7.4 + // "However, the LineTerminator at the end of the line is not + // considered to be part of the single-line comment; it is recognised + // separately by the lexical grammar and becomes part of the stream of + // input elements for the syntactic grammar." + return c, s[i:] +} + // tError is the context transition function for the error state. func tError(c context, s []byte) (context, []byte) { return c, nil diff --git a/src/pkg/exp/template/html/escape_test.go b/src/pkg/exp/template/html/escape_test.go index a911c7d835..6f5ecf6ef3 100644 --- a/src/pkg/exp/template/html/escape_test.go +++ b/src/pkg/exp/template/html/escape_test.go @@ -8,6 +8,7 @@ import ( "bytes" "strings" "template" + "template/parse" "testing" ) @@ -16,6 +17,8 @@ func TestEscape(t *testing.T) { F, T bool C, G, H string A, E []string + N int + Z *int }{ F: false, T: true, @@ -24,9 +27,11 @@ func TestEscape(t *testing.T) { H: "", A: []string{"", ""}, E: []string{}, + N: 42, + Z: nil, } - var testCases = []struct { + tests := []struct { name string input string output string @@ -141,29 +146,71 @@ func TestEscape(t *testing.T) { ``, ``, }, + { + "jsStrValue", + "